Commit 35b3e905 authored by Robert May's avatar Robert May

Merge branch '346284-pipeline-counts-resolver' into 'master'

Add a GraphQL resolvers to get pipeline counts by scope

See merge request gitlab-org/gitlab!77949
parents 039ea23b 2fd7902d
# frozen_string_literal: true
module Resolvers
module Ci
class ProjectPipelineCountsResolver < BaseResolver
type Types::Ci::PipelineCountsType, null: true
argument :ref,
GraphQL::Types::String,
required: false,
description: "Filter pipelines by the ref they are run for."
argument :sha,
GraphQL::Types::String,
required: false,
description: "Filter pipelines by the SHA of the commit they are run for."
argument :source,
GraphQL::Types::String,
required: false,
description: "Filter pipelines by their source."
def resolve(**args)
::Gitlab::PipelineScopeCounts.new(context[:current_user], object, args)
end
end
end
end
# frozen_string_literal: true
module Types
module Ci
class PipelineCountsType < BaseObject
graphql_name 'PipelineCounts'
description "Represents pipeline counts for the project"
authorize :read_pipeline
(::Types::Ci::PipelineScopeEnum.values.keys - %w[BRANCHES TAGS]).each do |scope|
field scope.downcase,
GraphQL::Types::Int,
null: true,
description: "Number of pipelines with scope #{scope} for the project"
end
field :all,
GraphQL::Types::Int,
null: true,
description: 'Total number of pipelines for the project.'
end
end
end
...@@ -195,6 +195,12 @@ module Types ...@@ -195,6 +195,12 @@ module Types
extras: [:lookahead], extras: [:lookahead],
resolver: Resolvers::ProjectPipelineResolver resolver: Resolvers::ProjectPipelineResolver
field :pipeline_counts,
Types::Ci::PipelineCountsType,
null: true,
description: 'Build pipeline counts of the project.',
resolver: Resolvers::Ci::ProjectPipelineCountsResolver
field :ci_cd_settings, field :ci_cd_settings,
Types::Ci::CiCdSettingType, Types::Ci::CiCdSettingType,
null: true, null: true,
......
# frozen_string_literal: true
module Ci
class ProjectPipelinesPolicy < BasePolicy
delegate { @subject.project }
end
end
...@@ -13207,6 +13207,19 @@ Represents the Geo sync and verification state of a pipeline artifact. ...@@ -13207,6 +13207,19 @@ Represents the Geo sync and verification state of a pipeline artifact.
| <a id="pipelineartifactregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the PipelineArtifactRegistry. | | <a id="pipelineartifactregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the PipelineArtifactRegistry. |
| <a id="pipelineartifactregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the PipelineArtifactRegistry. | | <a id="pipelineartifactregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the PipelineArtifactRegistry. |
### `PipelineCounts`
Represents pipeline counts for the project.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="pipelinecountsall"></a>`all` | [`Int`](#int) | Total number of pipelines for the project. |
| <a id="pipelinecountsfinished"></a>`finished` | [`Int`](#int) | Number of pipelines with scope FINISHED for the project. |
| <a id="pipelinecountspending"></a>`pending` | [`Int`](#int) | Number of pipelines with scope PENDING for the project. |
| <a id="pipelinecountsrunning"></a>`running` | [`Int`](#int) | Number of pipelines with scope RUNNING for the project. |
### `PipelineMessage` ### `PipelineMessage`
#### Fields #### Fields
...@@ -13977,6 +13990,20 @@ Returns [`Pipeline`](#pipeline). ...@@ -13977,6 +13990,20 @@ Returns [`Pipeline`](#pipeline).
| <a id="projectpipelineiid"></a>`iid` | [`ID`](#id) | IID of the Pipeline. For example, "1". | | <a id="projectpipelineiid"></a>`iid` | [`ID`](#id) | IID of the Pipeline. For example, "1". |
| <a id="projectpipelinesha"></a>`sha` | [`String`](#string) | SHA of the Pipeline. For example, "dyd0f15ay83993f5ab66k927w28673882x99100b". | | <a id="projectpipelinesha"></a>`sha` | [`String`](#string) | SHA of the Pipeline. For example, "dyd0f15ay83993f5ab66k927w28673882x99100b". |
##### `Project.pipelineCounts`
Build pipeline counts of the project.
Returns [`PipelineCounts`](#pipelinecounts).
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="projectpipelinecountsref"></a>`ref` | [`String`](#string) | Filter pipelines by the ref they are run for. |
| <a id="projectpipelinecountssha"></a>`sha` | [`String`](#string) | Filter pipelines by the SHA of the commit they are run for. |
| <a id="projectpipelinecountssource"></a>`source` | [`String`](#string) | Filter pipelines by their source. |
##### `Project.pipelines` ##### `Project.pipelines`
Build pipelines of the project. Build pipelines of the project.
# frozen_string_literal: true
module Gitlab
class PipelineScopeCounts
attr_reader :project
PIPELINES_COUNT_LIMIT = 1000
def self.declarative_policy_class
'Ci::ProjectPipelinesPolicy'
end
def initialize(current_user, project, params)
@current_user = current_user
@project = project
@params = params
end
def all
finder.execute.limit(PIPELINES_COUNT_LIMIT).count
end
def running
finder({ scope: "running" }).execute.limit(PIPELINES_COUNT_LIMIT).count
end
def finished
finder({ scope: "finished" }).execute.limit(PIPELINES_COUNT_LIMIT).count
end
def pending
finder({ scope: "pending" }).execute.limit(PIPELINES_COUNT_LIMIT).count
end
private
def finder(params = {})
::Ci::PipelinesFinder.new(@project, @current_user, @params.merge(params))
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Ci::ProjectPipelineCountsResolver do
include GraphqlHelpers
let(:current_user) { create(:user) }
let_it_be(:project) { create(:project, :private) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let_it_be(:failed_pipeline) { create(:ci_pipeline, :failed, project: project) }
let_it_be(:success_pipeline) { create(:ci_pipeline, :success, project: project) }
let_it_be(:ref_pipeline) { create(:ci_pipeline, project: project, ref: 'awesome-feature') }
let_it_be(:sha_pipeline) { create(:ci_pipeline, :running, project: project, sha: 'deadbeef') }
let_it_be(:on_demand_dast_scan) { create(:ci_pipeline, :success, project: project, source: 'ondemand_dast_scan') }
before do
project.add_developer(current_user)
end
describe '#resolve' do
it 'counts pipelines' do
expect(resolve_pipeline_counts).to have_attributes(
all: 6,
finished: 3,
running: 1,
pending: 2
)
end
it 'counts by ref' do
expect(resolve_pipeline_counts(ref: "awesome-feature")).to have_attributes(
all: 1,
finished: 0,
running: 0,
pending: 1
)
end
it 'counts by sha' do
expect(resolve_pipeline_counts(sha: "deadbeef")).to have_attributes(
all: 1,
finished: 0,
running: 1,
pending: 0
)
end
it 'counts by source' do
expect(resolve_pipeline_counts(source: "ondemand_dast_scan")).to have_attributes(
all: 1,
finished: 1,
running: 0,
pending: 0
)
end
end
def resolve_pipeline_counts(args = {}, context = { current_user: current_user })
resolve(described_class, obj: project, args: args, ctx: context)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['PipelineCounts'] do
include GraphqlHelpers
let(:current_user) { create(:user) }
let_it_be(:project) { create(:project, :private) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let_it_be(:failed_pipeline) { create(:ci_pipeline, :failed, project: project) }
let_it_be(:success_pipeline) { create(:ci_pipeline, :success, project: project) }
let_it_be(:ref_pipeline) { create(:ci_pipeline, project: project, ref: 'awesome-feature') }
let_it_be(:sha_pipeline) { create(:ci_pipeline, :running, project: project, sha: 'deadbeef') }
let_it_be(:on_demand_dast_scan) { create(:ci_pipeline, :success, project: project, source: 'ondemand_dast_scan') }
before do
project.add_developer(current_user)
end
specify { expect(described_class.graphql_name).to eq('PipelineCounts') }
it 'has the expected fields' do
expected_fields = %w[
all
finished
pending
running
]
expect(described_class).to include_graphql_fields(*expected_fields)
end
shared_examples 'pipeline counts query' do |args: "", expected_counts:|
let_it_be(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
pipelineCounts#{args} {
all
finished
pending
running
}
}
}
)
end
subject { GitlabSchema.execute(query, context: { current_user: current_user }).as_json }
it 'returns pipeline counts' do
actual_counts = subject.dig('data', 'project', 'pipelineCounts')
expect(actual_counts).to eq(expected_counts)
end
end
it_behaves_like "pipeline counts query", args: "", expected_counts: {
"all" => 6,
"finished" => 3,
"pending" => 2,
"running" => 1
}
it_behaves_like "pipeline counts query", args: '(ref: "awesome-feature")', expected_counts: {
"all" => 1,
"finished" => 0,
"pending" => 1,
"running" => 0
}
it_behaves_like "pipeline counts query", args: '(sha: "deadbeef")', expected_counts: {
"all" => 1,
"finished" => 0,
"pending" => 0,
"running" => 1
}
it_behaves_like "pipeline counts query", args: '(source: "ondemand_dast_scan")', expected_counts: {
"all" => 1,
"finished" => 1,
"pending" => 0,
"running" => 0
}
end
...@@ -25,7 +25,7 @@ RSpec.describe GitlabSchema.types['Project'] do ...@@ -25,7 +25,7 @@ RSpec.describe GitlabSchema.types['Project'] do
only_allow_merge_if_pipeline_succeeds request_access_enabled only_allow_merge_if_pipeline_succeeds request_access_enabled
only_allow_merge_if_all_discussions_are_resolved printing_merge_request_link_enabled only_allow_merge_if_all_discussions_are_resolved printing_merge_request_link_enabled
namespace group statistics repository merge_requests merge_request issues namespace group statistics repository merge_requests merge_request issues
issue milestones pipelines removeSourceBranchAfterMerge sentryDetailedError snippets issue milestones pipelines removeSourceBranchAfterMerge pipeline_counts sentryDetailedError snippets
grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments
environment boards jira_import_status jira_imports services releases release environment boards jira_import_status jira_imports services releases release
alert_management_alerts alert_management_alert alert_management_alert_status_counts alert_management_alerts alert_management_alert alert_management_alert_status_counts
...@@ -310,6 +310,13 @@ RSpec.describe GitlabSchema.types['Project'] do ...@@ -310,6 +310,13 @@ RSpec.describe GitlabSchema.types['Project'] do
end end
end end
describe 'pipelineCounts field' do
subject { described_class.fields['pipelineCounts'] }
it { is_expected.to have_graphql_type(Types::Ci::PipelineCountsType) }
it { is_expected.to have_graphql_resolver(Resolvers::Ci::ProjectPipelineCountsResolver) }
end
describe 'snippets field' do describe 'snippets field' do
subject { described_class.fields['snippets'] } subject { described_class.fields['snippets'] }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::PipelineScopeCounts do
let(:current_user) { create(:user) }
let_it_be(:project) { create(:project, :private) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let_it_be(:failed_pipeline) { create(:ci_pipeline, :failed, project: project) }
let_it_be(:success_pipeline) { create(:ci_pipeline, :success, project: project) }
let_it_be(:ref_pipeline) { create(:ci_pipeline, project: project, ref: 'awesome-feature') }
let_it_be(:sha_pipeline) { create(:ci_pipeline, :running, project: project, sha: 'deadbeef') }
let_it_be(:on_demand_dast_scan) { create(:ci_pipeline, :success, project: project, source: 'ondemand_dast_scan') }
before do
project.add_developer(current_user)
end
it 'has policy class' do
expect(described_class.declarative_policy_class).to be("Ci::ProjectPipelinesPolicy")
end
it 'has expected attributes' do
expect(described_class.new(current_user, project, {})).to have_attributes(
all: 6,
finished: 3,
pending: 2,
running: 1
)
end
describe 'with large amount of pipelines' do
it 'sets the PIPELINES_COUNT_LIMIT constant to a value of 1_000' do
expect(described_class::PIPELINES_COUNT_LIMIT).to eq(1_000)
end
context 'when there are more records than the limit' do
before do
stub_const('Gitlab::PipelineScopeCounts::PIPELINES_COUNT_LIMIT', 3)
end
it 'limits the found items' do
expect(described_class.new(current_user, project, {}).all).to eq(3)
end
end
end
end
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