Commit 8c83e8a5 authored by Fabio Pitino's avatar Fabio Pitino

Merge branch 'lm-upstream-downstream-pipeline' into 'master'

GraphQL: Adds upstream/downstream pipeline(s) and source job to Pipelines

See merge request gitlab-org/gitlab!45212
parents 5b685f31 ecdc6472
...@@ -15,7 +15,9 @@ module Resolvers ...@@ -15,7 +15,9 @@ module Resolvers
def preloads def preloads
{ {
jobs: [:statuses] jobs: [:statuses],
upstream: [:triggered_by_pipeline],
downstream: [:triggered_pipelines]
} }
end end
end end
......
...@@ -56,12 +56,24 @@ module Types ...@@ -56,12 +56,24 @@ module Types
description: 'Specifies if a pipeline can be canceled', description: 'Specifies if a pipeline can be canceled',
method: :cancelable?, method: :cancelable?,
null: false null: false
field :jobs, field :jobs,
::Types::Ci::JobType.connection_type, ::Types::Ci::JobType.connection_type,
null: true, null: true,
description: 'Jobs belonging to the pipeline', description: 'Jobs belonging to the pipeline',
method: :statuses method: :statuses
field :source_job, Types::Ci::JobType, null: true,
description: 'Job where pipeline was triggered from'
field :downstream, Types::Ci::PipelineType.connection_type, null: true,
description: 'Pipelines this pipeline will trigger',
method: :triggered_pipelines_with_preloads
field :upstream, Types::Ci::PipelineType, null: true,
description: 'Pipeline that triggered the pipeline',
method: :triggered_by_pipeline
field :path, GraphQL::STRING_TYPE, null: true,
description: "Relative path to the pipeline's page",
resolve: -> (obj, _args, _ctx) { ::Gitlab::Routing.url_helpers.project_pipeline_path(obj.project, obj) }
field :project, Types::ProjectType, null: true,
description: 'Project the pipeline belongs to'
end end
end end
end end
......
---
title: 'GraphQL: Adds downstream, upstream, source job, path, and project to PipelineType'
merge_request: 45212
author:
type: added
...@@ -13797,6 +13797,31 @@ type Pipeline { ...@@ -13797,6 +13797,31 @@ type Pipeline {
""" """
detailedStatus: DetailedStatus! detailedStatus: DetailedStatus!
"""
Pipelines this pipeline will trigger
"""
downstream(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
): PipelineConnection
""" """
Duration of the pipeline in seconds Duration of the pipeline in seconds
""" """
...@@ -13847,6 +13872,16 @@ type Pipeline { ...@@ -13847,6 +13872,16 @@ type Pipeline {
securityReportTypes: [SecurityReportTypeEnum!] securityReportTypes: [SecurityReportTypeEnum!]
): CiJobConnection ): CiJobConnection
"""
Relative path to the pipeline's page
"""
path: String
"""
Project the pipeline belongs to
"""
project: Project
""" """
Specifies if a pipeline can be retried Specifies if a pipeline can be retried
""" """
...@@ -13862,6 +13897,11 @@ type Pipeline { ...@@ -13862,6 +13897,11 @@ type Pipeline {
""" """
sha: String! sha: String!
"""
Job where pipeline was triggered from
"""
sourceJob: CiJob
""" """
Stages of the pipeline Stages of the pipeline
""" """
...@@ -13903,6 +13943,11 @@ type Pipeline { ...@@ -13903,6 +13943,11 @@ type Pipeline {
""" """
updatedAt: Time! updatedAt: Time!
"""
Pipeline that triggered the pipeline
"""
upstream: Pipeline
""" """
Pipeline user Pipeline user
""" """
......
...@@ -40697,6 +40697,59 @@ ...@@ -40697,6 +40697,59 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "downstream",
"description": "Pipelines this pipeline will trigger",
"args": [
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "PipelineConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "duration", "name": "duration",
"description": "Duration of the pipeline in seconds", "description": "Duration of the pipeline in seconds",
...@@ -40832,6 +40885,34 @@ ...@@ -40832,6 +40885,34 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "path",
"description": "Relative path to the pipeline's page",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "project",
"description": "Project the pipeline belongs to",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Project",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "retryable", "name": "retryable",
"description": "Specifies if a pipeline can be retried", "description": "Specifies if a pipeline can be retried",
...@@ -40882,6 +40963,20 @@ ...@@ -40882,6 +40963,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "sourceJob",
"description": "Job where pipeline was triggered from",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "CiJob",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "stages", "name": "stages",
"description": "Stages of the pipeline", "description": "Stages of the pipeline",
...@@ -40985,6 +41080,20 @@ ...@@ -40985,6 +41080,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "upstream",
"description": "Pipeline that triggered the pipeline",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Pipeline",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "user", "name": "user",
"description": "Pipeline user", "description": "Pipeline user",
...@@ -2009,12 +2009,16 @@ Information about pagination in a connection.. ...@@ -2009,12 +2009,16 @@ Information about pagination in a connection..
| `finishedAt` | Time | Timestamp of the pipeline's completion | | `finishedAt` | Time | Timestamp of the pipeline's completion |
| `id` | ID! | ID of the pipeline | | `id` | ID! | ID of the pipeline |
| `iid` | String! | Internal ID of the pipeline | | `iid` | String! | Internal ID of the pipeline |
| `path` | String | Relative path to the pipeline's page |
| `project` | Project | Project the pipeline belongs to |
| `retryable` | Boolean! | Specifies if a pipeline can be retried | | `retryable` | Boolean! | Specifies if a pipeline can be retried |
| `securityReportSummary` | SecurityReportSummary | Vulnerability and scanned resource counts for each security scanner of the pipeline | | `securityReportSummary` | SecurityReportSummary | Vulnerability and scanned resource counts for each security scanner of the pipeline |
| `sha` | String! | SHA of the pipeline's commit | | `sha` | String! | SHA of the pipeline's commit |
| `sourceJob` | CiJob | Job where pipeline was triggered from |
| `startedAt` | Time | Timestamp when the pipeline was started | | `startedAt` | Time | Timestamp when the pipeline was started |
| `status` | PipelineStatusEnum! | Status of the pipeline (CREATED, WAITING_FOR_RESOURCE, PREPARING, PENDING, RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED) | | `status` | PipelineStatusEnum! | Status of the pipeline (CREATED, WAITING_FOR_RESOURCE, PREPARING, PENDING, RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED) |
| `updatedAt` | Time! | Timestamp of the pipeline's last activity | | `updatedAt` | Time! | Timestamp of the pipeline's last activity |
| `upstream` | Pipeline | Pipeline that triggered the pipeline |
| `user` | User | Pipeline user | | `user` | User | Pipeline user |
| `userPermissions` | PipelinePermissions! | Permissions for the current user on the resource | | `userPermissions` | PipelinePermissions! | Permissions for the current user on the resource |
......
...@@ -55,4 +55,130 @@ RSpec.describe 'Query.project(fullPath).pipelines' do ...@@ -55,4 +55,130 @@ RSpec.describe 'Query.project(fullPath).pipelines' do
expect(job_names).to contain_exactly('Job 1', 'Job 2') expect(job_names).to contain_exactly('Job 1', 'Job 2')
end end
end end
describe 'upstream' do
let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: first_user) }
let_it_be(:upstream_project) { create(:project, :repository, :public) }
let_it_be(:upstream_pipeline) { create(:ci_pipeline, project: upstream_project, user: first_user) }
let(:upstream_pipelines_graphql_data) { graphql_data.dig(*%w[project pipelines nodes]).first['upstream'] }
let(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
pipelines {
nodes {
upstream {
iid
}
}
}
}
}
)
end
before do
create(:ci_sources_pipeline, source_pipeline: upstream_pipeline, pipeline: pipeline )
post_graphql(query, current_user: first_user)
end
it_behaves_like 'a working graphql query'
it 'returns the upstream pipeline of a pipeline' do
expect(upstream_pipelines_graphql_data['iid'].to_i).to eq(upstream_pipeline.iid)
end
context 'when fetching the upstream pipeline from the pipeline' do
it 'avoids N+1 queries' do
control_count = ActiveRecord::QueryRecorder.new do
post_graphql(query, current_user: first_user)
end
pipeline_2 = create(:ci_pipeline, project: project, user: first_user)
upstream_pipeline_2 = create(:ci_pipeline, project: upstream_project, user: first_user)
create(:ci_sources_pipeline, source_pipeline: upstream_pipeline_2, pipeline: pipeline_2 )
pipeline_3 = create(:ci_pipeline, project: project, user: first_user)
upstream_pipeline_3 = create(:ci_pipeline, project: upstream_project, user: first_user)
create(:ci_sources_pipeline, source_pipeline: upstream_pipeline_3, pipeline: pipeline_3 )
expect do
post_graphql(query, current_user: second_user)
end.not_to exceed_query_limit(control_count)
expect(response).to have_gitlab_http_status(:ok)
end
end
end
describe 'downstream' do
let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: first_user) }
let(:pipeline_2) { create(:ci_pipeline, project: project, user: first_user) }
let_it_be(:downstream_project) { create(:project, :repository, :public) }
let_it_be(:downstream_pipeline_a) { create(:ci_pipeline, project: downstream_project, user: first_user) }
let_it_be(:downstream_pipeline_b) { create(:ci_pipeline, project: downstream_project, user: first_user) }
let(:pipelines_graphql_data) { graphql_data.dig(*%w[project pipelines nodes]) }
let(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
pipelines {
nodes {
downstream {
nodes {
iid
}
}
}
}
}
}
)
end
before do
create(:ci_sources_pipeline, source_pipeline: pipeline, pipeline: downstream_pipeline_a)
create(:ci_sources_pipeline, source_pipeline: pipeline_2, pipeline: downstream_pipeline_b)
post_graphql(query, current_user: first_user)
end
it_behaves_like 'a working graphql query'
it 'returns the downstream pipelines of a pipeline' do
downstream_pipelines_graphql_data = pipelines_graphql_data.map { |pip| pip['downstream']['nodes'] }.flatten
expect(
downstream_pipelines_graphql_data.map { |pip| pip['iid'].to_i }
).to contain_exactly(downstream_pipeline_a.iid, downstream_pipeline_b.iid)
end
context 'when fetching the downstream pipelines from the pipeline' do
it 'avoids N+1 queries' do
control_count = ActiveRecord::QueryRecorder.new do
post_graphql(query, current_user: first_user)
end
downstream_pipeline_2a = create(:ci_pipeline, project: downstream_project, user: first_user)
create(:ci_sources_pipeline, source_pipeline: pipeline, pipeline: downstream_pipeline_2a)
downsteam_pipeline_3a = create(:ci_pipeline, project: downstream_project, user: first_user)
create(:ci_sources_pipeline, source_pipeline: pipeline, pipeline: downsteam_pipeline_3a)
downstream_pipeline_2b = create(:ci_pipeline, project: downstream_project, user: first_user)
create(:ci_sources_pipeline, source_pipeline: pipeline_2, pipeline: downstream_pipeline_2b)
downsteam_pipeline_3b = create(:ci_pipeline, project: downstream_project, user: first_user)
create(:ci_sources_pipeline, source_pipeline: pipeline_2, pipeline: downsteam_pipeline_3b)
expect do
post_graphql(query, current_user: second_user)
end.not_to exceed_query_limit(control_count)
expect(response).to have_gitlab_http_status(:ok)
end
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