Commit 6d12d7ba authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'ph/commitGraphqlPipelines' into 'master'

GraphQL commit type correctly return pipeline for ref

See merge request gitlab-org/gitlab!19758
parents 467fa2ce f4280ae8
......@@ -38,7 +38,14 @@ export default {
path: this.currentPath.replace(/^\//, ''),
};
},
update: data => data.project.repository.tree.lastCommit,
update: data => {
const pipelines = data.project.repository.tree.lastCommit.pipelines.edges;
return {
...data.project.repository.tree.lastCommit,
pipeline: pipelines.length && pipelines[0].node,
};
},
context: {
isSingleRequest: true,
},
......@@ -61,7 +68,7 @@ export default {
computed: {
statusTitle() {
return sprintf(s__('Commits|Commit: %{commitText}'), {
commitText: this.commit.latestPipeline.detailedStatus.text,
commitText: this.commit.pipeline.detailedStatus.text,
});
},
isLoading() {
......@@ -127,14 +134,14 @@ export default {
<div v-if="commit.signatureHtml" v-html="commit.signatureHtml"></div>
<div class="ci-status-link">
<gl-link
v-if="commit.latestPipeline"
v-if="commit.pipeline"
v-gl-tooltip.left
:href="commit.latestPipeline.detailedStatus.detailsPath"
:href="commit.pipeline.detailedStatus.detailsPath"
:title="statusTitle"
class="js-commit-pipeline"
>
<ci-icon
:status="commit.latestPipeline.detailedStatus"
:status="commit.pipeline.detailedStatus"
:size="24"
:aria-label="statusTitle"
/>
......
......@@ -14,7 +14,9 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
webUrl
}
signatureHtml
latestPipeline {
pipelines(ref: $ref, first: 1) {
edges {
node {
detailedStatus {
detailsPath
icon
......@@ -27,4 +29,6 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
}
}
}
}
}
}
......@@ -10,6 +10,14 @@ module Resolvers
end
end
def self.last
@last ||= Class.new(self) do
def resolve(**args)
super.last
end
end
end
def self.resolver_complexity(args, child_complexity:)
complexity = 1
complexity += 1 if args[:sort]
......
# frozen_string_literal: true
module Resolvers
class CommitPipelinesResolver < BaseResolver
include ::ResolvesPipelines
alias_method :commit, :object
def resolve(**args)
resolve_pipelines(commit.project, args.merge!({ sha: commit.sha }))
end
end
end
......@@ -29,12 +29,16 @@ module Types
field :author, type: Types::UserType, null: true,
description: 'Author of the commit'
field :pipelines, Types::Ci::PipelineType.connection_type,
null: true,
description: 'Pipelines of the commit ordered latest first',
resolver: Resolvers::CommitPipelinesResolver
field :latest_pipeline,
type: Types::Ci::PipelineType,
null: true,
description: "Latest pipeline of the commit",
resolve: -> (obj, ctx, args) do
Gitlab::Graphql::Loaders::PipelineForShaLoader.new(obj.project, obj.sha).find_last
end
deprecation_reason: 'use pipelines',
resolver: Resolvers::CommitPipelinesResolver.last
end
end
......@@ -139,13 +139,68 @@ type Commit {
"""
Latest pipeline of the commit
"""
latestPipeline: Pipeline
latestPipeline(
"""
Filter pipelines by the ref they are run for
"""
ref: String
"""
Filter pipelines by the sha of the commit they are run for
"""
sha: String
"""
Filter pipelines by their status
"""
status: PipelineStatusEnum
): Pipeline @deprecated(reason: "use pipelines")
"""
Raw commit message
"""
message: String
"""
Pipelines of the commit ordered latest first
"""
pipelines(
"""
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
"""
Filter pipelines by the ref they are run for
"""
ref: String
"""
Filter pipelines by the sha of the commit they are run for
"""
sha: String
"""
Filter pipelines by their status
"""
status: PipelineStatusEnum
): PipelineConnection
"""
SHA1 ID of the commit
"""
......
......@@ -10210,15 +10210,44 @@
"name": "latestPipeline",
"description": "Latest pipeline of the commit",
"args": [
{
"name": "status",
"description": "Filter pipelines by their status",
"type": {
"kind": "ENUM",
"name": "PipelineStatusEnum",
"ofType": null
},
"defaultValue": null
},
{
"name": "ref",
"description": "Filter pipelines by the ref they are run for",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "sha",
"description": "Filter pipelines by the sha of the commit they are run for",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "Pipeline",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
"isDeprecated": true,
"deprecationReason": "use pipelines"
},
{
"name": "message",
......@@ -10234,6 +10263,89 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pipelines",
"description": "Pipelines of the commit ordered latest first",
"args": [
{
"name": "status",
"description": "Filter pipelines by their status",
"type": {
"kind": "ENUM",
"name": "PipelineStatusEnum",
"ofType": null
},
"defaultValue": null
},
{
"name": "ref",
"description": "Filter pipelines by the ref they are run for",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "sha",
"description": "Filter pipelines by the sha of the commit they are run for",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"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": "sha",
"description": "SHA1 ID of the commit",
......@@ -10306,6 +10418,118 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "PipelineConnection",
"description": "The connection type for Pipeline.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PipelineEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Pipeline",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pageInfo",
"description": "Information to aid in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PageInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "PipelineEdge",
"description": "An edge in a connection.",
"fields": [
{
"name": "cursor",
"description": "A cursor for use in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "node",
"description": "The item at the end of the edge.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Pipeline",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Pipeline",
......@@ -13205,118 +13429,6 @@
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "PipelineConnection",
"description": "The connection type for Pipeline.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PipelineEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Pipeline",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pageInfo",
"description": "Information to aid in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PageInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "PipelineEdge",
"description": "An edge in a connection.",
"fields": [
{
"name": "cursor",
"description": "A cursor for use in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "node",
"description": "The item at the end of the edge.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Pipeline",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "IssueConnection",
......
# frozen_string_literal: true
module Gitlab
module Graphql
module Loaders
class PipelineForShaLoader
attr_accessor :project, :sha
def initialize(project, sha)
@project, @sha = project, sha
end
def find_last
BatchLoader::GraphQL.for(sha).batch(key: project) do |shas, loader, args|
pipelines = args[:key].ci_pipelines.latest_for_shas(shas)
pipelines.each do |pipeline|
loader.call(pipeline.sha, pipeline)
end
end
end
end
end
end
end
......@@ -9,8 +9,6 @@ describe 'user reads pipeline status', :js do
let(:x110_pipeline) { create_pipeline('x1.1.0', 'failed') }
before do
stub_feature_flags(vue_file_list: false)
project.add_maintainer(user)
project.repository.add_tag(user, 'x1.1.0', 'v1.1.0')
......@@ -25,7 +23,7 @@ describe 'user reads pipeline status', :js do
visit project_tree_path(project, expected_pipeline.ref)
wait_for_requests
page.within('.blob-commit-info') do
page.within('.commit-detail') do
expect(page).to have_link('', href: project_pipeline_path(project, expected_pipeline))
expect(page).to have_selector(".ci-status-icon-#{expected_pipeline.status}")
end
......
......@@ -17,7 +17,7 @@ function createCommitData(data = {}) {
avatarUrl: 'https://test.com',
webUrl: 'https://test.com/test',
},
latestPipeline: {
pipeline: {
detailedStatus: {
detailsPath: 'https://test.com/pipeline',
icon: 'failed',
......@@ -74,7 +74,7 @@ describe('Repository last commit component', () => {
});
it('hides pipeline components when pipeline does not exist', () => {
factory(createCommitData({ latestPipeline: null }));
factory(createCommitData({ pipeline: null }));
expect(vm.find('.js-commit-pipeline').exists()).toBe(false);
});
......
......@@ -13,6 +13,14 @@ describe Resolvers::BaseResolver do
end
end
let(:last_resolver) do
Class.new(described_class) do
def resolve(**args)
[1, 2]
end
end
end
describe '.single' do
it 'returns a subclass from the resolver' do
expect(resolver.single.superclass).to eq(resolver)
......@@ -29,6 +37,22 @@ describe Resolvers::BaseResolver do
end
end
describe '.last' do
it 'returns a subclass from the resolver' do
expect(last_resolver.last.superclass).to eq(last_resolver)
end
it 'returns the same subclass every time' do
expect(last_resolver.last.object_id).to eq(last_resolver.last.object_id)
end
it 'returns a resolver that gives the last result from the original resolver' do
result = resolve(last_resolver.last)
expect(result).to eq(2)
end
end
context 'when field is a connection' do
it 'increases complexity based on arguments' do
field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 1)
......
# frozen_string_literal: true
require 'spec_helper'
describe Resolvers::CommitPipelinesResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
let(:commit) { create(:commit, project: project) }
let_it_be(:current_user) { create(:user) }
let!(:pipeline) do
create(
:ci_pipeline,
project: project,
sha: commit.id,
ref: 'master',
status: 'success'
)
end
let!(:pipeline2) do
create(
:ci_pipeline,
project: project,
sha: commit.id,
ref: 'master',
status: 'failed'
)
end
let!(:pipeline3) do
create(
:ci_pipeline,
project: project,
sha: commit.id,
ref: 'my_branch',
status: 'failed'
)
end
before do
commit.project.add_developer(current_user)
end
def resolve_pipelines
resolve(described_class, obj: commit, ctx: { current_user: current_user }, args: { ref: 'master' })
end
it 'resolves pipelines for commit and ref' do
pipelines = resolve_pipelines
expect(pipelines).to eq([pipeline2, pipeline])
end
end
......@@ -10,7 +10,7 @@ describe GitlabSchema.types['Commit'] do
it 'contains attributes related to commit' do
expect(described_class).to have_graphql_fields(
:id, :sha, :title, :description, :message, :authored_date,
:author, :web_url, :latest_pipeline, :signature_html
:author, :web_url, :latest_pipeline, :pipelines, :signature_html
)
end
end
require 'spec_helper'
describe Gitlab::Graphql::Loaders::PipelineForShaLoader do
include GraphqlHelpers
describe '#find_last' do
it 'batch-resolves latest pipeline' do
project = create(:project, :repository)
pipeline1 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha)
pipeline2 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha)
pipeline3 = create(:ci_pipeline, project: project, ref: 'improve/awesome', sha: project.commit('improve/awesome').sha)
result = batch_sync(max_queries: 1) do
[pipeline1.sha, pipeline3.sha].map { |sha| described_class.new(project, sha).find_last }
end
expect(result).to contain_exactly(pipeline2, pipeline3)
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