Commit 363669db authored by charlie ablett's avatar charlie ablett

Merge branch 'lm-add-short-sha-and-path-to-pipeline' into 'master'

Makes pipeline be searchable by sha or iid

See merge request gitlab-org/gitlab!54471
parents e990feb0 5e1270ba
...@@ -7,14 +7,34 @@ module Resolvers ...@@ -7,14 +7,34 @@ module Resolvers
alias_method :project, :object alias_method :project, :object
argument :iid, GraphQL::ID_TYPE, argument :iid, GraphQL::ID_TYPE,
required: true, required: false,
description: 'IID of the Pipeline, e.g., "1".' description: 'IID of the Pipeline. For example, "1".'
def resolve(iid:) argument :sha, GraphQL::STRING_TYPE,
BatchLoader::GraphQL.for(iid).batch(key: project) do |iids, loader, args| required: false,
finder = ::Ci::PipelinesFinder.new(project, context[:current_user], iids: iids) description: 'SHA of the Pipeline. For example, "dyd0f15ay83993f5ab66k927w28673882x99100b".'
finder.execute.each { |pipeline| loader.call(pipeline.iid.to_s, pipeline) } def ready?(iid: nil, sha: nil)
unless iid.present? ^ sha.present?
raise Gitlab::Graphql::Errors::ArgumentError, 'Provide one of an IID or SHA'
end
super
end
def resolve(iid: nil, sha: nil)
if iid
BatchLoader::GraphQL.for(iid).batch(key: project) do |iids, loader, args|
finder = ::Ci::PipelinesFinder.new(project, current_user, iids: iids)
finder.execute.each { |pipeline| loader.call(pipeline.iid.to_s, pipeline) }
end
else
BatchLoader::GraphQL.for(sha).batch(key: project) do |shas, loader, args|
finder = ::Ci::PipelinesFinder.new(project, current_user, shas: shas)
finder.execute.each { |pipeline| loader.call(pipeline.sha.to_s, pipeline) }
end
end end
end end
end end
......
...@@ -95,6 +95,9 @@ module Types ...@@ -95,6 +95,9 @@ module Types
field :path, GraphQL::STRING_TYPE, null: true, field :path, GraphQL::STRING_TYPE, null: true,
description: "Relative path to the pipeline's page." description: "Relative path to the pipeline's page."
field :commit_path, GraphQL::STRING_TYPE, null: true,
description: 'Path to the commit that triggered the pipeline.'
field :project, Types::ProjectType, null: true, field :project, Types::ProjectType, null: true,
description: 'Project the pipeline belongs to.' description: 'Project the pipeline belongs to.'
...@@ -109,6 +112,10 @@ module Types ...@@ -109,6 +112,10 @@ module Types
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.user_id).find Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.user_id).find
end end
def commit_path
::Gitlab::Routing.url_helpers.project_commit_path(object.project, object.sha)
end
def path def path
::Gitlab::Routing.url_helpers.project_pipeline_path(object.project, object) ::Gitlab::Routing.url_helpers.project_pipeline_path(object.project, object)
end end
......
---
title: Allow search for pipeline by SHA as well as IID via GraphQL
merge_request: 54471
author:
type: changed
...@@ -3196,6 +3196,7 @@ Information about pagination in a connection. ...@@ -3196,6 +3196,7 @@ Information about pagination in a connection.
| `active` | Boolean! | Indicates if the pipeline is active. | | `active` | Boolean! | Indicates if the pipeline is active. |
| `beforeSha` | String | Base SHA of the source branch. | | `beforeSha` | String | Base SHA of the source branch. |
| `cancelable` | Boolean! | Specifies if a pipeline can be canceled. | | `cancelable` | Boolean! | Specifies if a pipeline can be canceled. |
| `commitPath` | String | Path to the commit that triggered the pipeline. |
| `committedAt` | Time | Timestamp of the pipeline's commit. | | `committedAt` | Time | Timestamp of the pipeline's commit. |
| `configSource` | PipelineConfigSourceEnum | Configuration source of the pipeline (UNKNOWN_SOURCE, REPOSITORY_SOURCE, AUTO_DEVOPS_SOURCE, WEBIDE_SOURCE, REMOTE_SOURCE, EXTERNAL_PROJECT_SOURCE, BRIDGE_SOURCE, PARAMETER_SOURCE, COMPLIANCE_SOURCE) | | `configSource` | PipelineConfigSourceEnum | Configuration source of the pipeline (UNKNOWN_SOURCE, REPOSITORY_SOURCE, AUTO_DEVOPS_SOURCE, WEBIDE_SOURCE, REMOTE_SOURCE, EXTERNAL_PROJECT_SOURCE, BRIDGE_SOURCE, PARAMETER_SOURCE, COMPLIANCE_SOURCE) |
| `coverage` | Float | Coverage percentage. | | `coverage` | Float | Coverage percentage. |
......
...@@ -14,7 +14,7 @@ RSpec.describe 'getting a requirement list for a project' do ...@@ -14,7 +14,7 @@ RSpec.describe 'getting a requirement list for a project' do
<<~QUERY <<~QUERY
edges { edges {
node { node {
#{all_graphql_fields_for('requirements'.classify)} #{all_graphql_fields_for('requirements'.classify, max_depth: 1)}
} }
} }
QUERY QUERY
......
...@@ -6,7 +6,7 @@ RSpec.describe Resolvers::ProjectPipelineResolver do ...@@ -6,7 +6,7 @@ RSpec.describe Resolvers::ProjectPipelineResolver do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project, iid: '1234') } let_it_be(:pipeline) { create(:ci_pipeline, project: project, iid: '1234', sha: 'sha') }
let_it_be(:other_pipeline) { create(:ci_pipeline) } let_it_be(:other_pipeline) { create(:ci_pipeline) }
let(:current_user) { create(:user) } let(:current_user) { create(:user) }
...@@ -30,7 +30,15 @@ RSpec.describe Resolvers::ProjectPipelineResolver do ...@@ -30,7 +30,15 @@ RSpec.describe Resolvers::ProjectPipelineResolver do
expect(result).to eq(pipeline) expect(result).to eq(pipeline)
end end
it 'keeps the queries under the threshold' do it 'resolves pipeline for the passed sha' do
result = batch_sync do
resolve_pipeline(project, { sha: 'sha' })
end
expect(result).to eq(pipeline)
end
it 'keeps the queries under the threshold for iid' do
create(:ci_pipeline, project: project, iid: '1235') create(:ci_pipeline, project: project, iid: '1235')
control = ActiveRecord::QueryRecorder.new do control = ActiveRecord::QueryRecorder.new do
...@@ -45,6 +53,21 @@ RSpec.describe Resolvers::ProjectPipelineResolver do ...@@ -45,6 +53,21 @@ RSpec.describe Resolvers::ProjectPipelineResolver do
end.not_to exceed_query_limit(control) end.not_to exceed_query_limit(control)
end end
it 'keeps the queries under the threshold for sha' do
create(:ci_pipeline, project: project, sha: 'sha2')
control = ActiveRecord::QueryRecorder.new do
batch_sync { resolve_pipeline(project, { sha: 'sha' }) }
end
expect do
batch_sync do
resolve_pipeline(project, { sha: 'sha' })
resolve_pipeline(project, { sha: 'sha2' })
end
end.not_to exceed_query_limit(control)
end
it 'does not resolve a pipeline outside the project' do it 'does not resolve a pipeline outside the project' do
result = batch_sync do result = batch_sync do
resolve_pipeline(other_pipeline.project, { iid: '1234' }) resolve_pipeline(other_pipeline.project, { iid: '1234' })
...@@ -53,8 +76,14 @@ RSpec.describe Resolvers::ProjectPipelineResolver do ...@@ -53,8 +76,14 @@ RSpec.describe Resolvers::ProjectPipelineResolver do
expect(result).to be_nil expect(result).to be_nil
end end
it 'errors when no iid is passed' do it 'errors when no iid or sha is passed' do
expect { resolve_pipeline(project, {}) }.to raise_error(ArgumentError) expect { resolve_pipeline(project, {}) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
end
it 'errors when both iid and sha are passed' do
expect { resolve_pipeline(project, { iid: '1234', sha: 'sha' }) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
end end
context 'when the pipeline is a dangling pipeline' do context 'when the pipeline is a dangling pipeline' do
......
...@@ -12,7 +12,7 @@ RSpec.describe Types::Ci::PipelineType do ...@@ -12,7 +12,7 @@ RSpec.describe Types::Ci::PipelineType do
id iid sha before_sha status detailed_status config_source duration id iid sha before_sha status detailed_status config_source duration
coverage created_at updated_at started_at finished_at committed_at coverage created_at updated_at started_at finished_at committed_at
stages user retryable cancelable jobs source_job downstream stages user retryable cancelable jobs source_job downstream
upstream path project active user_permissions warnings upstream path project active user_permissions warnings commit_path
] ]
if Gitlab.ee? if Gitlab.ee?
......
...@@ -13,7 +13,7 @@ RSpec.describe 'container repository details' do ...@@ -13,7 +13,7 @@ RSpec.describe 'container repository details' do
graphql_query_for( graphql_query_for(
'containerRepository', 'containerRepository',
{ id: container_repository_global_id }, { id: container_repository_global_id },
all_graphql_fields_for('ContainerRepositoryDetails') all_graphql_fields_for('ContainerRepositoryDetails', excluded: ['pipeline'])
) )
end end
......
...@@ -18,7 +18,7 @@ RSpec.describe 'getting container repositories in a group' do ...@@ -18,7 +18,7 @@ RSpec.describe 'getting container repositories in a group' do
<<~GQL <<~GQL
edges { edges {
node { node {
#{all_graphql_fields_for('container_repositories'.classify)} #{all_graphql_fields_for('container_repositories'.classify, max_depth: 1)}
} }
} }
GQL GQL
......
...@@ -23,7 +23,7 @@ RSpec.describe 'getting projects' do ...@@ -23,7 +23,7 @@ RSpec.describe 'getting projects' do
projects(includeSubgroups: #{include_subgroups}) { projects(includeSubgroups: #{include_subgroups}) {
edges { edges {
node { node {
#{all_graphql_fields_for('Project')} #{all_graphql_fields_for('Project', max_depth: 1)}
} }
} }
} }
......
...@@ -15,7 +15,7 @@ RSpec.describe 'package details' do ...@@ -15,7 +15,7 @@ RSpec.describe 'package details' do
end end
let(:depth) { 3 } let(:depth) { 3 }
let(:excluded) { %w[metadata apiFuzzingCiConfiguration] } let(:excluded) { %w[metadata apiFuzzingCiConfiguration pipeline] }
let(:query) do let(:query) do
graphql_query_for(:package, { id: package_global_id }, <<~FIELDS) graphql_query_for(:package, { id: package_global_id }, <<~FIELDS)
......
...@@ -16,7 +16,7 @@ RSpec.describe 'getting container repositories in a project' do ...@@ -16,7 +16,7 @@ RSpec.describe 'getting container repositories in a project' do
<<~GQL <<~GQL
edges { edges {
node { node {
#{all_graphql_fields_for('container_repositories'.classify)} #{all_graphql_fields_for('container_repositories'.classify, excluded: ['pipeline'])}
} }
} }
GQL GQL
......
...@@ -34,7 +34,7 @@ RSpec.describe 'getting notes for a merge request' do ...@@ -34,7 +34,7 @@ RSpec.describe 'getting notes for a merge request' do
notes { notes {
edges { edges {
node { node {
#{all_graphql_fields_for('Note')} #{all_graphql_fields_for('Note', excluded: ['pipeline'])}
} }
} }
} }
......
...@@ -9,7 +9,7 @@ RSpec.describe 'getting merge request information nested in a project' do ...@@ -9,7 +9,7 @@ RSpec.describe 'getting merge request information nested in a project' do
let(:current_user) { create(:user) } let(:current_user) { create(:user) }
let(:merge_request_graphql_data) { graphql_data['project']['mergeRequest'] } let(:merge_request_graphql_data) { graphql_data['project']['mergeRequest'] }
let!(:merge_request) { create(:merge_request, source_project: project) } let!(:merge_request) { create(:merge_request, source_project: project) }
let(:mr_fields) { all_graphql_fields_for('MergeRequest') } let(:mr_fields) { all_graphql_fields_for('MergeRequest', excluded: ['pipeline']) }
let(:query) do let(:query) do
graphql_query_for( graphql_query_for(
......
...@@ -11,10 +11,14 @@ RSpec.describe 'getting pipeline information nested in a project' do ...@@ -11,10 +11,14 @@ RSpec.describe 'getting pipeline information nested in a project' do
let(:pipeline_graphql_data) { graphql_data['project']['pipeline'] } let(:pipeline_graphql_data) { graphql_data['project']['pipeline'] }
let!(:query) do let!(:query) do
graphql_query_for( %(
'project', query {
{ 'fullPath' => project.full_path }, project(fullPath: "#{project.full_path}") {
query_graphql_field('pipeline', iid: pipeline.iid.to_s) pipeline(iid: "#{pipeline.iid}") {
configSource
}
}
}
) )
end end
......
...@@ -17,7 +17,7 @@ RSpec.shared_context 'exposing regular notes on a noteable in GraphQL' do ...@@ -17,7 +17,7 @@ RSpec.shared_context 'exposing regular notes on a noteable in GraphQL' do
notes { notes {
edges { edges {
node { node {
#{all_graphql_fields_for('Note')} #{all_graphql_fields_for('Note', max_depth: 1)}
} }
} }
} }
......
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