Commit 4451c5a0 authored by Laura Montemayor's avatar Laura Montemayor Committed by Vitali Tatarintev

Adds graphql types

* Stages, jobs, and groups
parent f6e53638
# frozen_string_literal: true
module Resolvers
module Ci
class PipelineStagesResolver < BaseResolver
include LooksAhead
alias_method :pipeline, :object
def resolve_with_lookahead
apply_lookahead(pipeline.stages)
end
def preloads
{
statuses: [:needs]
}
end
end
end
end
# frozen_string_literal: true
module Types
module Ci
# rubocop: disable Graphql/AuthorizeTypes
class GroupType < BaseObject
graphql_name 'CiGroup'
field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the job group'
field :size, GraphQL::INT_TYPE, null: true,
description: 'Size of the group'
field :jobs, Ci::JobType.connection_type, null: true,
description: 'Jobs in group'
end
end
end
# frozen_string_literal: true
module Types
module Ci
# rubocop: disable Graphql/AuthorizeTypes
class JobType < BaseObject
graphql_name 'CiJob'
field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the job'
field :needs, JobType.connection_type, null: true,
description: 'Builds that must complete before the jobs run'
end
end
end
...@@ -37,6 +37,10 @@ module Types ...@@ -37,6 +37,10 @@ module Types
description: "Timestamp of the pipeline's completion" description: "Timestamp of the pipeline's completion"
field :committed_at, Types::TimeType, null: true, field :committed_at, Types::TimeType, null: true,
description: "Timestamp of the pipeline's commit" description: "Timestamp of the pipeline's commit"
field :stages, Types::Ci::StageType.connection_type, null: true,
description: 'Stages of the pipeline',
extras: [:lookahead],
resolver: Resolvers::Ci::PipelineStagesResolver
# TODO: Add triggering user as a type # TODO: Add triggering user as a type
end end
......
# frozen_string_literal: true
module Types
module Ci
# rubocop: disable Graphql/AuthorizeTypes
class StageType < BaseObject
graphql_name 'CiStage'
field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the stage'
field :groups, Ci::GroupType.connection_type, null: true,
description: 'Group of jobs for the stage'
end
end
end
...@@ -1347,6 +1347,212 @@ type Branch { ...@@ -1347,6 +1347,212 @@ type Branch {
name: String! name: String!
} }
type CiGroup {
"""
Jobs in group
"""
jobs(
"""
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
): CiJobConnection
"""
Name of the job group
"""
name: String
"""
Size of the group
"""
size: Int
}
"""
The connection type for CiGroup.
"""
type CiGroupConnection {
"""
A list of edges.
"""
edges: [CiGroupEdge]
"""
A list of nodes.
"""
nodes: [CiGroup]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type CiGroupEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: CiGroup
}
type CiJob {
"""
Name of the job
"""
name: String
"""
Builds that must complete before the jobs run
"""
needs(
"""
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
): CiJobConnection
}
"""
The connection type for CiJob.
"""
type CiJobConnection {
"""
A list of edges.
"""
edges: [CiJobEdge]
"""
A list of nodes.
"""
nodes: [CiJob]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type CiJobEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: CiJob
}
type CiStage {
"""
Group of jobs for the stage
"""
groups(
"""
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
): CiGroupConnection
"""
Name of the stage
"""
name: String
}
"""
The connection type for CiStage.
"""
type CiStageConnection {
"""
A list of edges.
"""
edges: [CiStageEdge]
"""
A list of nodes.
"""
nodes: [CiStage]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type CiStageEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: CiStage
}
type Commit { type Commit {
""" """
Author of the commit Author of the commit
...@@ -9850,6 +10056,31 @@ type Pipeline { ...@@ -9850,6 +10056,31 @@ type Pipeline {
""" """
sha: String! sha: String!
"""
Stages of the pipeline
"""
stages(
"""
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
): CiStageConnection
""" """
Timestamp when the pipeline was started Timestamp when the pipeline was started
""" """
......
...@@ -3639,6 +3639,596 @@ ...@@ -3639,6 +3639,596 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "CiGroup",
"description": null,
"fields": [
{
"name": "jobs",
"description": "Jobs in group",
"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": "CiJobConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "name",
"description": "Name of the job group",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "size",
"description": "Size of the group",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "CiGroupConnection",
"description": "The connection type for CiGroup.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CiGroupEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CiGroup",
"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": "CiGroupEdge",
"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": "CiGroup",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "CiJob",
"description": null,
"fields": [
{
"name": "name",
"description": "Name of the job",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "needs",
"description": "Builds that must complete before the jobs run",
"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": "CiJobConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "CiJobConnection",
"description": "The connection type for CiJob.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CiJobEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CiJob",
"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": "CiJobEdge",
"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": "CiJob",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "CiStage",
"description": null,
"fields": [
{
"name": "groups",
"description": "Group of jobs for the stage",
"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": "CiGroupConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "name",
"description": "Name of the stage",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "CiStageConnection",
"description": "The connection type for CiStage.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CiStageEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CiStage",
"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": "CiStageEdge",
"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": "CiStage",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "Commit", "name": "Commit",
...@@ -29496,6 +30086,59 @@ ...@@ -29496,6 +30086,59 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "stages",
"description": "Stages of the pipeline",
"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": "CiStageConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "startedAt", "name": "startedAt",
"description": "Timestamp when the pipeline was started", "description": "Timestamp when the pipeline was started",
...@@ -231,6 +231,25 @@ Autogenerated return type of BoardListUpdateLimitMetrics ...@@ -231,6 +231,25 @@ Autogenerated return type of BoardListUpdateLimitMetrics
| `commit` | Commit | Commit for the branch | | `commit` | Commit | Commit for the branch |
| `name` | String! | Name of the branch | | `name` | String! | Name of the branch |
## CiGroup
| Name | Type | Description |
| --- | ---- | ---------- |
| `name` | String | Name of the job group |
| `size` | Int | Size of the group |
## CiJob
| Name | Type | Description |
| --- | ---- | ---------- |
| `name` | String | Name of the job |
## CiStage
| Name | Type | Description |
| --- | ---- | ---------- |
| `name` | String | Name of the stage |
## Commit ## Commit
| Name | Type | Description | | Name | Type | Description |
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Ci::GroupType do
specify { expect(described_class.graphql_name).to eq('CiGroup') }
it 'exposes the expected fields' do
expected_fields = %i[
name
size
jobs
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Ci::JobType do
specify { expect(described_class.graphql_name).to eq('CiJob') }
it 'exposes the expected fields' do
expected_fields = %i[
name
needs
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Ci::StageType do
specify { expect(described_class.graphql_name).to eq('CiStage') }
it 'exposes the expected fields' do
expected_fields = %i[
name
groups
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.project.pipeline.stages.groups' do
include GraphqlHelpers
let(:project) { create(:project, :repository, :public) }
let(:user) { create(:user) }
let(:pipeline) { create(:ci_pipeline, project: project, user: user) }
let(:group_graphql_data) { graphql_data.dig('project', 'pipeline', 'stages', 'nodes', 0, 'groups', 'nodes') }
let(:params) { {} }
let(:fields) do
<<~QUERY
nodes {
#{all_graphql_fields_for('CiGroup')}
}
QUERY
end
let(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
pipeline(iid: "#{pipeline.iid}") {
stages {
nodes {
groups {
#{fields}
}
}
}
}
}
}
)
end
before do
create(:commit_status, pipeline: pipeline, name: 'rspec 0 2')
create(:commit_status, pipeline: pipeline, name: 'rspec 0 1')
create(:commit_status, pipeline: pipeline, name: 'spinach 0 1')
post_graphql(query, current_user: user)
end
it_behaves_like 'a working graphql query'
it 'returns a array of jobs belonging to a pipeline' do
expect(group_graphql_data.map { |g| g.slice('name', 'size') }).to eq([
{ 'name' => 'rspec', 'size' => 2 },
{ 'name' => 'spinach', 'size' => 1 }
])
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.project.pipeline.stages.groups.jobs' do
include GraphqlHelpers
let(:project) { create(:project, :repository, :public) }
let(:user) { create(:user) }
let(:pipeline) do
pipeline = create(:ci_pipeline, project: project, user: user)
stage = create(:ci_stage_entity, pipeline: pipeline, name: 'first')
create(:commit_status, stage_id: stage.id, pipeline: pipeline, name: 'my test job')
pipeline
end
def first(field)
[field.pluralize, 'nodes', 0]
end
let(:jobs_graphql_data) { graphql_data.dig(*%w[project pipeline], *first('stage'), *first('group'), 'jobs', 'nodes') }
let(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
pipeline(iid: "#{pipeline.iid}") {
stages {
nodes {
name
groups {
nodes {
name
jobs {
nodes {
name
}
}
}
}
}
}
}
}
}
)
end
it 'returns the jobs of a pipeline stage' do
post_graphql(query, current_user: user)
expect(jobs_graphql_data).to contain_exactly(a_hash_including('name' => 'my test job'))
end
context 'when fetching jobs from the pipeline' do
it 'avoids N+1 queries' do
control_count = ActiveRecord::QueryRecorder.new do
post_graphql(query, current_user: user)
end
build_stage = create(:ci_stage_entity, name: 'build', pipeline: pipeline)
test_stage = create(:ci_stage_entity, name: 'test', pipeline: pipeline)
create(:commit_status, pipeline: pipeline, stage_id: build_stage.id, name: 'docker 1 2')
create(:commit_status, pipeline: pipeline, stage_id: build_stage.id, name: 'docker 2 2')
create(:commit_status, pipeline: pipeline, stage_id: test_stage.id, name: 'rspec 1 2')
create(:commit_status, pipeline: pipeline, stage_id: test_stage.id, name: 'rspec 2 2')
expect do
post_graphql(query, current_user: user)
end.not_to exceed_query_limit(control_count)
expect(response).to have_gitlab_http_status(:ok)
build_stage = graphql_data.dig('project', 'pipeline', 'stages', 'nodes').find do |stage|
stage['name'] == 'build'
end
test_stage = graphql_data.dig('project', 'pipeline', 'stages', 'nodes').find do |stage|
stage['name'] == 'test'
end
docker_group = build_stage.dig('groups', 'nodes').first
rspec_group = test_stage.dig('groups', 'nodes').first
expect(docker_group['name']).to eq('docker')
expect(rspec_group['name']).to eq('rspec')
docker_jobs = docker_group.dig('jobs', 'nodes')
rspec_jobs = rspec_group.dig('jobs', 'nodes')
expect(docker_jobs).to eq([{ 'name' => 'docker 1 2' }, { 'name' => 'docker 2 2' }])
expect(rspec_jobs).to eq([{ 'name' => 'rspec 1 2' }, { 'name' => 'rspec 2 2' }])
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Query.project.pipeline.stages' do
include GraphqlHelpers
let(:project) { create(:project, :repository, :public) }
let(:user) { create(:user) }
let(:pipeline) { create(:ci_pipeline, project: project, user: user) }
let(:stage_graphql_data) { graphql_data['project']['pipeline']['stages'] }
let(:params) { {} }
let(:fields) do
<<~QUERY
nodes {
#{all_graphql_fields_for('CiStage')}
}
QUERY
end
let(:query) do
%(
query {
project(fullPath: "#{project.full_path}") {
pipeline(iid: "#{pipeline.iid}") {
stages {
#{fields}
}
}
}
}
)
end
before do
create(:ci_stage_entity, pipeline: pipeline, name: 'deploy')
post_graphql(query, current_user: user)
end
it_behaves_like 'a working graphql query'
it 'returns the stage of a pipeline' do
expect(stage_graphql_data['nodes'].first['name']).to eq('deploy')
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