diff options
Diffstat (limited to 'app/graphql')
47 files changed, 596 insertions, 128 deletions
diff --git a/app/graphql/batch_loaders/award_emoji_votes_batch_loader.rb b/app/graphql/batch_loaders/award_emoji_votes_batch_loader.rb new file mode 100644 index 00000000000..c1b35d3eaf7 --- /dev/null +++ b/app/graphql/batch_loaders/award_emoji_votes_batch_loader.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module BatchLoaders + module AwardEmojiVotesBatchLoader + private + + def load_votes(object, vote_type) + BatchLoader::GraphQL.for(object.id).batch(key: "#{object.issuing_parent_id}-#{vote_type}") do |ids, loader, args| + counts = AwardEmoji.votes_for_collection(ids, object.class.name).named(vote_type).index_by(&:awardable_id) + + ids.each do |id| + loader.call(id, counts[id]&.count || 0) + end + end + end + + def authorized_resource?(object) + Ability.allowed?(current_user, "read_#{object.to_ability_name}".to_sym, object) + end + end +end diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb index c0e063a34d5..37adf4c2d3b 100644 --- a/app/graphql/gitlab_schema.rb +++ b/app/graphql/gitlab_schema.rb @@ -137,6 +137,19 @@ class GitlabSchema < GraphQL::Schema gid end + # Parse an array of strings to an array of GlobalIDs, raising ArgumentError if there are problems + # with it. + # See #parse_gid + # + # ``` + # gids = GitlabSchema.parse_gids(my_array_of_strings, expected_type: ::Project) + # project_ids = gids.map(&:model_id) + # gids.all? { |gid| gid.model_class == ::Project } + # ``` + def parse_gids(global_ids, ctx = {}) + global_ids.map { |gid| parse_gid(gid, ctx) } + end + private def max_query_complexity(ctx) diff --git a/app/graphql/graphql_triggers.rb b/app/graphql/graphql_triggers.rb index 8086d8c02a4..dc4f838ae36 100644 --- a/app/graphql/graphql_triggers.rb +++ b/app/graphql/graphql_triggers.rb @@ -13,6 +13,10 @@ module GraphqlTriggers GitlabSchema.subscriptions.trigger('issuableTitleUpdated', { issuable_id: issuable.to_gid }, issuable) end + def self.issuable_description_updated(issuable) + GitlabSchema.subscriptions.trigger('issuableDescriptionUpdated', { issuable_id: issuable.to_gid }, issuable) + end + def self.issuable_labels_updated(issuable) GitlabSchema.subscriptions.trigger('issuableLabelsUpdated', { issuable_id: issuable.to_gid }, issuable) end @@ -20,6 +24,22 @@ module GraphqlTriggers def self.issuable_dates_updated(issuable) GitlabSchema.subscriptions.trigger('issuableDatesUpdated', { issuable_id: issuable.to_gid }, issuable) end + + def self.merge_request_reviewers_updated(merge_request) + GitlabSchema.subscriptions.trigger( + 'mergeRequestReviewersUpdated', + { issuable_id: merge_request.to_gid }, + merge_request + ) + end + + def self.merge_request_merge_status_updated(merge_request) + GitlabSchema.subscriptions.trigger( + 'mergeRequestMergeStatusUpdated', + { issuable_id: merge_request.to_gid }, + merge_request + ) + end end GraphqlTriggers.prepend_mod diff --git a/app/graphql/mutations/alert_management/create_alert_issue.rb b/app/graphql/mutations/alert_management/create_alert_issue.rb index 2c128e1b339..77a7d7a4147 100644 --- a/app/graphql/mutations/alert_management/create_alert_issue.rb +++ b/app/graphql/mutations/alert_management/create_alert_issue.rb @@ -24,8 +24,8 @@ module Mutations def prepare_response(alert, result) { alert: alert, - issue: result.payload[:issue], - errors: Array(result.message) + issue: result[:issue], + errors: result.errors } end end diff --git a/app/graphql/mutations/ci/job/artifacts_destroy.rb b/app/graphql/mutations/ci/job/artifacts_destroy.rb index c27ab9c4d89..34c58fc1240 100644 --- a/app/graphql/mutations/ci/job/artifacts_destroy.rb +++ b/app/graphql/mutations/ci/job/artifacts_destroy.rb @@ -25,12 +25,21 @@ module Mutations def resolve(id:) job = authorized_find!(id: id) - result = ::Ci::JobArtifacts::DestroyBatchService.new(job.job_artifacts, pick_up_at: Time.current).execute - { - job: job, - destroyed_artifacts_count: result[:destroyed_artifacts_count], - errors: Array(result[:errors]) - } + result = ::Ci::JobArtifacts::DeleteService.new(job).execute + + if result.success? + { + job: job, + destroyed_artifacts_count: result.payload[:destroyed_artifacts_count], + errors: Array(result.payload[:errors]) + } + else + { + job: job, + destroyed_artifacts_count: 0, + errors: Array(result.message) + } + end end end end diff --git a/app/graphql/mutations/ci/pipeline_schedule/base.rb b/app/graphql/mutations/ci/pipeline_schedule/base.rb new file mode 100644 index 00000000000..a737ccce575 --- /dev/null +++ b/app/graphql/mutations/ci/pipeline_schedule/base.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Mutations + module Ci + module PipelineSchedule + class Base < BaseMutation + PipelineScheduleID = ::Types::GlobalIDType[::Ci::PipelineSchedule] + + argument :id, PipelineScheduleID, + required: true, + description: 'ID of the pipeline schedule to mutate.' + + private + + def find_object(id:) + GlobalID::Locator.locate(id) + end + end + end + end +end diff --git a/app/graphql/mutations/ci/pipeline_schedule/delete.rb b/app/graphql/mutations/ci/pipeline_schedule/delete.rb new file mode 100644 index 00000000000..ead9a43161d --- /dev/null +++ b/app/graphql/mutations/ci/pipeline_schedule/delete.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Mutations + module Ci + module PipelineSchedule + class Delete < Base + graphql_name 'PipelineScheduleDelete' + + authorize :admin_pipeline_schedule + + def resolve(id:) + schedule = authorized_find!(id: id) + + if schedule.destroy + { + errors: [] + } + else + { + errors: ['Failed to remove the pipeline schedule'] + } + end + end + end + end + end +end diff --git a/app/graphql/mutations/ci/project_ci_cd_settings_update.rb b/app/graphql/mutations/ci/project_ci_cd_settings_update.rb index b0cffa2c088..27b066ffcf6 100644 --- a/app/graphql/mutations/ci/project_ci_cd_settings_update.rb +++ b/app/graphql/mutations/ci/project_ci_cd_settings_update.rb @@ -19,7 +19,13 @@ module Mutations argument :job_token_scope_enabled, GraphQL::Types::Boolean, required: false, - description: 'Indicates CI job tokens generated in this project have restricted access to resources.' + description: 'Indicates CI/CD job tokens generated in this project ' \ + 'have restricted access to other projects.' + + argument :inbound_job_token_scope_enabled, GraphQL::Types::Boolean, + required: false, + description: 'Indicates CI/CD job tokens generated in other projects ' \ + 'have restricted access to this project.' field :ci_cd_settings, Types::Ci::CiCdSettingType, @@ -28,6 +34,9 @@ module Mutations def resolve(full_path:, **args) project = authorized_find!(full_path) + + args.delete(:inbound_job_token_scope_enabled) unless Feature.enabled?(:ci_inbound_job_token_scope, project) + settings = project.ci_cd_settings settings.update(args) diff --git a/app/graphql/mutations/ci/runner/update.rb b/app/graphql/mutations/ci/runner/update.rb index f98138646be..2f2c8c4c668 100644 --- a/app/graphql/mutations/ci/runner/update.rb +++ b/app/graphql/mutations/ci/runner/update.rb @@ -94,6 +94,7 @@ module Mutations ).execute return if result.success? + response[:runner] = nil response[:errors] = result.errors raise ActiveRecord::Rollback end @@ -102,6 +103,7 @@ module Mutations result = ::Ci::Runners::UpdateRunnerService.new(runner).execute(attrs) return if result.success? + response[:runner] = nil response[:errors] = result.errors raise ActiveRecord::Rollback end diff --git a/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb b/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb index 1f90f394521..e42e59de78f 100644 --- a/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb +++ b/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb @@ -30,6 +30,9 @@ module Mutations argument :start_and_due_date_widget, ::Types::WorkItems::Widgets::StartAndDueDateUpdateInputType, required: false, description: 'Input for start and due date widget.' + argument :labels_widget, ::Types::WorkItems::Widgets::LabelsUpdateInputType, + required: false, + description: 'Input for labels widget.' end end end diff --git a/app/graphql/mutations/issues/create.rb b/app/graphql/mutations/issues/create.rb index 6bf8caf82d7..0389a482822 100644 --- a/app/graphql/mutations/issues/create.rb +++ b/app/graphql/mutations/issues/create.rb @@ -83,13 +83,13 @@ module Mutations params = build_create_issue_params(attributes.merge(author_id: current_user.id), project) spam_params = ::Spam::SpamParams.new_from_request(request: context[:request]) - issue = ::Issues::CreateService.new(project: project, current_user: current_user, params: params, spam_params: spam_params).execute + result = ::Issues::CreateService.new(project: project, current_user: current_user, params: params, spam_params: spam_params).execute - check_spam_action_response!(issue) + check_spam_action_response!(result[:issue]) if result[:issue] { - issue: issue.valid? ? issue : nil, - errors: errors_on_object(issue) + issue: result.success? ? result[:issue] : nil, + errors: result.errors } end diff --git a/app/graphql/mutations/namespace/package_settings/update.rb b/app/graphql/mutations/namespace/package_settings/update.rb index e499e646781..ea72b71715c 100644 --- a/app/graphql/mutations/namespace/package_settings/update.rb +++ b/app/graphql/mutations/namespace/package_settings/update.rb @@ -35,6 +35,36 @@ module Mutations required: false, description: copy_field_description(Types::Namespace::PackageSettingsType, :generic_duplicate_exception_regex) + argument :maven_package_requests_forwarding, + GraphQL::Types::Boolean, + required: false, + description: copy_field_description(Types::Namespace::PackageSettingsType, :maven_package_requests_forwarding) + + argument :npm_package_requests_forwarding, + GraphQL::Types::Boolean, + required: false, + description: copy_field_description(Types::Namespace::PackageSettingsType, :npm_package_requests_forwarding) + + argument :pypi_package_requests_forwarding, + GraphQL::Types::Boolean, + required: false, + description: copy_field_description(Types::Namespace::PackageSettingsType, :pypi_package_requests_forwarding) + + argument :lock_maven_package_requests_forwarding, + GraphQL::Types::Boolean, + required: false, + description: copy_field_description(Types::Namespace::PackageSettingsType, :lock_maven_package_requests_forwarding) + + argument :lock_npm_package_requests_forwarding, + GraphQL::Types::Boolean, + required: false, + description: copy_field_description(Types::Namespace::PackageSettingsType, :lock_npm_package_requests_forwarding) + + argument :lock_pypi_package_requests_forwarding, + GraphQL::Types::Boolean, + required: false, + description: copy_field_description(Types::Namespace::PackageSettingsType, :lock_pypi_package_requests_forwarding) + field :package_settings, Types::Namespace::PackageSettingsType, null: true, diff --git a/app/graphql/mutations/packages/bulk_destroy.rb b/app/graphql/mutations/packages/bulk_destroy.rb new file mode 100644 index 00000000000..a0756d0c3f9 --- /dev/null +++ b/app/graphql/mutations/packages/bulk_destroy.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Mutations + module Packages + class BulkDestroy < ::Mutations::BaseMutation + graphql_name 'DestroyPackages' + + MAX_PACKAGES = 20 + TOO_MANY_IDS_ERROR = "Cannot delete more than #{MAX_PACKAGES} packages" + + argument :ids, + [::Types::GlobalIDType[::Packages::Package]], + required: true, + description: "Global IDs of the Packages. Max #{MAX_PACKAGES}" + + def resolve(ids:) + raise_resource_not_available_error!(TOO_MANY_IDS_ERROR) if ids.size > MAX_PACKAGES + + ids = GitlabSchema.parse_gids(ids, expected_type: ::Packages::Package) + .map(&:model_id) + + service = ::Packages::MarkPackagesForDestructionService.new( + packages: packages_from(ids), + current_user: current_user + ) + result = service.execute + + raise_resource_not_available_error! if result.reason == :unauthorized + + errors = result.error? ? Array.wrap(result[:message]) : [] + + { errors: errors } + end + + private + + def packages_from(ids) + ::Packages::Package.displayable + .id_in(ids) + end + end + end +end diff --git a/app/graphql/mutations/packages/destroy_files.rb b/app/graphql/mutations/packages/destroy_files.rb index 3900a2c46ae..60a21be20d8 100644 --- a/app/graphql/mutations/packages/destroy_files.rb +++ b/app/graphql/mutations/packages/destroy_files.rb @@ -25,7 +25,7 @@ module Mutations project = authorized_find!(project_path) raise_resource_not_available_error! "Cannot delete more than #{MAXIMUM_FILES} files" if ids.size > MAXIMUM_FILES - package_files = ::Packages::PackageFile.where(id: parse_gids(ids)) # rubocop:disable CodeReuse/ActiveRecord + package_files = ::Packages::PackageFile.id_in(parse_gids(ids)) ensure_file_access!(project, package_files) @@ -47,7 +47,7 @@ module Mutations end def parse_gids(gids) - gids.map { |gid| GitlabSchema.parse_gid(gid, expected_type: ::Packages::PackageFile).model_id } + GitlabSchema.parse_gids(gids, expected_type: ::Packages::PackageFile).map(&:model_id) end end end diff --git a/app/graphql/mutations/work_items/update_widgets.rb b/app/graphql/mutations/work_items/update_widgets.rb deleted file mode 100644 index 7037b7e5a2a..00000000000 --- a/app/graphql/mutations/work_items/update_widgets.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true - -module Mutations - module WorkItems - # TODO: Deprecate in favor of using WorkItemUpdate. See https://gitlab.com/gitlab-org/gitlab/-/issues/366300 - class UpdateWidgets < BaseMutation - graphql_name 'WorkItemUpdateWidgets' - description "Updates the attributes of a work item's widgets by global ID." \ - " Available only when feature flag `work_items` is enabled." - - include Mutations::SpamProtection - - authorize :update_work_item - - argument :id, ::Types::GlobalIDType[::WorkItem], - required: true, - description: 'Global ID of the work item.' - - argument :description_widget, ::Types::WorkItems::Widgets::DescriptionInputType, - required: false, - description: 'Input for description widget.' - - field :work_item, Types::WorkItemType, - null: true, - description: 'Updated work item.' - - def resolve(id:, **widget_attributes) - work_item = authorized_find!(id: id) - - unless work_item.project.work_items_feature_flag_enabled? - return { errors: ['`work_items` feature flag disabled for this project'] } - end - - spam_params = ::Spam::SpamParams.new_from_request(request: context[:request]) - - ::WorkItems::UpdateService.new( - project: work_item.project, - current_user: current_user, - # Cannot use prepare to use `.to_h` on each input due to - # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87472#note_945199865 - widget_params: widget_attributes.transform_values { |values| values.to_h }, - spam_params: spam_params - ).execute(work_item) - - check_spam_action_response!(work_item) - - { - work_item: work_item.valid? ? work_item : nil, - errors: errors_on_object(work_item) - } - end - - private - - def find_object(id:) - GitlabSchema.find_by_gid(id) - end - end - end -end diff --git a/app/graphql/resolvers/base_issues_resolver.rb b/app/graphql/resolvers/base_issues_resolver.rb index ec47a8996eb..6357132705e 100644 --- a/app/graphql/resolvers/base_issues_resolver.rb +++ b/app/graphql/resolvers/base_issues_resolver.rb @@ -47,8 +47,8 @@ module Resolvers def preloads { alert_management_alert: [:alert_management_alert], - labels: [:labels], assignees: [:assignees], + participants: Issue.participant_includes, timelogs: [:timelogs], customer_relations_contacts: { customer_relations_contacts: [:group] }, escalation_status: [:incident_management_issuable_escalation_status] diff --git a/app/graphql/resolvers/bulk_labels_resolver.rb b/app/graphql/resolvers/bulk_labels_resolver.rb new file mode 100644 index 00000000000..7362e257fb6 --- /dev/null +++ b/app/graphql/resolvers/bulk_labels_resolver.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Resolvers + class BulkLabelsResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + type Types::LabelType.connection_type, null: true + + def resolve + authorize!(object) + + BatchLoader::GraphQL.for(object.id).batch(cache: false) do |ids, loader, args| + labels = Label.for_targets(object.class.id_in(ids)).group_by(&:target_id) + + ids.each do |id| + loader.call(id, labels[id] || []) + end + end + end + + private + + def authorized_resource?(object) + Ability.allowed?(current_user, :read_label, object.issuing_parent) + end + end +end diff --git a/app/graphql/resolvers/ci/all_jobs_resolver.rb b/app/graphql/resolvers/ci/all_jobs_resolver.rb new file mode 100644 index 00000000000..d918bed9f57 --- /dev/null +++ b/app/graphql/resolvers/ci/all_jobs_resolver.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Resolvers + module Ci + class AllJobsResolver < BaseResolver + type ::Types::Ci::JobType.connection_type, null: true + + argument :statuses, [::Types::Ci::JobStatusEnum], + required: false, + description: 'Filter jobs by status.' + + def resolve(statuses: nil) + ::Ci::JobsFinder.new(current_user: current_user, params: { scope: statuses }).execute + end + end + end +end diff --git a/app/graphql/resolvers/ci/runner_projects_resolver.rb b/app/graphql/resolvers/ci/runner_projects_resolver.rb index ca3b4ebb797..af9a67acfda 100644 --- a/app/graphql/resolvers/ci/runner_projects_resolver.rb +++ b/app/graphql/resolvers/ci/runner_projects_resolver.rb @@ -21,8 +21,8 @@ module Resolvers 'Specify `"id_asc"` if query results\' order is important', milestone: '15.4' }, - description: "Sort order of results. Format: '<field_name>_<sort_direction>', " \ - "for example: 'id_desc' or 'name_asc'" + description: "Sort order of results. Format: `<field_name>_<sort_direction>`, " \ + "for example: `id_desc` or `name_asc`" def resolve_with_lookahead(**args) return unless runner.project_type? diff --git a/app/graphql/resolvers/concerns/looks_ahead.rb b/app/graphql/resolvers/concerns/looks_ahead.rb index b548dc1e175..81099c04e9f 100644 --- a/app/graphql/resolvers/concerns/looks_ahead.rb +++ b/app/graphql/resolvers/concerns/looks_ahead.rb @@ -32,16 +32,37 @@ module LooksAhead {} end + def nested_preloads + {} + end + def filtered_preloads nodes = node_selection return [] unless nodes selected_fields = nodes.selections.map(&:name) + root_level_preloads = preloads_from_node_selection(selected_fields, preloads) - preloads.each.flat_map do |name, requirements| - selected_fields.include?(name) ? requirements : [] - end + root_level_preloads + nested_filtered_preloads(nodes, selected_fields) + end + + def nested_filtered_preloads(nodes, selected_root_fields) + return [] if nested_preloads.empty? + + nested_preloads.each_with_object([]) do |(root_field, fields), result| + next unless selected_root_fields.include?(root_field) + + selected_fields = nodes.selection(root_field).selections.map(&:name) + + result << preloads_from_node_selection(selected_fields, fields) + end.flatten + end + + def preloads_from_node_selection(selected_fields, fields) + fields.each_with_object([]) do |(field, requirements), result| + result << requirements if selected_fields.include?(field) + end.flatten end def node_selection diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb index 697cc6f5b03..d56951bc821 100644 --- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb +++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb @@ -42,7 +42,6 @@ module ResolvesMergeRequests assignees: [:assignees], reviewers: [:reviewers], participants: MergeRequest.participant_includes, - labels: [:labels], author: [:author], merged_at: [:metrics], commit_count: [:metrics], @@ -53,7 +52,8 @@ module ResolvesMergeRequests head_pipeline: [:merge_request_diff, { head_pipeline: [:merge_request] }], timelogs: [:timelogs], pipelines: [:merge_request_diffs], # used by `recent_diff_head_shas` to load pipelines - committers: [merge_request_diff: [:merge_request_diff_commits]] + committers: [merge_request_diff: [:merge_request_diff_commits]], + suggested_reviewers: [:predictions] } end end diff --git a/app/graphql/resolvers/down_votes_count_resolver.rb b/app/graphql/resolvers/down_votes_count_resolver.rb new file mode 100644 index 00000000000..0e7772f988a --- /dev/null +++ b/app/graphql/resolvers/down_votes_count_resolver.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Resolvers + class DownVotesCountResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + include BatchLoaders::AwardEmojiVotesBatchLoader + + type GraphQL::Types::Int, null: true + + def resolve + authorize!(object) + load_votes(object, AwardEmoji::DOWNVOTE_NAME) + end + end +end diff --git a/app/graphql/resolvers/project_pipeline_schedules_resolver.rb b/app/graphql/resolvers/project_pipeline_schedules_resolver.rb new file mode 100644 index 00000000000..eb980f72717 --- /dev/null +++ b/app/graphql/resolvers/project_pipeline_schedules_resolver.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Resolvers + class ProjectPipelineSchedulesResolver < BaseResolver + alias_method :project, :object + + type ::Types::Ci::PipelineScheduleType.connection_type, null: true + + argument :status, ::Types::Ci::PipelineScheduleStatusEnum, + required: false, + description: 'Filter pipeline schedules by active status.' + + def resolve(status: nil) + ::Ci::PipelineSchedulesFinder.new(project).execute(scope: status) + end + end +end diff --git a/app/graphql/resolvers/projects/branch_rules_resolver.rb b/app/graphql/resolvers/projects/branch_rules_resolver.rb index 6c8b416bcea..e99d7ae4d5f 100644 --- a/app/graphql/resolvers/projects/branch_rules_resolver.rb +++ b/app/graphql/resolvers/projects/branch_rules_resolver.rb @@ -3,13 +3,17 @@ module Resolvers module Projects class BranchRulesResolver < BaseResolver + include LooksAhead + type Types::Projects::BranchRuleType.connection_type, null: false alias_method :project, :object - def resolve(**args) - project.protected_branches + def resolve_with_lookahead(**args) + apply_lookahead(project.protected_branches) end end end end + +Resolvers::Projects::BranchRulesResolver.prepend_mod_with('Resolvers::Projects::BranchRulesResolver') diff --git a/app/graphql/resolvers/projects_resolver.rb b/app/graphql/resolvers/projects_resolver.rb index 4d1e1b867da..0bdba53c7af 100644 --- a/app/graphql/resolvers/projects_resolver.rb +++ b/app/graphql/resolvers/projects_resolver.rb @@ -12,8 +12,8 @@ module Resolvers argument :sort, GraphQL::Types::String, required: false, - description: "Sort order of results. Format: '<field_name>_<sort_direction>', " \ - "for example: 'id_desc' or 'name_asc'" + description: "Sort order of results. Format: `<field_name>_<sort_direction>`, " \ + "for example: `id_desc` or `name_asc`" def resolve(**args) ProjectsFinder diff --git a/app/graphql/resolvers/up_votes_count_resolver.rb b/app/graphql/resolvers/up_votes_count_resolver.rb new file mode 100644 index 00000000000..1c78facb694 --- /dev/null +++ b/app/graphql/resolvers/up_votes_count_resolver.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Resolvers + class UpVotesCountResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + include BatchLoaders::AwardEmojiVotesBatchLoader + + type GraphQL::Types::Int, null: true + + def resolve + authorize!(object) + load_votes(object, AwardEmoji::UPVOTE_NAME) + end + end +end diff --git a/app/graphql/resolvers/work_items_resolver.rb b/app/graphql/resolvers/work_items_resolver.rb index a8c0d363325..a4cbcc61ead 100644 --- a/app/graphql/resolvers/work_items_resolver.rb +++ b/app/graphql/resolvers/work_items_resolver.rb @@ -37,20 +37,26 @@ module Resolvers def preloads { - last_edited_by: :last_edited_by, - web_url: { project: { namespace: :route } } + work_item_type: :work_item_type, + web_url: { project: { namespace: :route } }, + widgets: :work_item_type } end - # Allows to apply lookahead for fields - # selected from WidgetInterface - override :node_selection - def node_selection - selected_fields = super - - return unless selected_fields + def nested_preloads + { + widgets: widget_preloads, + user_permissions: { update_work_item: :assignees } + } + end - selected_fields.selection(:widgets) + def widget_preloads + { + last_edited_by: :last_edited_by, + assignees: :assignees, + parent: :work_item_parent, + labels: :labels + } end def unconditional_includes diff --git a/app/graphql/types/ci/ci_cd_setting_type.rb b/app/graphql/types/ci/ci_cd_setting_type.rb index bec8c72e783..574791b79e6 100644 --- a/app/graphql/types/ci/ci_cd_setting_type.rb +++ b/app/graphql/types/ci/ci_cd_setting_type.rb @@ -10,8 +10,17 @@ module Types field :job_token_scope_enabled, GraphQL::Types::Boolean, null: true, - description: 'Indicates CI job tokens generated in this project have restricted access to resources.', + description: 'Indicates CI/CD job tokens generated in this project ' \ + 'have restricted access to other projects.', method: :job_token_scope_enabled? + + field :inbound_job_token_scope_enabled, + GraphQL::Types::Boolean, + null: true, + description: 'Indicates CI/CD job tokens generated in other projects ' \ + 'have restricted access to this project.', + method: :inbound_job_token_scope_enabled? + field :keep_latest_artifact, GraphQL::Types::Boolean, null: true, description: 'Whether to keep the latest builds artifacts.', method: :keep_latest_artifacts_available? diff --git a/app/graphql/types/ci/config_variable_type.rb b/app/graphql/types/ci/config_variable_type.rb index 87ae026c2c1..5b5890fd5a5 100644 --- a/app/graphql/types/ci/config_variable_type.rb +++ b/app/graphql/types/ci/config_variable_type.rb @@ -17,6 +17,10 @@ module Types field :value, GraphQL::Types::String, null: true, description: 'Value of the variable.' + + field :value_options, [GraphQL::Types::String], + null: true, + description: 'Value options for the variable.' end end end diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb index ab6103d9469..4447a10a74e 100644 --- a/app/graphql/types/ci/job_type.rb +++ b/app/graphql/types/ci/job_type.rb @@ -56,6 +56,8 @@ module Types description: 'Indicates the job is active.' field :artifacts, Types::Ci::JobArtifactType.connection_type, null: true, description: 'Artifacts generated by the job.' + field :browse_artifacts_path, GraphQL::Types::String, null: true, + description: "URL for browsing the artifact's archive." field :cancelable, GraphQL::Types::Boolean, null: false, method: :cancelable?, description: 'Indicates the job can be canceled.' field :commit_path, GraphQL::Types::String, null: true, @@ -148,17 +150,7 @@ module Types end def stage - ::Gitlab::Graphql::Lazy.with_value(pipeline) do |pl| - BatchLoader::GraphQL.for([pl, object.stage]).batch do |ids, loader| - by_pipeline = ids - .group_by(&:first) - .transform_values { |grp| grp.map(&:second) } - - by_pipeline.each do |p, names| - p.stages.by_name(names).each { |s| loader.call([p, s.name], s) } - end - end - end + ::Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Stage, object.stage_id).find end # This class is a secret union! @@ -187,6 +179,10 @@ module Types ::Gitlab::Routing.url_helpers.project_job_path(object.project, object) end + def browse_artifacts_path + ::Gitlab::Routing.url_helpers.browse_project_job_artifacts_path(object.project, object) + end + def coverage object&.coverage end diff --git a/app/graphql/types/ci/pipeline_schedule_status_enum.rb b/app/graphql/types/ci/pipeline_schedule_status_enum.rb new file mode 100644 index 00000000000..61bae7daff8 --- /dev/null +++ b/app/graphql/types/ci/pipeline_schedule_status_enum.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Types + module Ci + class PipelineScheduleStatusEnum < BaseEnum + graphql_name 'PipelineScheduleStatus' + + value 'ACTIVE', value: "active", description: 'Active pipeline schedules.' + value 'INACTIVE', value: "inactive", description: 'Inactive pipeline schedules.' + end + end +end diff --git a/app/graphql/types/ci/pipeline_schedule_type.rb b/app/graphql/types/ci/pipeline_schedule_type.rb new file mode 100644 index 00000000000..04f9fc78a92 --- /dev/null +++ b/app/graphql/types/ci/pipeline_schedule_type.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Types + module Ci + class PipelineScheduleType < BaseObject + graphql_name 'PipelineSchedule' + + connection_type_class(Types::CountableConnectionType) + + expose_permissions Types::PermissionTypes::Ci::PipelineSchedules + + authorize :read_pipeline_schedule + + field :id, GraphQL::Types::ID, null: false, description: 'ID of the pipeline schedule.' + + field :description, GraphQL::Types::String, null: true, description: 'Description of the pipeline schedule.' + + field :owner, ::Types::UserType, null: false, description: 'Owner of the pipeline schedule.' + + field :active, GraphQL::Types::Boolean, null: false, description: 'Indicates if a pipeline schedule is active.' + + field :next_run_at, Types::TimeType, null: false, description: 'Time when the next pipeline will run.' + + field :real_next_run, Types::TimeType, null: false, description: 'Time when the next pipeline will run.' + + field :last_pipeline, PipelineType, null: true, description: 'Last pipeline object.' + + field :ref_for_display, GraphQL::Types::String, + null: true, description: 'Git ref for the pipeline schedule.', method: :ref_for_display + + field :ref_path, GraphQL::Types::String, null: true, description: 'Path to the ref that triggered the pipeline.' + + field :for_tag, GraphQL::Types::Boolean, + null: false, description: 'Indicates if a pipelines schedule belongs to a tag.', method: :for_tag? + + field :cron, GraphQL::Types::String, null: false, description: 'Cron notation for the schedule.' + + field :cron_timezone, GraphQL::Types::String, null: false, description: 'Timezone for the pipeline schedule.' + + def ref_path + ::Gitlab::Routing.url_helpers.project_commits_path(object.project, object.ref_for_display) + end + end + end +end diff --git a/app/graphql/types/ci/runner_membership_filter_enum.rb b/app/graphql/types/ci/runner_membership_filter_enum.rb index 4fd7e0749b0..d59a68b427b 100644 --- a/app/graphql/types/ci/runner_membership_filter_enum.rb +++ b/app/graphql/types/ci/runner_membership_filter_enum.rb @@ -15,6 +15,13 @@ module Types description: "Include runners that have either a direct or inherited relationship. " \ "These runners can be specific to a project or a group.", value: :descendants + + value 'ALL_AVAILABLE', + description: + "Include all runners. This list includes runners for all projects in the group " \ + "and subgroups, as well as for the parent groups and instance.", + value: :all_available, + deprecated: { milestone: '15.5', reason: :alpha } end end end diff --git a/app/graphql/types/environment_type.rb b/app/graphql/types/environment_type.rb index eb4e7b1dabf..dd2286d333d 100644 --- a/app/graphql/types/environment_type.rb +++ b/app/graphql/types/environment_type.rb @@ -57,7 +57,7 @@ module Types field :deployments, Types::DeploymentType.connection_type, null: true, - description: 'Deployments of the environment. This field can only be resolved for one project in any single request.', + description: 'Deployments of the environment. This field can only be resolved for one environment in any single request.', resolver: Resolvers::DeploymentsResolver do extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1 end @@ -72,3 +72,5 @@ module Types end end end + +Types::EnvironmentType.prepend_mod_with('Types::EnvironmentType') diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index d897f3cde48..76fac831199 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -43,8 +43,10 @@ module Types field :updated_by, Types::UserType, null: true, description: 'User that last updated the issue.' - field :labels, Types::LabelType.connection_type, null: true, - description: 'Labels of the issue.' + field :labels, Types::LabelType.connection_type, + null: true, + description: 'Labels of the issue.', + resolver: Resolvers::BulkLabelsResolver field :milestone, Types::MilestoneType, null: true, description: 'Milestone of the issue.' @@ -58,15 +60,20 @@ module Types description: 'Indicates the issue is hidden because the author has been banned. ' \ 'Will always return `null` if `ban_user_feature_flag` feature flag is disabled.' - field :downvotes, GraphQL::Types::Int, null: false, - description: 'Number of downvotes the issue has received.' + field :downvotes, GraphQL::Types::Int, + null: false, + description: 'Number of downvotes the issue has received.', + resolver: Resolvers::DownVotesCountResolver field :merge_requests_count, GraphQL::Types::Int, null: false, description: 'Number of merge requests that close the issue on merge.', resolver: Resolvers::MergeRequestsCountResolver field :relative_position, GraphQL::Types::Int, null: true, description: 'Relative position of the issue (used for positioning in epic tree and issue boards).' - field :upvotes, GraphQL::Types::Int, null: false, - description: 'Number of upvotes the issue has received.' + field :upvotes, GraphQL::Types::Int, + null: false, + description: 'Number of upvotes the issue has received.', + resolver: Resolvers::UpVotesCountResolver + field :user_discussions_count, GraphQL::Types::Int, null: false, description: 'Number of user discussions in the issue.', resolver: Resolvers::UserDiscussionsCountResolver diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index 399dcc8e03d..8cc600fc68e 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -75,8 +75,12 @@ module Types null: false, calls_gitaly: true, method: :diverged_from_target_branch?, description: 'Indicates if the source branch is behind the target branch.' - field :downvotes, GraphQL::Types::Int, null: false, - description: 'Number of downvotes for the merge request.' + + field :downvotes, GraphQL::Types::Int, + null: false, + description: 'Number of downvotes for the merge request.', + resolver: Resolvers::DownVotesCountResolver + field :force_remove_source_branch, GraphQL::Types::Boolean, method: :force_remove_source_branch?, null: true, description: 'Indicates if the project settings will lead to source branch deletion after merge.' field :in_progress_merge_commit_sha, GraphQL::Types::String, null: true, @@ -118,8 +122,12 @@ module Types null: false, calls_gitaly: true, method: :target_branch_exists?, description: 'Indicates if the target branch of the merge request exists.' - field :upvotes, GraphQL::Types::Int, null: false, - description: 'Number of upvotes for the merge request.' + + field :upvotes, GraphQL::Types::Int, + null: false, + description: 'Number of upvotes for the merge request.', + resolver: Resolvers::UpVotesCountResolver + field :user_discussions_count, GraphQL::Types::Int, null: true, description: 'Number of user discussions in the merge request.', resolver: Resolvers::UserDiscussionsCountResolver @@ -150,8 +158,11 @@ module Types description: 'Human-readable time estimate of the merge request.' field :human_total_time_spent, GraphQL::Types::String, null: true, description: 'Human-readable total time reported as spent on the merge request.' - field :labels, Types::LabelType.connection_type, null: true, complexity: 5, - description: 'Labels of the merge request.' + field :labels, Types::LabelType.connection_type, + null: true, complexity: 5, + description: 'Labels of the merge request.', + resolver: Resolvers::BulkLabelsResolver + field :milestone, Types::MilestoneType, null: true, description: 'Milestone of the merge request.' field :participants, Types::MergeRequests::ParticipantType.connection_type, null: true, complexity: 15, diff --git a/app/graphql/types/merge_requests/detailed_merge_status_enum.rb b/app/graphql/types/merge_requests/detailed_merge_status_enum.rb index 3de6296154d..1ba72ae33b5 100644 --- a/app/graphql/types/merge_requests/detailed_merge_status_enum.rb +++ b/app/graphql/types/merge_requests/detailed_merge_status_enum.rb @@ -42,6 +42,9 @@ module Types value 'POLICIES_DENIED', value: :policies_denied, description: 'There are denied policies for the merge request.' + value 'EXTERNAL_STATUS_CHECKS', + value: :status_checks_must_pass, + description: 'Status checks must pass.' end end end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index ea833b35085..5ffc1aeacad 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -114,6 +114,7 @@ module Types mount_mutation Mutations::Ci::Pipeline::Cancel mount_mutation Mutations::Ci::Pipeline::Destroy mount_mutation Mutations::Ci::Pipeline::Retry + mount_mutation Mutations::Ci::PipelineSchedule::Delete mount_mutation Mutations::Ci::CiCdSettingsUpdate, deprecated: { reason: :renamed, replacement: 'ProjectCiCdSettingsUpdate', @@ -137,6 +138,8 @@ module Types mount_mutation Mutations::UserCallouts::Create mount_mutation Mutations::UserPreferences::Update mount_mutation Mutations::Packages::Destroy + mount_mutation Mutations::Packages::BulkDestroy, + extensions: [::Gitlab::Graphql::Limit::FieldCallCount => { limit: 1 }] mount_mutation Mutations::Packages::DestroyFile mount_mutation Mutations::Packages::DestroyFiles mount_mutation Mutations::Packages::Cleanup::Policy::Update @@ -146,7 +149,6 @@ module Types mount_mutation Mutations::WorkItems::Delete, alpha: { milestone: '15.1' } mount_mutation Mutations::WorkItems::DeleteTask, alpha: { milestone: '15.1' } mount_mutation Mutations::WorkItems::Update, alpha: { milestone: '15.1' } - mount_mutation Mutations::WorkItems::UpdateWidgets, alpha: { milestone: '15.1' } mount_mutation Mutations::WorkItems::UpdateTask, alpha: { milestone: '15.1' } mount_mutation Mutations::SavedReplies::Create mount_mutation Mutations::SavedReplies::Update diff --git a/app/graphql/types/namespace/package_settings_type.rb b/app/graphql/types/namespace/package_settings_type.rb index 7a0abe619a5..84becba8001 100644 --- a/app/graphql/types/namespace/package_settings_type.rb +++ b/app/graphql/types/namespace/package_settings_type.rb @@ -8,9 +8,50 @@ module Types authorize :admin_package - field :generic_duplicate_exception_regex, Types::UntrustedRegexp, null: true, description: 'When generic_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect.' - field :generic_duplicates_allowed, GraphQL::Types::Boolean, null: false, description: 'Indicates whether duplicate generic packages are allowed for this namespace.' - field :maven_duplicate_exception_regex, Types::UntrustedRegexp, null: true, description: 'When maven_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect.' - field :maven_duplicates_allowed, GraphQL::Types::Boolean, null: false, description: 'Indicates whether duplicate Maven packages are allowed for this namespace.' + field :generic_duplicate_exception_regex, Types::UntrustedRegexp, + null: true, + description: 'When generic_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect.' + field :generic_duplicates_allowed, GraphQL::Types::Boolean, + null: false, + description: 'Indicates whether duplicate generic packages are allowed for this namespace.' + field :maven_duplicate_exception_regex, Types::UntrustedRegexp, + null: true, + description: 'When maven_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect.' + field :maven_duplicates_allowed, GraphQL::Types::Boolean, + null: false, + description: 'Indicates whether duplicate Maven packages are allowed for this namespace.' + + field :maven_package_requests_forwarding, GraphQL::Types::Boolean, + null: true, + description: 'Indicates whether Maven package forwarding is allowed for this namespace.' + field :npm_package_requests_forwarding, GraphQL::Types::Boolean, + null: true, + description: 'Indicates whether npm package forwarding is allowed for this namespace.' + field :pypi_package_requests_forwarding, GraphQL::Types::Boolean, + null: true, + description: 'Indicates whether PyPI package forwarding is allowed for this namespace.' + + field :lock_maven_package_requests_forwarding, GraphQL::Types::Boolean, + null: false, + description: 'Indicates whether Maven package forwarding is locked for all descendent namespaces.' + field :lock_npm_package_requests_forwarding, GraphQL::Types::Boolean, + null: false, + description: 'Indicates whether npm package forwarding is locked for all descendent namespaces.' + field :lock_pypi_package_requests_forwarding, GraphQL::Types::Boolean, + null: false, + description: 'Indicates whether PyPI package forwarding is locked for all descendent namespaces.' + + field :maven_package_requests_forwarding_locked, GraphQL::Types::Boolean, + null: false, + method: :maven_package_requests_forwarding_locked?, + description: 'Indicates whether Maven package forwarding settings are locked by a parent namespace.' + field :npm_package_requests_forwarding_locked, GraphQL::Types::Boolean, + null: false, + method: :npm_package_requests_forwarding_locked?, + description: 'Indicates whether npm package forwarding settings are locked by a parent namespace.' + field :pypi_package_requests_forwarding_locked, GraphQL::Types::Boolean, + null: false, + method: :pypi_package_requests_forwarding_locked?, + description: 'Indicates whether PyPI package forwarding settings are locked by a parent namespace.' end end diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb index c254460a51f..eef5ce40bde 100644 --- a/app/graphql/types/notes/note_type.rb +++ b/app/graphql/types/notes/note_type.rb @@ -41,7 +41,7 @@ module Types deprecated: { reason: :renamed, replacement: 'internal', - milestone: '15.3' + milestone: '15.5' } field :internal, GraphQL::Types::Boolean, null: true, diff --git a/app/graphql/types/permission_types/ci/pipeline_schedules.rb b/app/graphql/types/permission_types/ci/pipeline_schedules.rb new file mode 100644 index 00000000000..268ac6096d0 --- /dev/null +++ b/app/graphql/types/permission_types/ci/pipeline_schedules.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Types + module PermissionTypes + module Ci + class PipelineSchedules < BasePermissionType + graphql_name 'PipelineSchedulePermissions' + + abilities :take_ownership_pipeline_schedule, + :update_pipeline_schedule, + :admin_pipeline_schedule + + ability_field :play_pipeline_schedule, calls_gitaly: true + end + end + end +end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index f43f5c27dac..a41af34ef4c 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -309,6 +309,12 @@ module Types extras: [:lookahead], resolver: Resolvers::ProjectPipelinesResolver + field :pipeline_schedules, + type: Types::Ci::PipelineScheduleType.connection_type, + null: true, + description: 'Pipeline schedules of the project. This field can only be resolved for one project per request.', + resolver: Resolvers::ProjectPipelineSchedulesResolver + field :pipeline, Types::Ci::PipelineType, null: true, description: 'Build pipeline of the project.', diff --git a/app/graphql/types/projects/branch_rule_type.rb b/app/graphql/types/projects/branch_rule_type.rb index 866cff0f439..e7632c17cca 100644 --- a/app/graphql/types/projects/branch_rule_type.rb +++ b/app/graphql/types/projects/branch_rule_type.rb @@ -13,6 +13,13 @@ module Types null: false, description: 'Branch name, with wildcards, for the branch rules.' + field :is_default, + type: GraphQL::Types::Boolean, + null: false, + method: :default_branch?, + calls_gitaly: true, + description: "Check if this branch rule protects the project's default branch." + field :branch_protection, type: Types::BranchRules::BranchProtectionType, null: false, @@ -31,3 +38,5 @@ module Types end end end + +Types::Projects::BranchRuleType.prepend_mod diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 78463a1804a..1b39f43659e 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -154,6 +154,12 @@ module Types null: true, description: "Whether Gitpod is enabled in application settings." + field :jobs, + ::Types::Ci::JobType.connection_type, + null: true, + description: 'All jobs on this GitLab instance.', + resolver: ::Resolvers::Ci::AllJobsResolver + def design_management DesignManagementObject.new(nil) end diff --git a/app/graphql/types/subscription_type.rb b/app/graphql/types/subscription_type.rb index ef701bbfc10..3b8f5c64beb 100644 --- a/app/graphql/types/subscription_type.rb +++ b/app/graphql/types/subscription_type.rb @@ -13,6 +13,9 @@ module Types field :issuable_title_updated, subscription: Subscriptions::IssuableUpdated, null: true, description: 'Triggered when the title of an issuable is updated.' + field :issuable_description_updated, subscription: Subscriptions::IssuableUpdated, null: true, + description: 'Triggered when the description of an issuable is updated.' + field :issuable_labels_updated, subscription: Subscriptions::IssuableUpdated, null: true, description: 'Triggered when the labels of an issuable are updated.' @@ -23,6 +26,11 @@ module Types subscription: Subscriptions::IssuableUpdated, null: true, description: 'Triggered when the reviewers of a merge request are updated.' + + field :merge_request_merge_status_updated, + subscription: Subscriptions::IssuableUpdated, + null: true, + description: 'Triggered when the merge status of a merge request is updated.' end end diff --git a/app/graphql/types/work_items/widget_interface.rb b/app/graphql/types/work_items/widget_interface.rb index eca8c8d845a..a3943361114 100644 --- a/app/graphql/types/work_items/widget_interface.rb +++ b/app/graphql/types/work_items/widget_interface.rb @@ -23,6 +23,9 @@ module Types ORPHAN_TYPES end + # Whenever a new widget is added make sure to update the spec to avoid N + 1 queries in + # spec/requests/api/graphql/project/work_items_spec.rb and add the necessary preloads + # in app/graphql/resolvers/work_items_resolver.rb def self.resolve_type(object, context) case object when ::WorkItems::Widgets::Description diff --git a/app/graphql/types/work_items/widgets/labels_update_input_type.rb b/app/graphql/types/work_items/widgets/labels_update_input_type.rb new file mode 100644 index 00000000000..d38b8cefa63 --- /dev/null +++ b/app/graphql/types/work_items/widgets/labels_update_input_type.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Types + module WorkItems + module Widgets + class LabelsUpdateInputType < BaseInputObject + graphql_name 'WorkItemWidgetLabelsUpdateInput' + + argument :add_label_ids, [Types::GlobalIDType[::Label]], + required: false, + description: 'Global IDs of labels to be added to the work item.', + prepare: ->(label_ids, _ctx) { label_ids.map(&:model_id) } + argument :remove_label_ids, [Types::GlobalIDType[::Label]], + required: false, + description: 'Global IDs of labels to be removed from the work item.', + prepare: ->(label_ids, _ctx) { label_ids.map(&:model_id) } + end + end + end +end |