diff --git a/app/finders/ci/runners_finder.rb b/app/finders/ci/runners_finder.rb
index 4c4bd3fc5a62cd32de5ed496f805ba613624d7ee..356915722fe3add6f58fc3fa101f35402ad6ff87 100644
--- a/app/finders/ci/runners_finder.rb
+++ b/app/finders/ci/runners_finder.rb
@@ -4,7 +4,7 @@ module Ci
class RunnersFinder < UnionFinder
include Gitlab::Allowable
- ALLOWED_SORTS = %w[contacted_asc contacted_desc created_at_asc created_at_desc created_date].freeze
+ ALLOWED_SORTS = %w[contacted_asc contacted_desc created_at_asc created_at_desc created_date token_expires_at_asc token_expires_at_desc].freeze
DEFAULT_SORT = 'created_at_desc'
def initialize(current_user:, params:)
diff --git a/app/graphql/types/ci/runner_sort_enum.rb b/app/graphql/types/ci/runner_sort_enum.rb
index 95ec1867fea8c883ed24cf706a597d3a9709b955..8f2a13bd69995a347b5fed4fc0ccf3161c572b97 100644
--- a/app/graphql/types/ci/runner_sort_enum.rb
+++ b/app/graphql/types/ci/runner_sort_enum.rb
@@ -10,6 +10,8 @@ class RunnerSortEnum < BaseEnum
value 'CONTACTED_DESC', 'Ordered by contacted_at in descending order.', value: :contacted_desc
value 'CREATED_ASC', 'Ordered by created_at in ascending order.', value: :created_at_asc
value 'CREATED_DESC', 'Ordered by created_at in descending order.', value: :created_at_desc
+ value 'TOKEN_EXPIRES_AT_ASC', 'Ordered by token_expires_at in ascending order.', value: :token_expires_at_asc
+ value 'TOKEN_EXPIRES_AT_DESC', 'Ordered by token_expires_at in descending order.', value: :token_expires_at_desc
end
end
end
diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb
index f3b7b067c93535a13a974aa8145282fc6cdfa7be..9094c6b96e470b69ee671c0aedc30a56baebcc8d 100644
--- a/app/graphql/types/ci/runner_type.rb
+++ b/app/graphql/types/ci/runner_type.rb
@@ -25,6 +25,9 @@ class RunnerType < BaseObject
field :contacted_at, Types::TimeType, null: true,
description: 'Timestamp of last contact from this runner.',
method: :contacted_at
+ field :token_expires_at, Types::TimeType, null: true,
+ description: 'Runner token expiration time.',
+ method: :token_expires_at
field :maximum_timeout, GraphQL::Types::Int, null: true,
description: 'Maximum timeout (in seconds) for jobs processed by the runner.'
field :access_level, ::Types::Ci::RunnerAccessLevelEnum, null: false,
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index bb12816e0fe176090b3d6573d092de3f88b656d3..24cde068619f70d000b401fd923bdb8dcbf1327c 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -9201,6 +9201,7 @@ Represents the total number of issues and their weights for a particular day.
| `runnerType` | [`CiRunnerType!`](#cirunnertype) | Type of the runner. |
| `shortSha` | [`String`](#string) | First eight characters of the runner's token used to authenticate new job requests. Used as the runner's unique ID. |
| `tagList` | [`[String!]`](#string) | Tags associated with the runner. |
+| `tokenExpiresAt` | [`Time`](#time) | Runner token expiration time. |
| `userPermissions` | [`RunnerPermissions!`](#runnerpermissions) | Permissions for the current user on the resource. |
| `version` | [`String`](#string) | Version of the runner. |
@@ -16796,6 +16797,8 @@ Values for sorting runners.
| `CONTACTED_DESC` | Ordered by contacted_at in descending order. |
| `CREATED_ASC` | Ordered by created_at in ascending order. |
| `CREATED_DESC` | Ordered by created_at in descending order. |
+| `TOKEN_EXPIRES_AT_ASC` | Ordered by token_expires_at in ascending order. |
+| `TOKEN_EXPIRES_AT_DESC` | Ordered by token_expires_at in descending order. |
### `CiRunnerStatus`
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index 6665b7b76a0ce76436e51df919a2469b42bdb6a0..180264122617323c1c1b4c68733089f5b472f883 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -13,6 +13,7 @@
transient do
groups { [] }
projects { [] }
+ token_expires_at { nil }
end
after(:build) do |runner, evaluator|
@@ -25,6 +26,10 @@
end
end
+ after(:create) do |runner, evaluator|
+ runner.update!(token_expires_at: evaluator.token_expires_at) if evaluator.token_expires_at
+ end
+
trait :online do
contacted_at { Time.now }
end
diff --git a/spec/finders/ci/runners_finder_spec.rb b/spec/finders/ci/runners_finder_spec.rb
index 7e3c1abd6d1e94fab3624b5be75d1cb254afe8c6..e7ec4f019959d8963e553b377691c2acc94ef75c 100644
--- a/spec/finders/ci/runners_finder_spec.rb
+++ b/spec/finders/ci/runners_finder_spec.rb
@@ -91,8 +91,8 @@
end
context 'sorting' do
- let_it_be(:runner1) { create :ci_runner, created_at: '2018-07-12 07:00', contacted_at: 1.minute.ago }
- let_it_be(:runner2) { create :ci_runner, created_at: '2018-07-12 08:00', contacted_at: 3.minutes.ago }
+ let_it_be(:runner1) { create :ci_runner, created_at: '2018-07-12 07:00', contacted_at: 1.minute.ago, token_expires_at: '2022-02-15 07:00' }
+ let_it_be(:runner2) { create :ci_runner, created_at: '2018-07-12 08:00', contacted_at: 3.minutes.ago, token_expires_at: '2022-02-15 06:00' }
let_it_be(:runner3) { create :ci_runner, created_at: '2018-07-12 09:00', contacted_at: 2.minutes.ago }
subject do
@@ -142,6 +142,22 @@
is_expected.to eq [runner1, runner3, runner2]
end
end
+
+ context 'with sort param equal to token_expires_at_asc' do
+ let(:params) { { sort: 'token_expires_at_asc' } }
+
+ it 'sorts by contacted_at ascending' do
+ is_expected.to eq [runner2, runner1, runner3]
+ end
+ end
+
+ context 'with sort param equal to token_expires_at_desc' do
+ let(:params) { { sort: 'token_expires_at_desc' } }
+
+ it 'sorts by contacted_at descending' do
+ is_expected.to eq [runner3, runner1, runner2]
+ end
+ end
end
context 'by non admin user' do
diff --git a/spec/graphql/types/ci/runner_type_spec.rb b/spec/graphql/types/ci/runner_type_spec.rb
index 77eac658c1984e4a320974f1f74d44672d8021e2..7697cd0ef7924d851435dc1d72fba7bd4b726850 100644
--- a/spec/graphql/types/ci/runner_type_spec.rb
+++ b/spec/graphql/types/ci/runner_type_spec.rb
@@ -12,7 +12,7 @@
id description created_at contacted_at maximum_timeout access_level active paused status
version short_sha revision locked run_untagged ip_address runner_type tag_list
project_count job_count admin_url edit_admin_url user_permissions executor_name
- groups projects jobs
+ groups projects jobs token_expires_at
]
expect(described_class).to include_graphql_fields(*expected_fields)