Commit 8a0c7e25 authored by Luke Duncalfe's avatar Luke Duncalfe

Merge branch 'pedropombeiro/332345/graphql-add-counts' into 'master'

Add job and project count to Runner GraphQL object

See merge request gitlab-org/gitlab!64117
parents bcf8d48e af9e0ea5
......@@ -6,6 +6,10 @@ module Types
graphql_name 'CiRunner'
authorize :read_runner
JOB_COUNT_LIMIT = 1000
alias_method :runner, :object
field :id, ::Types::GlobalIDType[::Ci::Runner], null: false,
description: 'ID of the runner.'
field :description, GraphQL::STRING_TYPE, null: true,
......@@ -37,6 +41,30 @@ module Types
description: 'Type of the runner.'
field :tag_list, [GraphQL::STRING_TYPE], null: true,
description: 'Tags associated with the runner.'
field :project_count, GraphQL::INT_TYPE, null: true,
description: 'Number of projects that the runner is associated with.'
field :job_count, GraphQL::INT_TYPE, null: true,
description: "Number of jobs processed by the runner (limited to #{JOB_COUNT_LIMIT}, plus one to indicate that more items exist)."
def job_count
# We limit to 1 above the JOB_COUNT_LIMIT to indicate that more items exist after JOB_COUNT_LIMIT
runner.builds.limit(JOB_COUNT_LIMIT + 1).count
end
# rubocop: disable CodeReuse/ActiveRecord
def project_count
BatchLoader::GraphQL.for(runner.id).batch(key: :runner_project_count) do |ids, loader, args|
counts = ::Ci::RunnerProject.select(:runner_id, 'COUNT(*) as count')
.where(runner_id: ids)
.group(:runner_id)
.index_by(&:runner_id)
ids.each do |id|
loader.call(id, counts[id]&.count)
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
......
......@@ -7710,9 +7710,11 @@ Represents the total number of issues and their weights for a particular day.
| <a id="cirunnerdescription"></a>`description` | [`String`](#string) | Description of the runner. |
| <a id="cirunnerid"></a>`id` | [`CiRunnerID!`](#cirunnerid) | ID of the runner. |
| <a id="cirunneripaddress"></a>`ipAddress` | [`String!`](#string) | IP address of the runner. |
| <a id="cirunnerjobcount"></a>`jobCount` | [`Int`](#int) | Number of jobs processed by the runner (limited to 1000, plus one to indicate that more items exist). |
| <a id="cirunnerlocked"></a>`locked` | [`Boolean`](#boolean) | Indicates the runner is locked. |
| <a id="cirunnermaximumtimeout"></a>`maximumTimeout` | [`Int`](#int) | Maximum timeout (in seconds) for jobs processed by the runner. |
| <a id="cirunnerprivateprojectsminutescostfactor"></a>`privateProjectsMinutesCostFactor` | [`Float`](#float) | Private projects' "minutes cost factor" associated with the runner (GitLab.com only). |
| <a id="cirunnerprojectcount"></a>`projectCount` | [`Int`](#int) | Number of projects that the runner is associated with. |
| <a id="cirunnerpublicprojectsminutescostfactor"></a>`publicProjectsMinutesCostFactor` | [`Float`](#float) | Public projects' "minutes cost factor" associated with the runner (GitLab.com only). |
| <a id="cirunnerrevision"></a>`revision` | [`String!`](#string) | Revision of the runner. |
| <a id="cirunnerrununtagged"></a>`runUntagged` | [`Boolean!`](#boolean) | Indicates the runner is able to run untagged jobs. |
......
......@@ -11,6 +11,7 @@ RSpec.describe GitlabSchema.types['CiRunner'] do
expected_fields = %w[
id description contacted_at maximum_timeout access_level active status
version short_sha revision locked run_untagged ip_address runner_type tag_list
project_count job_count
]
expect(described_class).to include_graphql_fields(*expected_fields)
......
......@@ -59,7 +59,9 @@ RSpec.describe 'Query.runner(id)' do
'accessLevel' => runner.access_level.to_s.upcase,
'runUntagged' => runner.run_untagged,
'ipAddress' => runner.ip_address,
'runnerType' => 'INSTANCE_TYPE'
'runnerType' => 'INSTANCE_TYPE',
'jobCount' => 0,
'projectCount' => nil
)
expect(runner_data['tagList']).to match_array runner.tag_list
end
......@@ -111,6 +113,51 @@ RSpec.describe 'Query.runner(id)' do
it_behaves_like 'runner details fetch', :inactive_runner
end
describe 'for multiple runners' do
let_it_be(:project1) { create(:project, :test_repo) }
let_it_be(:project2) { create(:project, :test_repo) }
let_it_be(:project_runner) do
create(:ci_runner, :project, projects: [project1, project2], description: 'Runner 1', contacted_at: 2.hours.ago,
active: true, version: 'adfe156', revision: 'a', locked: true, ip_address: '127.0.0.1', maximum_timeout: 600,
access_level: 0, run_untagged: true)
end
let!(:job) { create(:ci_build, runner: project_runner) }
context 'requesting project and job counts' do
let(:query) do
%(
query {
projectRunner: runner(id: "#{project_runner.to_global_id}") {
projectCount
jobCount
}
activeRunner: runner(id: "#{active_runner.to_global_id}") {
projectCount
jobCount
}
}
)
end
before do
post_graphql(query, current_user: user)
end
it 'retrieves expected fields' do
runner1_data = graphql_data_at(:project_runner)
runner2_data = graphql_data_at(:active_runner)
expect(runner1_data).to match a_hash_including(
'jobCount' => 1,
'projectCount' => 2)
expect(runner2_data).to match a_hash_including(
'jobCount' => 0,
'projectCount' => nil)
end
end
end
describe 'by regular user' do
let(:user) { create_default(:user) }
......@@ -127,11 +174,14 @@ RSpec.describe 'Query.runner(id)' do
def runner_query(runner)
<<~SINGLE
runner(id: "#{runner.to_global_id}") {
#{all_graphql_fields_for('CiRunner')}
#{all_graphql_fields_for('CiRunner', excluded: excluded_fields)}
}
SINGLE
end
# Currently excluding a known N+1 issue, see https://gitlab.com/gitlab-org/gitlab/-/issues/334759
let(:excluded_fields) { %w[jobCount] }
let(:single_query) do
<<~QUERY
{
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment