Commit bb5ec122 authored by Alex Kalderimis's avatar Alex Kalderimis

Change type of CiJob.needs

This changes the type of `CiJob.needs` from `JobType.connection_type`
to `BuildNeedType.connection_type`. This is because `Build#needs` does
not return a relation of builds, but one of `Ci::BuildNeed`.

This change is a backwards compatible bug-fix.
Any query that works now will continue to work.
parent 28a2f781
# frozen_string_literal: true
module Types
module Ci
# rubocop: disable Graphql/AuthorizeTypes
# This type is only accessible from CiJob
class BuildNeedType < BaseObject
graphql_name 'CiBuildNeed'
field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the job we need to complete.'
end
end
end
...@@ -10,8 +10,8 @@ module Types ...@@ -10,8 +10,8 @@ module Types
description: 'Pipeline the job belongs to' description: 'Pipeline the job belongs to'
field :name, GraphQL::STRING_TYPE, null: true, field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the job' description: 'Name of the job'
field :needs, JobType.connection_type, null: true, field :needs, BuildNeedType.connection_type, null: true,
description: 'Builds that must complete before the jobs run' description: 'References to builds that must complete before the jobs run'
field :detailed_status, Types::Ci::DetailedStatusType, null: true, field :detailed_status, Types::Ci::DetailedStatusType, null: true,
description: 'Detailed status of the job' description: 'Detailed status of the job'
field :scheduled_at, Types::TimeType, null: true, field :scheduled_at, Types::TimeType, null: true,
......
...@@ -9,6 +9,7 @@ module Types ...@@ -9,6 +9,7 @@ module Types
field :name, GraphQL::STRING_TYPE, null: true, field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the stage' description: 'Name of the stage'
field :groups, Ci::GroupType.connection_type, null: true, field :groups, Ci::GroupType.connection_type, null: true,
extras: [:lookahead],
description: 'Group of jobs for the stage' description: 'Group of jobs for the stage'
field :detailed_status, Types::Ci::DetailedStatusType, null: true, field :detailed_status, Types::Ci::DetailedStatusType, null: true,
description: 'Detailed status of the stage' description: 'Detailed status of the stage'
...@@ -18,24 +19,42 @@ module Types ...@@ -18,24 +19,42 @@ module Types
end end
# Issues one query per pipeline # Issues one query per pipeline
def groups def groups(lookahead:)
BatchLoader::GraphQL.for([object.pipeline, object]).batch(default_value: []) do |keys, loader| needs_selected = %i[nodes jobs nodes]
.reduce(lookahead) { |q, f| q.selection(f) }
.selects?(:needs)
key = [object.pipeline, object, needs_selected]
BatchLoader::GraphQL.for(key).batch(default_value: []) do |keys, loader|
by_pipeline = keys.group_by(&:first) by_pipeline = keys.group_by(&:first)
include_needs = keys.any? { |k| k[2] }
by_pipeline.each do |pl, key_group| by_pipeline.each do |pl, key_group|
project = pl.project project = pl.project
stages = key_group.map(&:second).uniq stages = key_group.map(&:second).uniq
indexed = stages.index_by(&:id) indexed = stages.index_by(&:id)
results = pl.latest_statuses.where(stage_id: stages.map(&:id)) # rubocop: disable CodeReuse/ActiveRecord
results.group_by(&:stage_id).each do |stage_id, statuses| jobs_for_pipeline(pl, stages.map(&:id), include_needs).each do |stage_id, statuses|
stage = indexed[stage_id] stage = indexed[stage_id]
groups = ::Ci::Group.fabricate(project, stage, statuses) groups = ::Ci::Group.fabricate(project, stage, statuses)
loader.call([pl, stage], groups) # we don't know (and do not care) whether this set of jobs was
# loaded with needs preloaded as part of the key.
[true, false].each { |b| loader.call([pl, stage, b], groups) }
end
end end
end end
end end
private
# rubocop: disable CodeReuse/ActiveRecord
def jobs_for_pipeline(pipeline, stage_ids, include_needs)
results = pipeline.latest_statuses.where(stage_id: stage_ids)
results = results.preload(:needs) if include_needs
results.group_by(&:stage_id)
end end
# rubocop: enable CodeReuse/ActiveRecord
end end
end end
end end
---
title: Change type of CiJob.needs
merge_request: 50192
author:
type: fixed
...@@ -2288,6 +2288,48 @@ type BurnupChartDailyTotals { ...@@ -2288,6 +2288,48 @@ type BurnupChartDailyTotals {
scopeWeight: Int! scopeWeight: Int!
} }
type CiBuildNeed {
"""
Name of the job we need to complete.
"""
name: String
}
"""
The connection type for CiBuildNeed.
"""
type CiBuildNeedConnection {
"""
A list of edges.
"""
edges: [CiBuildNeedEdge]
"""
A list of nodes.
"""
nodes: [CiBuildNeed]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type CiBuildNeedEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: CiBuildNeed
}
type CiConfig { type CiConfig {
""" """
Linting errors Linting errors
...@@ -2717,7 +2759,7 @@ type CiJob { ...@@ -2717,7 +2759,7 @@ type CiJob {
name: String name: String
""" """
Builds that must complete before the jobs run References to builds that must complete before the jobs run
""" """
needs( needs(
""" """
...@@ -2739,7 +2781,7 @@ type CiJob { ...@@ -2739,7 +2781,7 @@ type CiJob {
Returns the last _n_ elements from the list. Returns the last _n_ elements from the list.
""" """
last: Int last: Int
): CiJobConnection ): CiBuildNeedConnection
""" """
Pipeline the job belongs to Pipeline the job belongs to
......
...@@ -6101,6 +6101,145 @@ ...@@ -6101,6 +6101,145 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "CiBuildNeed",
"description": null,
"fields": [
{
"name": "name",
"description": "Name of the job we need to complete.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "CiBuildNeedConnection",
"description": "The connection type for CiBuildNeed.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CiBuildNeedEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "CiBuildNeed",
"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": "CiBuildNeedEdge",
"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": "CiBuildNeed",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "CiConfig", "name": "CiConfig",
...@@ -7305,7 +7444,7 @@ ...@@ -7305,7 +7444,7 @@
}, },
{ {
"name": "needs", "name": "needs",
"description": "Builds that must complete before the jobs run", "description": "References to builds that must complete before the jobs run",
"args": [ "args": [
{ {
"name": "after", "name": "after",
...@@ -7350,7 +7489,7 @@ ...@@ -7350,7 +7489,7 @@
], ],
"type": { "type": {
"kind": "OBJECT", "kind": "OBJECT",
"name": "CiJobConnection", "name": "CiBuildNeedConnection",
"ofType": null "ofType": null
}, },
"isDeprecated": false, "isDeprecated": false,
...@@ -375,6 +375,12 @@ Represents the total number of issues and their weights for a particular day. ...@@ -375,6 +375,12 @@ Represents the total number of issues and their weights for a particular day.
| `scopeCount` | Int! | Number of issues as of this day | | `scopeCount` | Int! | Number of issues as of this day |
| `scopeWeight` | Int! | Total weight of issues as of this day | | `scopeWeight` | Int! | Total weight of issues as of this day |
### CiBuildNeed
| Field | Type | Description |
| ----- | ---- | ----------- |
| `name` | String | Name of the job we need to complete. |
### CiConfig ### CiConfig
| Field | Type | Description | | Field | Type | Description |
...@@ -430,7 +436,7 @@ Represents the total number of issues and their weights for a particular day. ...@@ -430,7 +436,7 @@ Represents the total number of issues and their weights for a particular day.
| `artifacts` | CiJobArtifactConnection | Artifacts generated by the job | | `artifacts` | CiJobArtifactConnection | Artifacts generated by the job |
| `detailedStatus` | DetailedStatus | Detailed status of the job | | `detailedStatus` | DetailedStatus | Detailed status of the job |
| `name` | String | Name of the job | | `name` | String | Name of the job |
| `needs` | CiJobConnection | Builds that must complete before the jobs run | | `needs` | CiBuildNeedConnection | References to builds that must complete before the jobs run |
| `pipeline` | Pipeline | Pipeline the job belongs to | | `pipeline` | Pipeline | Pipeline the job belongs to |
| `scheduledAt` | Time | Schedule for the build | | `scheduledAt` | Time | Schedule for the build |
......
...@@ -15,7 +15,7 @@ RSpec.describe 'Query.project.pipeline' do ...@@ -15,7 +15,7 @@ RSpec.describe 'Query.project.pipeline' do
let(:pipeline) do let(:pipeline) do
pipeline = create(:ci_pipeline, project: project, user: user) pipeline = create(:ci_pipeline, project: project, user: user)
stage = create(:ci_stage_entity, project: project, pipeline: pipeline, name: 'first') stage = create(:ci_stage_entity, project: project, pipeline: pipeline, name: 'first')
create(:commit_status, stage_id: stage.id, pipeline: pipeline, name: 'my test job') create(:ci_build, stage_id: stage.id, pipeline: pipeline, name: 'my test job')
pipeline pipeline
end end
...@@ -42,6 +42,9 @@ RSpec.describe 'Query.project.pipeline' do ...@@ -42,6 +42,9 @@ RSpec.describe 'Query.project.pipeline' do
jobs { jobs {
nodes { nodes {
name name
needs {
nodes { #{all_graphql_fields_for('CiBuildNeed')} }
}
pipeline { pipeline {
id id
} }
...@@ -53,6 +56,27 @@ RSpec.describe 'Query.project.pipeline' do ...@@ -53,6 +56,27 @@ RSpec.describe 'Query.project.pipeline' do
FIELDS FIELDS
end end
context 'when there are build needs' do
before do
pipeline.statuses.each do |build|
create_list(:ci_build_need, 2, build: build)
end
end
it 'reports the build needs' do
post_graphql(query, current_user: user)
expect(jobs_graphql_data).to contain_exactly a_hash_including(
'needs' => a_hash_including(
'nodes' => contain_exactly(
a_hash_including('name' => String),
a_hash_including('name' => String)
)
)
)
end
end
it 'returns the jobs of a pipeline stage' do it 'returns the jobs of a pipeline stage' do
post_graphql(query, current_user: user) post_graphql(query, current_user: user)
......
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