diff --git a/app/graphql/resolvers/ci/jobs_resolver.rb b/app/graphql/resolvers/ci/jobs_resolver.rb index df138a155388bdd665f2e24524879237879beaa5..91f29948ad086801063298579f3ba9f9a401d6d3 100644 --- a/app/graphql/resolvers/ci/jobs_resolver.rb +++ b/app/graphql/resolvers/ci/jobs_resolver.rb @@ -15,9 +15,15 @@ class JobsResolver < BaseResolver required: false, description: 'Filter jobs by status.' - def resolve(statuses: nil, security_report_types: []) + argument :retried, ::GraphQL::Types::Boolean, + required: false, + description: 'Filter jobs by retry-status.' + + def resolve(statuses: nil, security_report_types: [], retried: nil) jobs = init_collection(security_report_types) jobs = jobs.with_status(statuses) if statuses.present? + jobs = jobs.retried if retried + jobs = jobs.latest if retried == false jobs end diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb index b20a671179b2df93cd4b3c80db88065fa236cc8e..802cb09b36a7f3cfac3ff00c6ce970f7af04f144 100644 --- a/app/graphql/types/ci/job_type.rb +++ b/app/graphql/types/ci/job_type.rb @@ -78,6 +78,8 @@ class JobType < BaseObject description: 'Ref name of the job.' field :ref_path, GraphQL::Types::String, null: true, description: 'Path to the ref.' + field :retried, GraphQL::Types::Boolean, null: true, + description: 'Indicates that the job has been retried.' field :retryable, GraphQL::Types::Boolean, null: false, method: :retryable?, description: 'Indicates the job can be retried.' field :scheduling_type, GraphQL::Types::String, null: true, diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 90894506cfda84649cd4c97839ac7a6359ba4f1b..32c34b176c157e55b8cf61e7e153e207becf0cc3 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -9924,6 +9924,7 @@ Represents the total number of issues and their weights for a particular day. | <a id="cijobqueuedduration"></a>`queuedDuration` | [`Duration`](#duration) | How long the job was enqueued before starting. | | <a id="cijobrefname"></a>`refName` | [`String`](#string) | Ref name of the job. | | <a id="cijobrefpath"></a>`refPath` | [`String`](#string) | Path to the ref. | +| <a id="cijobretried"></a>`retried` | [`Boolean`](#boolean) | Indicates that the job has been retried. | | <a id="cijobretryable"></a>`retryable` | [`Boolean!`](#boolean) | Indicates the job can be retried. | | <a id="cijobscheduledat"></a>`scheduledAt` | [`Time`](#time) | Schedule for the build. | | <a id="cijobschedulingtype"></a>`schedulingType` | [`String`](#string) | Type of job scheduling. Value is `dag` if the job uses the `needs` keyword, and `stage` otherwise. | @@ -14972,6 +14973,7 @@ four standard [pagination arguments](#connection-pagination-arguments): | Name | Type | Description | | ---- | ---- | ----------- | +| <a id="pipelinejobsretried"></a>`retried` | [`Boolean`](#boolean) | Filter jobs by retry-status. | | <a id="pipelinejobssecurityreporttypes"></a>`securityReportTypes` | [`[SecurityReportTypeEnum!]`](#securityreporttypeenum) | Filter jobs by the type of security report they produce. | | <a id="pipelinejobsstatuses"></a>`statuses` | [`[CiJobStatus!]`](#cijobstatus) | Filter jobs by status. | diff --git a/ee/spec/requests/api/graphql/boards/board_lists_query_spec.rb b/ee/spec/requests/api/graphql/boards/board_lists_query_spec.rb index 90c6efe7cc98814713927b2c80ceca899a359d10..12aa1d903064fbb9b7efa7896ca575e515760d0c 100644 --- a/ee/spec/requests/api/graphql/boards/board_lists_query_spec.rb +++ b/ee/spec/requests/api/graphql/boards/board_lists_query_spec.rb @@ -90,7 +90,8 @@ def pagination_query(params) context 'when ascending' do it_behaves_like 'sorted paginated query' do - let(:sort_param) { } + include_context 'no sort argument' + let(:first_param) { 2 } let(:all_records) { lists.map { |list| global_id_of(list).to_s } } end diff --git a/ee/spec/requests/api/graphql/boards/epic_board_list_epics_query_spec.rb b/ee/spec/requests/api/graphql/boards/epic_board_list_epics_query_spec.rb index e9071cc6e88c7f193995ecf300201034791c0854..eeb85fbf50bd90d3f84aa6ff0053c4ba7cd3dc6a 100644 --- a/ee/spec/requests/api/graphql/boards/epic_board_list_epics_query_spec.rb +++ b/ee/spec/requests/api/graphql/boards/epic_board_list_epics_query_spec.rb @@ -46,9 +46,8 @@ def pagination_query(params = {}) let(:all_records) { [epic2.to_global_id.to_s, epic1.to_global_id.to_s, epic3.to_global_id.to_s] } it_behaves_like 'sorted paginated query' do - # currently we don't support custom sorting for epic lists, - # nil value will be ignored by ::Graphql::Arguments - let(:sort_param) { nil } + include_context 'no sort argument' + let(:first_param) { 2 } end end diff --git a/ee/spec/requests/api/graphql/boards/epic_boards_query_spec.rb b/ee/spec/requests/api/graphql/boards/epic_boards_query_spec.rb index 07b14627b538bd55d6c3523dd7150d82f577cb3b..b9096ba04166a15b5248918698e71c568773ed44 100644 --- a/ee/spec/requests/api/graphql/boards/epic_boards_query_spec.rb +++ b/ee/spec/requests/api/graphql/boards/epic_boards_query_spec.rb @@ -43,9 +43,8 @@ def pagination_results_data(nodes) end it_behaves_like 'sorted paginated query' do - # currently we don't support custom sorting for epic boards, - # nil value will be ignored by ::Graphql::Arguments - let(:sort_param) { nil } + include_context 'no sort argument' + let(:first_param) { 2 } end end diff --git a/ee/spec/requests/api/graphql/boards/epic_lists_query_spec.rb b/ee/spec/requests/api/graphql/boards/epic_lists_query_spec.rb index 5e78c6713d4111b3efd993c83bc5e9aaad3ec838..fcd362ccf412eff3a2a1f5dcf5763c8a4f653352 100644 --- a/ee/spec/requests/api/graphql/boards/epic_lists_query_spec.rb +++ b/ee/spec/requests/api/graphql/boards/epic_lists_query_spec.rb @@ -51,9 +51,8 @@ def pagination_results_data(nodes) end it_behaves_like 'sorted paginated query' do - # currently we don't support custom sorting for epic lists, - # nil value will be ignored by ::Graphql::Arguments - let(:sort_param) { nil } + include_context 'no sort argument' + let(:first_param) { 2 } end end diff --git a/ee/spec/requests/api/graphql/group/epic/epic_issues_spec.rb b/ee/spec/requests/api/graphql/group/epic/epic_issues_spec.rb index 450c22e30f6867486d7395a9f495aa1dceaadb8e..71df5b2de946c41db47ae4f1bb1a2a7d8d8f25a6 100644 --- a/ee/spec/requests/api/graphql/group/epic/epic_issues_spec.rb +++ b/ee/spec/requests/api/graphql/group/epic/epic_issues_spec.rb @@ -86,8 +86,9 @@ def epic_fields(args) end it_behaves_like 'sorted paginated query' do + include_context 'no sort argument' + let(:current_user) { user } - let(:sort_param) { } let(:first_param) { 1 } let(:all_records) { [issue, confidential_issue].map { |i| global_id_of(i).to_s } } end diff --git a/ee/spec/requests/api/graphql/group_query_spec.rb b/ee/spec/requests/api/graphql/group_query_spec.rb index 68bbf3e64dba90a54928a902364f32c81bb82c20..08213623da05e97e1b4fce91eedddc3b69731513 100644 --- a/ee/spec/requests/api/graphql/group_query_spec.rb +++ b/ee/spec/requests/api/graphql/group_query_spec.rb @@ -364,8 +364,9 @@ def pagination_query(params) let(:start_date) { 5.weeks.ago.to_date.to_s } it_behaves_like 'sorted paginated query' do + include_context 'no sort argument' + let(:node_path) { ['averageCoverage'] } - let(:sort_param) { } let(:first_param) { 2 } let(:all_records) { [cov_1, cov_2, cov_3, cov_4, cov_5].reverse.map(&:coverage) } end diff --git a/ee/spec/requests/api/graphql/project/dast_profiles_spec.rb b/ee/spec/requests/api/graphql/project/dast_profiles_spec.rb index 772ff7d037e5f82bf6c4d8c353ed0b1b1e5a90a1..35614540a996cf96cc570536ca33444fb6b715a0 100644 --- a/ee/spec/requests/api/graphql/project/dast_profiles_spec.rb +++ b/ee/spec/requests/api/graphql/project/dast_profiles_spec.rb @@ -83,7 +83,8 @@ def pagination_results_data(dast_profiles) end it_behaves_like 'sorted paginated query' do - let(:sort_param) { nil } + include_context 'no sort argument' + let(:first_param) { 3 } end diff --git a/ee/spec/requests/api/graphql/project/dast_site_validations_spec.rb b/ee/spec/requests/api/graphql/project/dast_site_validations_spec.rb index 1be1aadc3c79155a14636039164ef3724957e6b0..31b7aa03a87b6df27ce06673f56880c527074fab 100644 --- a/ee/spec/requests/api/graphql/project/dast_site_validations_spec.rb +++ b/ee/spec/requests/api/graphql/project/dast_site_validations_spec.rb @@ -70,7 +70,8 @@ def pagination_results_data(dast_site_validations) end it_behaves_like 'sorted paginated query' do - let(:sort_param) { nil } + include_context 'no sort argument' + let(:first_param) { 3 } let(:all_records) do diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb index 655c36368832b9d581ea8f548c8fc7662ac84611..959f0ebefd8c75166645ab49d1a2af23975b9bff 100644 --- a/spec/graphql/types/ci/job_type_spec.rb +++ b/spec/graphql/types/ci/job_type_spec.rb @@ -33,6 +33,7 @@ refName refPath retryable + retried scheduledAt schedulingType shortSha diff --git a/spec/requests/api/graphql/boards/board_lists_query_spec.rb b/spec/requests/api/graphql/boards/board_lists_query_spec.rb index eb206465bce5c5129f8e4e9e50da5c6b9151673f..39ff108a9e1d4f4a4ba5c04494e4fe37553c4a48 100644 --- a/spec/requests/api/graphql/boards/board_lists_query_spec.rb +++ b/spec/requests/api/graphql/boards/board_lists_query_spec.rb @@ -96,7 +96,8 @@ def pagination_query(params) context 'when ascending' do it_behaves_like 'sorted paginated query' do - let(:sort_param) { } + include_context 'no sort argument' + let(:first_param) { 2 } let(:all_records) { lists.map { |list| a_graphql_entity_for(list) } } end diff --git a/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb index 9bb661a734e5f953f201bf3d17a073c8b56a1401..37ef7089c2f2f1d49b0ab2b046190784033ade7b 100644 --- a/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb +++ b/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb @@ -125,7 +125,8 @@ let_it_be(:descending_manifests) { manifests.reverse.map { |manifest| global_id_of(manifest) } } it_behaves_like 'sorted paginated query' do - let(:sort_param) { '' } + include_context 'no sort argument' + let(:first_param) { 2 } let(:all_records) { descending_manifests.map(&:to_s) } end @@ -134,7 +135,7 @@ def pagination_query(params) # remove sort since the type does not accept sorting, but be future proof graphql_query_for('group', { 'fullPath' => group.full_path }, - query_nodes(:dependencyProxyManifests, :id, include_pagination_info: true, args: params.merge(sort: nil)) + query_nodes(:dependencyProxyManifests, :id, include_pagination_info: true, args: params) ) end end diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb index 1151438b446c2821c80dfad7655d6ea865ce51bc..08c6a2d9927ca5f8fd4735cb59a0fb4272259bff 100644 --- a/spec/requests/api/graphql/project/pipeline_spec.rb +++ b/spec/requests/api/graphql/project/pipeline_spec.rb @@ -105,6 +105,62 @@ def successful_pipeline end end + context 'when a job has been retried' do + let_it_be(:retried) do + create(:ci_build, :retried, + name: build_job.name, + pipeline: pipeline, + stage_idx: 0, + stage: build_job.stage) + end + + let(:fields) do + query_graphql_field(:jobs, { retried: retried_argument }, + query_graphql_field(:nodes, {}, all_graphql_fields_for('CiJob', max_depth: 3))) + end + + context 'when we filter out retried jobs' do + let(:retried_argument) { false } + + it 'contains latest jobs' do + post_graphql(query, current_user: current_user) + + expect(graphql_data_at(*path, :jobs, :nodes)).to include( + a_graphql_entity_for(build_job, :name, :duration, :retried) + ) + + expect(graphql_data_at(*path, :jobs, :nodes)).not_to include( + a_graphql_entity_for(retried) + ) + end + end + + context 'when we filter to only retried jobs' do + let(:retried_argument) { true } + + it 'contains only retried jobs' do + post_graphql(query, current_user: current_user) + + expect(graphql_data_at(*path, :jobs, :nodes)).to contain_exactly( + a_graphql_entity_for(retried) + ) + end + end + + context 'when we pass null explicitly' do + let(:retried_argument) { nil } + + it 'contains all jobs' do + post_graphql(query, current_user: current_user) + + expect(graphql_data_at(*path, :jobs, :nodes)).to include( + a_graphql_entity_for(build_job), + a_graphql_entity_for(retried) + ) + end + end + end + context 'when requesting only builds with certain statuses' do let(:variables) do { diff --git a/spec/support/graphql/arguments.rb b/spec/support/graphql/arguments.rb index a5bb01c31a3cf76cc5185bbd0c2570ba237588a8..478a460a0f60c0c31e6cc31ddfeb2f7abbdf55a4 100644 --- a/spec/support/graphql/arguments.rb +++ b/spec/support/graphql/arguments.rb @@ -5,7 +5,7 @@ class Arguments delegate :blank?, :empty?, to: :to_h def initialize(values) - @values = values.compact + @values = values end def to_h @@ -42,7 +42,7 @@ def self.as_graphql_literal(value) when Integer, Float, Symbol then value.to_s when String, GlobalID then "\"#{value.to_s.gsub(/"/, '\\"')}\"" when Time, Date then "\"#{value.iso8601}\"" - when nil then 'null' + when NilClass then 'null' when true then 'true' when false then 'false' else diff --git a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb index 6d6e7b761f6c893753e3c53a1f81e16cf250329d..59927fa1cc9d952591478cf9a4da651076131455 100644 --- a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb +++ b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb @@ -44,19 +44,25 @@ # end # end # + +# Include this context if your field does not accept a sort argument +RSpec.shared_context 'no sort argument' do + let(:sort_argument) { graphql_args } +end + RSpec.shared_examples 'sorted paginated query' do |conditions = {}| # Provided as a convenience when constructing queries using string concatenation let(:page_info) { 'pageInfo { startCursor endCursor }' } # Convenience for using default implementation of pagination_results_data let(:node_path) { ['id'] } + let(:sort_argument) { graphql_args(sort: sort_param) } it_behaves_like 'requires variables' do - let(:required_variables) { [:sort_param, :first_param, :all_records, :data_path, :current_user] } + let(:required_variables) { [:first_param, :all_records, :data_path, :current_user] } end describe do - let(:sort_argument) { graphql_args(sort: sort_param) } - let(:params) { sort_argument } + let(:params) { sort_argument } # Convenience helper for the large number of queries defined as a projection # from some root value indexed by full_path to a collection of objects with IID diff --git a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb index e534a02e56298341cd19f832c0b8867dba1e62f0..8ab820e9d43b14c99581eed4a8776a2c29ab7d80 100644 --- a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb @@ -64,7 +64,8 @@ def pagination_query(params) context 'when ascending' do it_behaves_like 'sorted paginated query' do - let(:sort_param) { } + include_context 'no sort argument' + let(:first_param) { 2 } def pagination_results_data(nodes) diff --git a/spec/support_specs/graphql/arguments_spec.rb b/spec/support_specs/graphql/arguments_spec.rb index ffb58503a0e3d45b8c8c97d619e6c54dbdfe3939..925af1ab79c27cee94fcba725ef9166d6c04a5df 100644 --- a/spec/support_specs/graphql/arguments_spec.rb +++ b/spec/support_specs/graphql/arguments_spec.rb @@ -52,7 +52,7 @@ float: 2.7, string: %q[he said "no"], enum: :OFF, - null: nil, # we expect this to be omitted - absence is the same as explicit nullness + null: nil, bool_true: true, bool_false: false, var: ::Graphql::Var.new('x', 'Int') @@ -64,6 +64,7 @@ 'int: 42, float: 2.7', %q(string: "he said \\"no\\""), 'enum: OFF', + 'null: null', 'boolTrue: true, boolFalse: false', 'var: $x' ].join(', ')) diff --git a/spec/support_specs/helpers/graphql_helpers_spec.rb b/spec/support_specs/helpers/graphql_helpers_spec.rb index f567097af6fe7ea0d01ba20a653643620a882454..c02e4adf983e57842d264f9639200195f2d95ef1 100644 --- a/spec/support_specs/helpers/graphql_helpers_spec.rb +++ b/spec/support_specs/helpers/graphql_helpers_spec.rb @@ -305,6 +305,7 @@ def norm(query) aFloat: 0.1, aString: "wibble", anEnum: LOW, + null: null, aBool: false, aVar: #{x.to_graphql_value} EXP