diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-17 11:33:21 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-11-17 11:33:21 +0000 |
commit | 7021455bd1ed7b125c55eb1b33c5a01f2bc55ee0 (patch) | |
tree | 5bdc2229f5198d516781f8d24eace62fc7e589e9 /app/graphql | |
parent | 185b095e93520f96e9cfc31d9c3e69b498cdab7c (diff) | |
download | gitlab-ce-7021455bd1ed7b125c55eb1b33c5a01f2bc55ee0.tar.gz |
Add latest changes from gitlab-org/gitlab@15-6-stable-eev15.6.0-rc42
Diffstat (limited to 'app/graphql')
80 files changed, 948 insertions, 366 deletions
diff --git a/app/graphql/graphql_triggers.rb b/app/graphql/graphql_triggers.rb index dc4f838ae36..710e7fe110c 100644 --- a/app/graphql/graphql_triggers.rb +++ b/app/graphql/graphql_triggers.rb @@ -25,6 +25,10 @@ module GraphqlTriggers GitlabSchema.subscriptions.trigger('issuableDatesUpdated', { issuable_id: issuable.to_gid }, issuable) end + def self.issuable_milestone_updated(issuable) + GitlabSchema.subscriptions.trigger('issuableMilestoneUpdated', { issuable_id: issuable.to_gid }, issuable) + end + def self.merge_request_reviewers_updated(merge_request) GitlabSchema.subscriptions.trigger( 'mergeRequestReviewersUpdated', diff --git a/app/graphql/mutations/ci/job/artifacts_destroy.rb b/app/graphql/mutations/ci/job/artifacts_destroy.rb index 34c58fc1240..9171299f353 100644 --- a/app/graphql/mutations/ci/job/artifacts_destroy.rb +++ b/app/graphql/mutations/ci/job/artifacts_destroy.rb @@ -18,7 +18,7 @@ module Mutations null: false, description: 'Number of artifacts deleted.' - def find_object(id: ) + def find_object(id:) GlobalID::Locator.locate(id) end diff --git a/app/graphql/mutations/ci/job/base.rb b/app/graphql/mutations/ci/job/base.rb index 6ea8e25a58d..f68f0507b28 100644 --- a/app/graphql/mutations/ci/job/base.rb +++ b/app/graphql/mutations/ci/job/base.rb @@ -10,7 +10,7 @@ module Mutations required: true, description: 'ID of the job to mutate.' - def find_object(id: ) + def find_object(id:) GlobalID::Locator.locate(id) end end diff --git a/app/graphql/mutations/ci/job_artifact/destroy.rb b/app/graphql/mutations/ci/job_artifact/destroy.rb index 47b3535d631..add1b431fbf 100644 --- a/app/graphql/mutations/ci/job_artifact/destroy.rb +++ b/app/graphql/mutations/ci/job_artifact/destroy.rb @@ -20,7 +20,7 @@ module Mutations null: true, description: 'Deleted artifact.' - def find_object(id: ) + def find_object(id:) GlobalID::Locator.locate(id) end diff --git a/app/graphql/mutations/ci/pipeline_schedule/take_ownership.rb b/app/graphql/mutations/ci/pipeline_schedule/take_ownership.rb new file mode 100644 index 00000000000..2e4312f0045 --- /dev/null +++ b/app/graphql/mutations/ci/pipeline_schedule/take_ownership.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Mutations + module Ci + module PipelineSchedule + class TakeOwnership < Base + graphql_name 'PipelineScheduleTakeOwnership' + + authorize :take_ownership_pipeline_schedule + + field :pipeline_schedule, + Types::Ci::PipelineScheduleType, + description: 'Updated pipeline schedule ownership.' + + def resolve(id:) + schedule = authorized_find!(id: id) + + service_response = ::Ci::PipelineSchedules::TakeOwnershipService.new(schedule, current_user).execute + { + pipeline_schedule: schedule, + errors: service_response.errors + } + end + end + end + end +end diff --git a/app/graphql/mutations/ci/runner/bulk_delete.rb b/app/graphql/mutations/ci/runner/bulk_delete.rb index 4265099d28e..f053eda0f55 100644 --- a/app/graphql/mutations/ci/runner/bulk_delete.rb +++ b/app/graphql/mutations/ci/runner/bulk_delete.rb @@ -25,13 +25,12 @@ module Mutations 'Only present if operation was performed synchronously.' def resolve(**runner_attrs) - raise_resource_not_available_error! unless Ability.allowed?(current_user, :delete_runners) - if ids = runner_attrs[:ids] - runners = find_all_runners_by_ids(model_ids_of(ids)) + runner_ids = model_ids_of(ids) + runners = find_all_runners_by_ids(runner_ids) - result = ::Ci::Runners::BulkDeleteRunnersService.new(runners: runners).execute - result.payload.slice(:deleted_count, :deleted_ids).merge(errors: []) + result = ::Ci::Runners::BulkDeleteRunnersService.new(runners: runners, current_user: current_user).execute + result.payload.slice(:deleted_count, :deleted_ids, :errors) else { errors: [] } end @@ -39,14 +38,15 @@ module Mutations private - def model_ids_of(ids) - ids.filter_map { |gid| gid.model_id.to_i } + def model_ids_of(global_ids) + global_ids.filter_map { |gid| gid.model_id.to_i } end def find_all_runners_by_ids(ids) return ::Ci::Runner.none if ids.blank? - ::Ci::Runner.id_in(ids) + limit = ::Ci::Runners::BulkDeleteRunnersService::RUNNER_LIMIT + ::Ci::Runner.id_in(ids).limit(limit + 1) end end end diff --git a/app/graphql/mutations/ci/runner/update.rb b/app/graphql/mutations/ci/runner/update.rb index 2f2c8c4c668..3c99cde60a4 100644 --- a/app/graphql/mutations/ci/runner/update.rb +++ b/app/graphql/mutations/ci/runner/update.rb @@ -68,7 +68,7 @@ module Mutations response = { runner: runner, errors: [] } ::Ci::Runner.transaction do - associate_runner_projects(response, runner, associated_projects_ids) if associated_projects_ids.present? + associate_runner_projects(response, runner, associated_projects_ids) unless associated_projects_ids.nil? update_runner(response, runner, runner_attrs) 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 e42e59de78f..6738f268e92 100644 --- a/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb +++ b/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb @@ -33,6 +33,9 @@ module Mutations argument :labels_widget, ::Types::WorkItems::Widgets::LabelsUpdateInputType, required: false, description: 'Input for labels widget.' + argument :milestone_widget, ::Types::WorkItems::Widgets::MilestoneInputType, + required: false, + description: 'Input for milestone widget.' end end end diff --git a/app/graphql/mutations/container_repositories/destroy.rb b/app/graphql/mutations/container_repositories/destroy.rb index 2a45291be22..fe1c3fe4e61 100644 --- a/app/graphql/mutations/container_repositories/destroy.rb +++ b/app/graphql/mutations/container_repositories/destroy.rb @@ -21,9 +21,11 @@ module Mutations container_repository = authorized_find!(id: id) container_repository.delete_scheduled! - # rubocop:disable CodeReuse/Worker - DeleteContainerRepositoryWorker.perform_async(current_user.id, container_repository.id) - # rubocop:enable CodeReuse/Worker + + unless Feature.enabled?(:container_registry_delete_repository_with_cron_worker) + DeleteContainerRepositoryWorker.perform_async(current_user.id, container_repository.id) # rubocop:disable CodeReuse/Worker + end + track_event(:delete_repository, :container) { diff --git a/app/graphql/mutations/incident_management/timeline_event/create.rb b/app/graphql/mutations/incident_management/timeline_event/create.rb index 1907954cada..419b814dc8c 100644 --- a/app/graphql/mutations/incident_management/timeline_event/create.rb +++ b/app/graphql/mutations/incident_management/timeline_event/create.rb @@ -18,6 +18,10 @@ module Mutations required: true, description: 'Timestamp of when the event occurred.' + argument :timeline_event_tag_names, [GraphQL::Types::String], + required: false, + description: copy_field_description(Types::IncidentManagement::TimelineEventType, :timeline_event_tags) + def resolve(incident_id:, **args) incident = authorized_find!(id: incident_id) diff --git a/app/graphql/mutations/incident_management/timeline_event_tag/base.rb b/app/graphql/mutations/incident_management/timeline_event_tag/base.rb new file mode 100644 index 00000000000..b1d07203ca1 --- /dev/null +++ b/app/graphql/mutations/incident_management/timeline_event_tag/base.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Mutations + module IncidentManagement + module TimelineEventTag + class Base < BaseMutation + field :timeline_event_tag, + ::Types::IncidentManagement::TimelineEventTagType, + null: true, + description: 'Timeline event tag.' + + authorize :admin_incident_management_timeline_event_tag + + private + + def response(result) + { + timeline_event_tag: result.payload[:timeline_event_tag], + errors: result.errors + } + end + end + end + end +end diff --git a/app/graphql/mutations/incident_management/timeline_event_tag/create.rb b/app/graphql/mutations/incident_management/timeline_event_tag/create.rb new file mode 100644 index 00000000000..14b1d288365 --- /dev/null +++ b/app/graphql/mutations/incident_management/timeline_event_tag/create.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Mutations + module IncidentManagement + module TimelineEventTag + class Create < Base + graphql_name 'TimelineEventTagCreate' + + include FindsProject + + argument :project_path, GraphQL::Types::ID, + required: true, + description: 'Project to create the timeline event tag in.' + + argument :name, GraphQL::Types::String, + required: true, + description: 'Name of the tag.' + + def resolve(project_path:, **args) + project = authorized_find!(project_path) + + response ::IncidentManagement::TimelineEventTags::CreateService.new( + project, current_user, args + ).execute + end + end + end + end +end diff --git a/app/graphql/mutations/work_items/create.rb b/app/graphql/mutations/work_items/create.rb index ece00e04ed9..793e5d3caf8 100644 --- a/app/graphql/mutations/work_items/create.rb +++ b/app/graphql/mutations/work_items/create.rb @@ -9,7 +9,7 @@ module Mutations include FindsProject include Mutations::WorkItems::Widgetable - description "Creates a work item. Available only when feature flag `work_items` is enabled." + description "Creates a work item." authorize :create_work_item @@ -22,6 +22,9 @@ module Mutations argument :hierarchy_widget, ::Types::WorkItems::Widgets::HierarchyCreateInputType, required: false, description: 'Input for hierarchy widget.' + argument :milestone_widget, ::Types::WorkItems::Widgets::MilestoneInputType, + required: false, + description: 'Input for milestone widget.' argument :project_path, GraphQL::Types::ID, required: true, description: 'Full path of the project the work item is associated with.' @@ -39,10 +42,6 @@ module Mutations def resolve(project_path:, **attributes) project = authorized_find!(project_path) - unless 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]) params = global_id_compatibility_params(attributes).merge(author_id: current_user.id) type = ::WorkItems::Type.find(attributes[:work_item_type_id]) diff --git a/app/graphql/mutations/work_items/create_from_task.rb b/app/graphql/mutations/work_items/create_from_task.rb index 5ebe8b2c6d7..4ef8269a42f 100644 --- a/app/graphql/mutations/work_items/create_from_task.rb +++ b/app/graphql/mutations/work_items/create_from_task.rb @@ -7,8 +7,7 @@ module Mutations include Mutations::SpamProtection - description "Creates a work item from a task in another work item's description." \ - " Available only when feature flag `work_items` is enabled." + description "Creates a work item from a task in another work item's description." authorize :update_work_item @@ -31,10 +30,6 @@ module Mutations def resolve(id:, work_item_data:) 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]) result = ::WorkItems::CreateFromTaskService.new( diff --git a/app/graphql/mutations/work_items/delete.rb b/app/graphql/mutations/work_items/delete.rb index 240a8b4c11e..4b0067d40d4 100644 --- a/app/graphql/mutations/work_items/delete.rb +++ b/app/graphql/mutations/work_items/delete.rb @@ -4,8 +4,7 @@ module Mutations module WorkItems class Delete < BaseMutation graphql_name 'WorkItemDelete' - description "Deletes a work item." \ - " Available only when feature flag `work_items` is enabled." + description "Deletes a work item." authorize :delete_work_item @@ -20,10 +19,6 @@ module Mutations def resolve(id:) 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 - result = ::WorkItems::DeleteService.new( project: work_item.project, current_user: current_user diff --git a/app/graphql/mutations/work_items/delete_task.rb b/app/graphql/mutations/work_items/delete_task.rb index b1bfed0cbf1..47ab3748ab4 100644 --- a/app/graphql/mutations/work_items/delete_task.rb +++ b/app/graphql/mutations/work_items/delete_task.rb @@ -5,8 +5,7 @@ module Mutations class DeleteTask < BaseMutation graphql_name 'WorkItemDeleteTask' - description "Deletes a task in a work item's description." \ - ' Available only when feature flag `work_items` is enabled.' + description "Deletes a task in a work item's description." authorize :update_work_item @@ -29,10 +28,6 @@ module Mutations work_item = authorized_find!(id: id) task_data[:task] = authorized_find_task!(task_data[:id]) - unless work_item.project.work_items_feature_flag_enabled? - return { errors: ['`work_items` feature flag disabled for this project'] } - end - result = ::WorkItems::DeleteTaskService.new( work_item: work_item, current_user: current_user, diff --git a/app/graphql/mutations/work_items/update.rb b/app/graphql/mutations/work_items/update.rb index b4ed0a1a3ca..04c63d8e876 100644 --- a/app/graphql/mutations/work_items/update.rb +++ b/app/graphql/mutations/work_items/update.rb @@ -4,8 +4,7 @@ module Mutations module WorkItems class Update < BaseMutation graphql_name 'WorkItemUpdate' - description "Updates a work item by Global ID." \ - " Available only when feature flag `work_items` is enabled." + description "Updates a work item by Global ID." include Mutations::SpamProtection include Mutations::WorkItems::UpdateArguments @@ -20,10 +19,6 @@ module Mutations def resolve(id:, **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]) widget_params = extract_widget_params!(work_item.work_item_type, attributes) diff --git a/app/graphql/mutations/work_items/update_task.rb b/app/graphql/mutations/work_items/update_task.rb index 35fbe672b66..aeb4f1d0f06 100644 --- a/app/graphql/mutations/work_items/update_task.rb +++ b/app/graphql/mutations/work_items/update_task.rb @@ -4,8 +4,7 @@ module Mutations module WorkItems class UpdateTask < BaseMutation graphql_name 'WorkItemUpdateTask' - description "Updates a work item's task by Global ID." \ - " Available only when feature flag `work_items` is enabled." + description "Updates a work item's task by Global ID." include Mutations::SpamProtection @@ -30,10 +29,6 @@ module Mutations work_item = authorized_find!(id: id) task = authorized_find_task!(task_data_hash[: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( diff --git a/app/graphql/resolvers/base_issues_resolver.rb b/app/graphql/resolvers/base_issues_resolver.rb deleted file mode 100644 index 6357132705e..00000000000 --- a/app/graphql/resolvers/base_issues_resolver.rb +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -module Resolvers - class BaseIssuesResolver < BaseResolver - prepend IssueResolverArguments - - argument :sort, Types::IssueSortEnum, - description: 'Sort issues by this criteria.', - required: false, - default_value: :created_desc - argument :state, Types::IssuableStateEnum, - required: false, - description: 'Current state of this issue.' - - # see app/graphql/types/issue_connection.rb - type 'Types::IssueConnection', null: true - - NON_STABLE_CURSOR_SORTS = %i[priority_asc priority_desc - popularity_asc popularity_desc - label_priority_asc label_priority_desc - milestone_due_asc milestone_due_desc - escalation_status_asc escalation_status_desc].freeze - - def continue_issue_resolve(parent, finder, **args) - issues = Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all { |q| apply_lookahead(q) } - - if non_stable_cursor_sort?(args[:sort]) - # Certain complex sorts are not supported by the stable cursor pagination yet. - # In these cases, we use offset pagination, so we return the correct connection. - offset_pagination(issues) - else - issues - end - end - - private - - def unconditional_includes - [ - { - project: [:project_feature, :group] - }, - :author - ] - end - - def preloads - { - alert_management_alert: [:alert_management_alert], - assignees: [:assignees], - participants: Issue.participant_includes, - timelogs: [:timelogs], - customer_relations_contacts: { customer_relations_contacts: [:group] }, - escalation_status: [:incident_management_issuable_escalation_status] - } - end - - def non_stable_cursor_sort?(sort) - NON_STABLE_CURSOR_SORTS.include?(sort) - end - end -end - -Resolvers::BaseIssuesResolver.prepend_mod_with('Resolvers::BaseIssuesResolver') diff --git a/app/graphql/resolvers/blobs_resolver.rb b/app/graphql/resolvers/blobs_resolver.rb index 0704a845bb0..fb5fa4465f9 100644 --- a/app/graphql/resolvers/blobs_resolver.rb +++ b/app/graphql/resolvers/blobs_resolver.rb @@ -5,7 +5,7 @@ module Resolvers include Gitlab::Graphql::Authorize::AuthorizeResource type Types::Tree::BlobType.connection_type, null: true - authorize :download_code + authorize :read_code calls_gitaly! alias_method :repository, :object diff --git a/app/graphql/resolvers/bulk_labels_resolver.rb b/app/graphql/resolvers/bulk_labels_resolver.rb index 7362e257fb6..d7e9564352d 100644 --- a/app/graphql/resolvers/bulk_labels_resolver.rb +++ b/app/graphql/resolvers/bulk_labels_resolver.rb @@ -9,7 +9,7 @@ module Resolvers def resolve authorize!(object) - BatchLoader::GraphQL.for(object.id).batch(cache: false) do |ids, loader, args| + BatchLoader::GraphQL.for(object.id).batch(key: object.class.name, cache: false) do |ids, loader, args| labels = Label.for_targets(object.class.id_in(ids)).group_by(&:target_id) ids.each do |id| diff --git a/app/graphql/resolvers/concerns/board_item_filterable.rb b/app/graphql/resolvers/concerns/board_item_filterable.rb index 1457a02e44f..9c0ada4f72c 100644 --- a/app/graphql/resolvers/concerns/board_item_filterable.rb +++ b/app/graphql/resolvers/concerns/board_item_filterable.rb @@ -14,6 +14,16 @@ module BoardItemFilterable set_filter_values(filters[:not]) end + if filters[:or] + if ::Feature.disabled?(:or_issuable_queries, resource_parent) + raise ::Gitlab::Graphql::Errors::ArgumentError, + "'or' arguments are only allowed when the `or_issuable_queries` feature flag is enabled." + end + + rewrite_param_name(filters[:or], :author_usernames, :author_username) + rewrite_param_name(filters[:or], :assignee_usernames, :assignee_username) + end + filters end @@ -30,6 +40,14 @@ module BoardItemFilterable filters[:assignee_id] = filters.delete(:assignee_wildcard_id) end end + + def rewrite_param_name(filters, old_name, new_name) + filters[new_name] = filters.delete(old_name) if filters[old_name].present? + end + + def resource_parent + respond_to?(:board) ? board.resource_parent : list.board.resource_parent + end end ::BoardItemFilterable.prepend_mod_with('Resolvers::BoardItemFilterable') diff --git a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb deleted file mode 100644 index 8295bd58388..00000000000 --- a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb +++ /dev/null @@ -1,167 +0,0 @@ -# frozen_string_literal: true - -module IssueResolverArguments - extend ActiveSupport::Concern - - prepended do - include SearchArguments - include LooksAhead - - argument :iid, GraphQL::Types::String, - required: false, - description: 'IID of the issue. For example, "1".' - argument :iids, [GraphQL::Types::String], - required: false, - description: 'List of IIDs of issues. For example, `["1", "2"]`.' - argument :label_name, [GraphQL::Types::String, null: true], - required: false, - description: 'Labels applied to this issue.' - argument :milestone_title, [GraphQL::Types::String, null: true], - required: false, - description: 'Milestone applied to this issue.' - argument :author_username, GraphQL::Types::String, - required: false, - description: 'Username of the author of the issue.' - argument :assignee_username, GraphQL::Types::String, - required: false, - description: 'Username of a user assigned to the issue.', - deprecated: { reason: 'Use `assigneeUsernames`', milestone: '13.11' } - argument :assignee_usernames, [GraphQL::Types::String], - required: false, - description: 'Usernames of users assigned to the issue.' - argument :assignee_id, GraphQL::Types::String, - required: false, - description: 'ID of a user assigned to the issues. Wildcard values "NONE" and "ANY" are supported.' - argument :created_before, Types::TimeType, - required: false, - description: 'Issues created before this date.' - argument :created_after, Types::TimeType, - required: false, - description: 'Issues created after this date.' - argument :updated_before, Types::TimeType, - required: false, - description: 'Issues updated before this date.' - argument :updated_after, Types::TimeType, - required: false, - description: 'Issues updated after this date.' - argument :closed_before, Types::TimeType, - required: false, - description: 'Issues closed before this date.' - argument :closed_after, Types::TimeType, - required: false, - description: 'Issues closed after this date.' - argument :types, [Types::IssueTypeEnum], - as: :issue_types, - description: 'Filter issues by the given issue types.', - required: false - argument :milestone_wildcard_id, ::Types::MilestoneWildcardIdEnum, - required: false, - description: 'Filter issues by milestone ID wildcard.' - argument :my_reaction_emoji, GraphQL::Types::String, - required: false, - description: 'Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported.' - argument :confidential, - GraphQL::Types::Boolean, - required: false, - description: 'Filter for confidential issues. If "false", excludes confidential issues. If "true", returns only confidential issues.' - argument :not, Types::Issues::NegatedIssueFilterInputType, - description: 'Negated arguments.', - required: false - argument :crm_contact_id, GraphQL::Types::String, - required: false, - description: 'ID of a contact assigned to the issues.' - argument :crm_organization_id, GraphQL::Types::String, - required: false, - description: 'ID of an organization assigned to the issues.' - end - - def resolve_with_lookahead(**args) - return Issue.none if resource_parent.nil? - - finder = IssuesFinder.new(current_user, prepare_finder_params(args)) - - continue_issue_resolve(resource_parent, finder, **args) - end - - def ready?(**args) - args[:not] = args[:not].to_h if args[:not].present? - - params_not_mutually_exclusive(args, mutually_exclusive_assignee_username_args) - params_not_mutually_exclusive(args, mutually_exclusive_milestone_args) - params_not_mutually_exclusive(args.fetch(:not, {}), mutually_exclusive_milestone_args) - params_not_mutually_exclusive(args, mutually_exclusive_release_tag_args) - - super - end - - class_methods do - def resolver_complexity(args, child_complexity:) - complexity = super - complexity += 2 if args[:labelName] - - complexity - end - - def accept_release_tag - argument :release_tag, [GraphQL::Types::String], - required: false, - description: "Release tag associated with the issue's milestone." - argument :release_tag_wildcard_id, Types::ReleaseTagWildcardIdEnum, - required: false, - description: 'Filter issues by release tag ID wildcard.' - end - end - - private - - def prepare_finder_params(args) - params = super(args) - params[:iids] ||= [params.delete(:iid)].compact if params[:iid] - params[:attempt_project_search_optimizations] = true if params[:search].present? - - prepare_assignee_username_params(params) - prepare_release_tag_params(params) - - params - end - - def prepare_release_tag_params(args) - release_tag_wildcard = args.delete(:release_tag_wildcard_id) - return if release_tag_wildcard.blank? - - args[:release_tag] ||= release_tag_wildcard - end - - def prepare_assignee_username_params(args) - args[:assignee_username] = args.delete(:assignee_usernames) if args[:assignee_usernames].present? - args[:not][:assignee_username] = args[:not].delete(:assignee_usernames) if args.dig(:not, :assignee_usernames).present? - end - - def mutually_exclusive_release_tag_args - [:release_tag, :release_tag_wildcard_id] - end - - def mutually_exclusive_milestone_args - [:milestone_title, :milestone_wildcard_id] - end - - def mutually_exclusive_assignee_username_args - [:assignee_usernames, :assignee_username] - end - - def params_not_mutually_exclusive(args, mutually_exclusive_args) - if args.slice(*mutually_exclusive_args).compact.size > 1 - arg_str = mutually_exclusive_args.map { |x| x.to_s.camelize(:lower) }.join(', ') - raise ::Gitlab::Graphql::Errors::ArgumentError, "only one of [#{arg_str}] arguments is allowed at the same time." - end - end - - def resource_parent - # The project could have been loaded in batch by `BatchLoader`. - # At this point we need the `id` of the project to query for issues, so - # make sure it's loaded and not `nil` before continuing. - strong_memoize(:resource_parent) do - object.respond_to?(:sync) ? object.sync : object - end - end -end diff --git a/app/graphql/resolvers/concerns/issues/look_ahead_preloads.rb b/app/graphql/resolvers/concerns/issues/look_ahead_preloads.rb new file mode 100644 index 00000000000..c6e32be245d --- /dev/null +++ b/app/graphql/resolvers/concerns/issues/look_ahead_preloads.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Issues + module LookAheadPreloads + extend ActiveSupport::Concern + + prepended do + include ::LooksAhead + end + + private + + def unconditional_includes + [ + { + project: [:project_feature, :group] + }, + :author + ] + end + + def preloads + { + alert_management_alert: [:alert_management_alert], + assignees: [:assignees], + participants: Issue.participant_includes, + timelogs: [:timelogs], + customer_relations_contacts: { customer_relations_contacts: [:group] }, + escalation_status: [:incident_management_issuable_escalation_status] + } + end + end +end + +Issues::LookAheadPreloads.prepend_mod diff --git a/app/graphql/resolvers/concerns/issues/sort_arguments.rb b/app/graphql/resolvers/concerns/issues/sort_arguments.rb new file mode 100644 index 00000000000..70ae6bd8a5b --- /dev/null +++ b/app/graphql/resolvers/concerns/issues/sort_arguments.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Issues + module SortArguments + extend ActiveSupport::Concern + + NON_STABLE_CURSOR_SORTS = %i[priority_asc priority_desc + popularity_asc popularity_desc + label_priority_asc label_priority_desc + milestone_due_asc milestone_due_desc + escalation_status_asc escalation_status_desc].freeze + + included do + argument :sort, Types::IssueSortEnum, + description: 'Sort issues by this criteria.', + required: false, + default_value: :created_desc + end + + private + + def non_stable_cursor_sort?(sort) + NON_STABLE_CURSOR_SORTS.include?(sort) + end + end +end diff --git a/app/graphql/resolvers/concerns/project_search_arguments.rb b/app/graphql/resolvers/concerns/project_search_arguments.rb index 7e03963f412..faf3b85fc14 100644 --- a/app/graphql/resolvers/concerns/project_search_arguments.rb +++ b/app/graphql/resolvers/concerns/project_search_arguments.rb @@ -25,7 +25,6 @@ module ProjectSearchArguments def project_finder_params(params) { - without_deleted: true, non_public: params[:membership], search: params[:search], search_namespaces: params[:search_namespaces], diff --git a/app/graphql/resolvers/concerns/search_arguments.rb b/app/graphql/resolvers/concerns/search_arguments.rb index 95c6dbf7497..ccc012f2bf9 100644 --- a/app/graphql/resolvers/concerns/search_arguments.rb +++ b/app/graphql/resolvers/concerns/search_arguments.rb @@ -46,9 +46,17 @@ module SearchArguments def prepare_search_params(args) return args unless args[:search].present? + args[:in] = args[:in].join(',') if args[:in].present? + set_search_optimization_param(args) + + args + end + + def set_search_optimization_param(args) + return args unless respond_to?(:resource_parent, true) && resource_parent.present? + parent_type = resource_parent.is_a?(Project) ? :project : :group args[:"attempt_#{parent_type}_search_optimizations"] = true - args[:in] = args[:in].join(',') if args[:in].present? args end diff --git a/app/graphql/resolvers/group_issues_resolver.rb b/app/graphql/resolvers/group_issues_resolver.rb index 05c5e803539..43f01395896 100644 --- a/app/graphql/resolvers/group_issues_resolver.rb +++ b/app/graphql/resolvers/group_issues_resolver.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -# rubocop:disable Graphql/ResolverType (inherited from BaseIssuesResolver) +# rubocop:disable Graphql/ResolverType (inherited from Issues::BaseParentResolver) module Resolvers - class GroupIssuesResolver < BaseIssuesResolver + class GroupIssuesResolver < Issues::BaseParentResolver def self.issuable_collection_name 'issues' end @@ -18,3 +18,4 @@ module Resolvers end end end +# rubocop:enable Graphql/ResolverType diff --git a/app/graphql/resolvers/incident_management/timeline_event_tags_resolver.rb b/app/graphql/resolvers/incident_management/timeline_event_tags_resolver.rb new file mode 100644 index 00000000000..ac6577d119b --- /dev/null +++ b/app/graphql/resolvers/incident_management/timeline_event_tags_resolver.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Resolvers + module IncidentManagement + class TimelineEventTagsResolver < BaseResolver + include LooksAhead + + type ::Types::IncidentManagement::TimelineEventTagType.connection_type, null: true + + def resolve(**args) + apply_lookahead(::IncidentManagement::TimelineEventTagsFinder.new(current_user, object, args).execute) + end + end + end +end diff --git a/app/graphql/resolvers/incident_management/timeline_events_resolver.rb b/app/graphql/resolvers/incident_management/timeline_events_resolver.rb index b9978259e6b..0d46b1387b0 100644 --- a/app/graphql/resolvers/incident_management/timeline_events_resolver.rb +++ b/app/graphql/resolvers/incident_management/timeline_events_resolver.rb @@ -22,11 +22,17 @@ module Resolvers prepare: ->(id, ctx) { id.model_id } end - def resolve(**args) + def resolve_with_lookahead(**args) incident = args[:incident_id].find apply_lookahead(::IncidentManagement::TimelineEventsFinder.new(current_user, incident, args).execute) end + + def preloads + { + timeline_event_tags: [:timeline_event_tags] + } + end end end end diff --git a/app/graphql/resolvers/issue_status_counts_resolver.rb b/app/graphql/resolvers/issue_status_counts_resolver.rb index db5c91daac2..92cda77d717 100644 --- a/app/graphql/resolvers/issue_status_counts_resolver.rb +++ b/app/graphql/resolvers/issue_status_counts_resolver.rb @@ -1,17 +1,29 @@ # frozen_string_literal: true module Resolvers - class IssueStatusCountsResolver < BaseResolver - prepend IssueResolverArguments - + class IssueStatusCountsResolver < Issues::BaseResolver type Types::IssueStatusCountsType, null: true + accept_release_tag - extras [:lookahead] + def resolve(**args) + return Issue.none if resource_parent.nil? + + finder = IssuesFinder.new(current_user, prepare_finder_params(args)) + finder.parent_param = resource_parent + + Gitlab::IssuablesCountForState.new(finder, resource_parent) + end + + private - def continue_issue_resolve(parent, finder, **args) - finder.parent_param = parent - apply_lookahead(Gitlab::IssuablesCountForState.new(finder, parent)) + def resource_parent + # The project could have been loaded in batch by `BatchLoader`. + # At this point we need the `id` of the project to query for issues, so + # make sure it's loaded and not `nil` before continuing. + strong_memoize(:resource_parent) do + object.respond_to?(:sync) ? object.sync : object + end end end end diff --git a/app/graphql/resolvers/issues/base_parent_resolver.rb b/app/graphql/resolvers/issues/base_parent_resolver.rb new file mode 100644 index 00000000000..6308e56f049 --- /dev/null +++ b/app/graphql/resolvers/issues/base_parent_resolver.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Resolvers + module Issues + class BaseParentResolver < Issues::BaseResolver + prepend ::Issues::LookAheadPreloads + include ::Issues::SortArguments + + argument :state, Types::IssuableStateEnum, + required: false, + description: 'Current state of this issue.' + + # see app/graphql/types/issue_connection.rb + type 'Types::IssueConnection', null: true + + def resolve_with_lookahead(**args) + return Issue.none if resource_parent.nil? + + finder = IssuesFinder.new(current_user, prepare_finder_params(args)) + + issues = Gitlab::Graphql::Loaders::IssuableLoader.new(resource_parent, finder).batching_find_all do |q| + apply_lookahead(q) + end + + if non_stable_cursor_sort?(args[:sort]) + # Certain complex sorts are not supported by the stable cursor pagination yet. + # In these cases, we use offset pagination, so we return the correct connection. + offset_pagination(issues) + else + issues + end + end + + private + + def resource_parent + # The project could have been loaded in batch by `BatchLoader`. + # At this point we need the `id` of the project to query for issues, so + # make sure it's loaded and not `nil` before continuing. + strong_memoize(:resource_parent) do + object.respond_to?(:sync) ? object.sync : object + end + end + end + end +end + +Resolvers::Issues::BaseParentResolver.prepend_mod diff --git a/app/graphql/resolvers/issues/base_resolver.rb b/app/graphql/resolvers/issues/base_resolver.rb new file mode 100644 index 00000000000..9a2c4572abb --- /dev/null +++ b/app/graphql/resolvers/issues/base_resolver.rb @@ -0,0 +1,186 @@ +# frozen_string_literal: true + +module Resolvers + module Issues + # rubocop:disable Graphql/ResolverType + class BaseResolver < Resolvers::BaseResolver + include SearchArguments + + argument :assignee_id, GraphQL::Types::String, + required: false, + description: 'ID of a user assigned to the issues. Wildcard values "NONE" and "ANY" are supported.' + argument :assignee_username, GraphQL::Types::String, + required: false, + description: 'Username of a user assigned to the issue.', + deprecated: { reason: 'Use `assigneeUsernames`', milestone: '13.11' } + argument :assignee_usernames, [GraphQL::Types::String], + required: false, + description: 'Usernames of users assigned to the issue.' + argument :author_username, GraphQL::Types::String, + required: false, + description: 'Username of the author of the issue.' + argument :closed_after, Types::TimeType, + required: false, + description: 'Issues closed after this date.' + argument :closed_before, Types::TimeType, + required: false, + description: 'Issues closed before this date.' + argument :confidential, + GraphQL::Types::Boolean, + required: false, + description: 'Filter for confidential issues. If "false", excludes confidential issues.' \ + ' If "true", returns only confidential issues.' + argument :created_after, Types::TimeType, + required: false, + description: 'Issues created after this date.' + argument :created_before, Types::TimeType, + required: false, + description: 'Issues created before this date.' + argument :crm_contact_id, GraphQL::Types::String, + required: false, + description: 'ID of a contact assigned to the issues.' + argument :crm_organization_id, GraphQL::Types::String, + required: false, + description: 'ID of an organization assigned to the issues.' + argument :iid, GraphQL::Types::String, + required: false, + description: 'IID of the issue. For example, "1".' + argument :iids, [GraphQL::Types::String], + required: false, + description: 'List of IIDs of issues. For example, `["1", "2"]`.' + argument :label_name, [GraphQL::Types::String, { null: true }], + required: false, + description: 'Labels applied to this issue.' + argument :milestone_title, [GraphQL::Types::String, { null: true }], + required: false, + description: 'Milestone applied to this issue.' + argument :milestone_wildcard_id, ::Types::MilestoneWildcardIdEnum, + required: false, + description: 'Filter issues by milestone ID wildcard.' + argument :my_reaction_emoji, GraphQL::Types::String, + required: false, + description: 'Filter by reaction emoji applied by the current user.' \ + ' Wildcard values "NONE" and "ANY" are supported.' + argument :not, Types::Issues::NegatedIssueFilterInputType, + description: 'Negated arguments.', + required: false + argument :or, Types::Issues::UnionedIssueFilterInputType, + description: 'List of arguments with inclusive OR.', + required: false + argument :types, [Types::IssueTypeEnum], + as: :issue_types, + description: 'Filter issues by the given issue types.', + required: false + argument :updated_after, Types::TimeType, + required: false, + description: 'Issues updated after this date.' + argument :updated_before, Types::TimeType, + required: false, + description: 'Issues updated before this date.' + + class << self + def resolver_complexity(args, child_complexity:) + complexity = super + complexity += 2 if args[:labelName] + + complexity + end + + def accept_release_tag + argument :release_tag, [GraphQL::Types::String], + required: false, + description: "Release tag associated with the issue's milestone." + argument :release_tag_wildcard_id, Types::ReleaseTagWildcardIdEnum, + required: false, + description: 'Filter issues by release tag ID wildcard.' + end + end + + def ready?(**args) + if args[:or].present? && or_issuable_queries_disabled? + raise ::Gitlab::Graphql::Errors::ArgumentError, + "'or' arguments are only allowed when the `or_issuable_queries` feature flag is enabled." + end + + args[:not] = args[:not].to_h if args[:not] + args[:or] = args[:or].to_h if args[:or] + + params_not_mutually_exclusive(args, mutually_exclusive_assignee_username_args) + params_not_mutually_exclusive(args, mutually_exclusive_milestone_args) + params_not_mutually_exclusive(args.fetch(:not, {}), mutually_exclusive_milestone_args) + params_not_mutually_exclusive(args, mutually_exclusive_release_tag_args) + + super + end + + private + + def or_issuable_queries_disabled? + if respond_to?(:resource_parent, true) + ::Feature.disabled?(:or_issuable_queries, resource_parent) + else + ::Feature.disabled?(:or_issuable_queries) + end + end + + def prepare_finder_params(args) + params = super(args) + params[:not] = params[:not].to_h if params[:not] + params[:or] = params[:or].to_h if params[:or] + params[:iids] ||= [params.delete(:iid)].compact if params[:iid] + + prepare_author_username_params(params) + prepare_assignee_username_params(params) + prepare_release_tag_params(params) + + params + end + + def prepare_release_tag_params(args) + release_tag_wildcard = args.delete(:release_tag_wildcard_id) + return if release_tag_wildcard.blank? + + args[:release_tag] ||= release_tag_wildcard + end + + def prepare_author_username_params(args) + args[:or][:author_username] = args[:or].delete(:author_usernames) if args.dig(:or, :author_usernames).present? + end + + def prepare_assignee_username_params(args) + args[:assignee_username] = args.delete(:assignee_usernames) if args[:assignee_usernames].present? + + if args.dig(:or, :assignee_usernames).present? + args[:or][:assignee_username] = args[:or].delete(:assignee_usernames) + end + + return unless args.dig(:not, :assignee_usernames).present? + + args[:not][:assignee_username] = args[:not].delete(:assignee_usernames) + end + + def mutually_exclusive_release_tag_args + [:release_tag, :release_tag_wildcard_id] + end + + def mutually_exclusive_milestone_args + [:milestone_title, :milestone_wildcard_id] + end + + def mutually_exclusive_assignee_username_args + [:assignee_usernames, :assignee_username] + end + + def params_not_mutually_exclusive(args, mutually_exclusive_args) + return unless args.slice(*mutually_exclusive_args).compact.size > 1 + + arg_str = mutually_exclusive_args.map { |x| x.to_s.camelize(:lower) }.join(', ') + raise ::Gitlab::Graphql::Errors::ArgumentError, + "only one of [#{arg_str}] arguments is allowed at the same time." + end + end + # rubocop:enable Graphql/ResolverType + end +end + +Resolvers::Issues::BaseResolver.prepend_mod diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb index 4b52ef61d57..e3102a7d32a 100644 --- a/app/graphql/resolvers/issues_resolver.rb +++ b/app/graphql/resolvers/issues_resolver.rb @@ -1,8 +1,31 @@ # frozen_string_literal: true -# rubocop:disable Graphql/ResolverType (inherited from BaseIssuesResolver) module Resolvers - class IssuesResolver < BaseIssuesResolver - accept_release_tag + class IssuesResolver < Issues::BaseResolver + prepend ::Issues::LookAheadPreloads + include ::Issues::SortArguments + + argument :state, Types::IssuableStateEnum, + required: false, + description: 'Current state of this issue.' + + # see app/graphql/types/issue_connection.rb + type 'Types::IssueConnection', null: true + + def resolve_with_lookahead(**args) + return unless Feature.enabled?(:root_level_issues_query) + + issues = apply_lookahead( + IssuesFinder.new(current_user, prepare_finder_params(args)).execute + ) + + if non_stable_cursor_sort?(args[:sort]) + # Certain complex sorts are not supported by the stable cursor pagination yet. + # In these cases, we use offset pagination, so we return the correct connection. + offset_pagination(issues) + else + issues + end + end end end diff --git a/app/graphql/resolvers/project_issues_resolver.rb b/app/graphql/resolvers/project_issues_resolver.rb new file mode 100644 index 00000000000..f869d8f11c6 --- /dev/null +++ b/app/graphql/resolvers/project_issues_resolver.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# rubocop:disable Graphql/ResolverType (inherited from Issues::BaseParentResolver) +module Resolvers + class ProjectIssuesResolver < Issues::BaseParentResolver + accept_release_tag + end +end +# rubocop:enable Graphql/ResolverType diff --git a/app/graphql/resolvers/work_item_resolver.rb b/app/graphql/resolvers/work_item_resolver.rb index 9eb7d6bc693..b174a0d2693 100644 --- a/app/graphql/resolvers/work_item_resolver.rb +++ b/app/graphql/resolvers/work_item_resolver.rb @@ -11,10 +11,7 @@ module Resolvers argument :id, ::Types::GlobalIDType[::WorkItem], required: true, description: 'Global ID of the work item.' def resolve(id:) - work_item = authorized_find!(id: id) - return unless work_item.project.work_items_feature_flag_enabled? - - work_item + authorized_find!(id: id) end private diff --git a/app/graphql/resolvers/work_items/types_resolver.rb b/app/graphql/resolvers/work_items/types_resolver.rb index 5f9f8ab5572..2508125d392 100644 --- a/app/graphql/resolvers/work_items/types_resolver.rb +++ b/app/graphql/resolvers/work_items/types_resolver.rb @@ -11,8 +11,6 @@ module Resolvers ' Argument is experimental and can be removed in the future without notice.' def resolve(taskable: nil) - return unless feature_flag_enabled_for_parent?(object) - # This will require a finder in the future when groups/projects get their work item types # All groups/projects use the default types for now base_scope = ::WorkItems::Type.default @@ -20,14 +18,6 @@ module Resolvers base_scope.order_by_name_asc end - - private - - def feature_flag_enabled_for_parent?(parent) - return false unless parent.is_a?(::Project) || parent.is_a?(::Group) - - parent.work_items_feature_flag_enabled? - end end end end diff --git a/app/graphql/resolvers/work_items_resolver.rb b/app/graphql/resolvers/work_items_resolver.rb index a4cbcc61ead..42f4f99d4a9 100644 --- a/app/graphql/resolvers/work_items_resolver.rb +++ b/app/graphql/resolvers/work_items_resolver.rb @@ -26,7 +26,7 @@ module Resolvers required: false def resolve_with_lookahead(**args) - return WorkItem.none if resource_parent.nil? || !resource_parent.work_items_feature_flag_enabled? + return WorkItem.none if resource_parent.nil? finder = ::WorkItems::WorkItemsFinder.new(current_user, prepare_finder_params(args)) @@ -55,7 +55,8 @@ module Resolvers last_edited_by: :last_edited_by, assignees: :assignees, parent: :work_item_parent, - labels: :labels + labels: :labels, + milestone: :milestone } end diff --git a/app/graphql/types/base_argument.rb b/app/graphql/types/base_argument.rb index 2c899e9edaa..4086015dad6 100644 --- a/app/graphql/types/base_argument.rb +++ b/app/graphql/types/base_argument.rb @@ -4,10 +4,10 @@ module Types class BaseArgument < GraphQL::Schema::Argument include GitlabStyleDeprecations - attr_reader :deprecation, :doc_reference + attr_reader :doc_reference def initialize(*args, **kwargs, &block) - @deprecation = gitlab_deprecation(kwargs) + init_gitlab_deprecation(kwargs) @doc_reference = kwargs.delete(:see) # our custom addition `nullable` which allows us to declare diff --git a/app/graphql/types/base_enum.rb b/app/graphql/types/base_enum.rb index 0224aeddac6..11877b79e59 100644 --- a/app/graphql/types/base_enum.rb +++ b/app/graphql/types/base_enum.rb @@ -6,10 +6,8 @@ module Types class CustomValue < GraphQL::Schema::EnumValue include ::GitlabStyleDeprecations - attr_reader :deprecation - def initialize(name, desc = nil, **kwargs) - @deprecation = gitlab_deprecation(kwargs) + init_gitlab_deprecation(kwargs) super(name, desc, **kwargs) end diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb index 6f64e5b5053..36ba3399754 100644 --- a/app/graphql/types/base_field.rb +++ b/app/graphql/types/base_field.rb @@ -8,16 +8,16 @@ module Types DEFAULT_COMPLEXITY = 1 - attr_reader :deprecation, :doc_reference + attr_reader :doc_reference def initialize(**kwargs, &block) + init_gitlab_deprecation(kwargs) @calls_gitaly = !!kwargs.delete(:calls_gitaly) @doc_reference = kwargs.delete(:see) @constant_complexity = kwargs[:complexity].is_a?(Integer) && kwargs[:complexity] > 0 @requires_argument = !!kwargs.delete(:requires_argument) @authorize = Array.wrap(kwargs.delete(:authorize)) kwargs[:complexity] = field_complexity(kwargs[:resolver_class], kwargs[:complexity]) - @deprecation = gitlab_deprecation(kwargs) after_connection_extensions = kwargs.delete(:late_extensions) || [] super(**kwargs, &block) diff --git a/app/graphql/types/boards/board_issue_input_type.rb b/app/graphql/types/boards/board_issue_input_type.rb index 0dd7fbc87da..897e3d05948 100644 --- a/app/graphql/types/boards/board_issue_input_type.rb +++ b/app/graphql/types/boards/board_issue_input_type.rb @@ -9,6 +9,10 @@ module Types required: false, description: 'List of negated arguments.' + argument :or, Types::Issues::UnionedIssueFilterInputType, + required: false, + description: 'List of arguments with inclusive OR.' + argument :search, GraphQL::Types::String, required: false, description: 'Search query for issue title or description.' diff --git a/app/graphql/types/branch_protections/merge_access_level_type.rb b/app/graphql/types/branch_protections/merge_access_level_type.rb index 85295e1ba25..e8fcd57ba80 100644 --- a/app/graphql/types/branch_protections/merge_access_level_type.rb +++ b/app/graphql/types/branch_protections/merge_access_level_type.rb @@ -4,7 +4,7 @@ module Types module BranchProtections class MergeAccessLevelType < BaseAccessLevelType # rubocop:disable Graphql/AuthorizeTypes graphql_name 'MergeAccessLevel' - description 'Represents the merge access level of a branch protection.' + description 'Defines which user roles, users, or groups can merge into a protected branch.' accepts ::ProtectedBranch::MergeAccessLevel end end diff --git a/app/graphql/types/branch_protections/push_access_level_type.rb b/app/graphql/types/branch_protections/push_access_level_type.rb index bfbdc4edbea..c5e21fad88d 100644 --- a/app/graphql/types/branch_protections/push_access_level_type.rb +++ b/app/graphql/types/branch_protections/push_access_level_type.rb @@ -4,7 +4,7 @@ module Types module BranchProtections class PushAccessLevelType < BaseAccessLevelType # rubocop:disable Graphql/AuthorizeTypes graphql_name 'PushAccessLevel' - description 'Represents the push access level of a branch protection.' + description 'Defines which user roles, users, or groups can push to a protected branch.' accepts ::ProtectedBranch::PushAccessLevel end end diff --git a/app/graphql/types/ci/job_need_union.rb b/app/graphql/types/ci/job_need_union.rb index 59608a6a312..61ad5432db8 100644 --- a/app/graphql/types/ci/job_need_union.rb +++ b/app/graphql/types/ci/job_need_union.rb @@ -8,9 +8,10 @@ module Types possible_types Types::Ci::JobType, Types::Ci::BuildNeedType def self.resolve_type(object, context) - if object.is_a?(::Ci::BuildNeed) + case object + when ::Ci::BuildNeed Types::Ci::BuildNeedType - elsif object.is_a?(CommitStatus) + when CommitStatus Types::Ci::JobType else raise TypeNotSupportedError diff --git a/app/graphql/types/commit_signature_interface.rb b/app/graphql/types/commit_signature_interface.rb new file mode 100644 index 00000000000..6b0c16e538a --- /dev/null +++ b/app/graphql/types/commit_signature_interface.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Types + module CommitSignatureInterface + include Types::BaseInterface + + graphql_name 'CommitSignature' + + description 'Represents signing information for a commit' + + field :verification_status, CommitSignatures::VerificationStatusEnum, + null: true, + description: 'Indicates verification status of the associated key or certificate.' + + field :commit_sha, GraphQL::Types::String, + null: true, + description: 'SHA of the associated commit.' + + field :project, Types::ProjectType, + null: true, + description: 'Project of the associated commit.' + + orphan_types Types::CommitSignatures::GpgSignatureType, + Types::CommitSignatures::X509SignatureType + + def self.resolve_type(object, context) + case object + when ::CommitSignatures::GpgSignature + Types::CommitSignatures::GpgSignatureType + when ::CommitSignatures::X509CommitSignature + Types::CommitSignatures::X509SignatureType + else + raise 'Unsupported commit signature type' + end + end + end +end diff --git a/app/graphql/types/commit_signatures/gpg_signature_type.rb b/app/graphql/types/commit_signatures/gpg_signature_type.rb new file mode 100644 index 00000000000..2a845fff3e2 --- /dev/null +++ b/app/graphql/types/commit_signatures/gpg_signature_type.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Types + module CommitSignatures + class GpgSignatureType < Types::BaseObject + graphql_name 'GpgSignature' + description 'GPG signature for a signed commit' + + implements Types::CommitSignatureInterface + + authorize :download_code + + field :user, Types::UserType, null: true, + description: 'User associated with the key.' + + field :gpg_key_user_name, GraphQL::Types::String, + null: true, + description: 'User name associated with the GPG key.' + + field :gpg_key_user_email, GraphQL::Types::String, + null: true, + description: 'User email associated with the GPG key.' + + field :gpg_key_primary_keyid, GraphQL::Types::String, + null: true, + description: 'ID of the GPG key.' + end + end +end diff --git a/app/graphql/types/commit_signatures/verification_status_enum.rb b/app/graphql/types/commit_signatures/verification_status_enum.rb new file mode 100644 index 00000000000..9df1b7abd82 --- /dev/null +++ b/app/graphql/types/commit_signatures/verification_status_enum.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# rubocop:disable Graphql/AuthorizeTypes + +module Types + module CommitSignatures + class VerificationStatusEnum < BaseEnum + graphql_name 'VerificationStatus' + description 'Verification status of a GPG or X.509 signature for a commit.' + + ::CommitSignatures::GpgSignature.verification_statuses.each do |status, _| + value status.upcase, value: status, description: "#{status} verification status." + end + end + end +end + +# rubocop:enable Graphql/AuthorizeTypes diff --git a/app/graphql/types/commit_signatures/x509_signature_type.rb b/app/graphql/types/commit_signatures/x509_signature_type.rb new file mode 100644 index 00000000000..9ac96dbc015 --- /dev/null +++ b/app/graphql/types/commit_signatures/x509_signature_type.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Types + module CommitSignatures + class X509SignatureType < Types::BaseObject + graphql_name 'X509Signature' + description 'X.509 signature for a signed commit' + + implements Types::CommitSignatureInterface + + authorize :download_code + + field :user, Types::UserType, null: true, + calls_gitaly: true, + description: 'User associated with the key.' + + field :x509_certificate, Types::X509CertificateType, + null: true, + description: 'Certificate used for the signature.' + end + end +end diff --git a/app/graphql/types/commit_type.rb b/app/graphql/types/commit_type.rb index dfb02f29fb7..5dd862c7388 100644 --- a/app/graphql/types/commit_type.rb +++ b/app/graphql/types/commit_type.rb @@ -4,7 +4,7 @@ module Types class CommitType < BaseObject graphql_name 'Commit' - authorize :download_code + authorize :read_code present_using CommitPresenter @@ -40,6 +40,11 @@ module Types field :web_path, type: GraphQL::Types::String, null: false, description: 'Web path of the commit.' + field :signature, type: Types::CommitSignatureInterface, + null: true, + calls_gitaly: true, + description: 'Signature of the commit.' + field :signature_html, type: GraphQL::Types::String, null: true, calls_gitaly: true, description: 'Rendered HTML of the commit signature.' diff --git a/app/graphql/types/concerns/gitlab_style_deprecations.rb b/app/graphql/types/concerns/gitlab_style_deprecations.rb index e404f1fcad9..859a27cac4c 100644 --- a/app/graphql/types/concerns/gitlab_style_deprecations.rb +++ b/app/graphql/types/concerns/gitlab_style_deprecations.rb @@ -1,14 +1,22 @@ # frozen_string_literal: true -# Concern for handling deprecation arguments. +# Concern for handling GraphQL deprecations. # https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-schema-items module GitlabStyleDeprecations extend ActiveSupport::Concern + included do + attr_accessor :deprecation + end + + def visible?(ctx) + super && ctx[:remove_deprecated] == true ? deprecation.nil? : true + end + private - # Mutate the arguments, returns the deprecation - def gitlab_deprecation(kwargs) + # Set deprecation, mutate the arguments + def init_gitlab_deprecation(kwargs) if kwargs[:deprecation_reason].present? raise ArgumentError, 'Use `deprecated` property instead of `deprecation_reason`. ' \ 'See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-schema-items' @@ -17,14 +25,12 @@ module GitlabStyleDeprecations # GitLab allows items to be marked as "alpha", which leverages GraphQL deprecations. deprecation_args = kwargs.extract!(:alpha, :deprecated) - deprecation = ::Gitlab::Graphql::Deprecation.parse(**deprecation_args) + self.deprecation = ::Gitlab::Graphql::Deprecation.parse(**deprecation_args) return unless deprecation raise ArgumentError, "Bad deprecation. #{deprecation.errors.full_messages.to_sentence}" unless deprecation.valid? kwargs[:deprecation_reason] = deprecation.deprecation_reason kwargs[:description] = deprecation.edit_description(kwargs[:description]) - - deprecation end end diff --git a/app/graphql/types/deployment_details_type.rb b/app/graphql/types/deployment_details_type.rb index f8ba0cb1b24..bbb5cc8e3f1 100644 --- a/app/graphql/types/deployment_details_type.rb +++ b/app/graphql/types/deployment_details_type.rb @@ -5,7 +5,7 @@ module Types graphql_name 'DeploymentDetails' description 'The details of the deployment' authorize :read_deployment - present_using Deployments::DeploymentPresenter + present_using ::Deployments::DeploymentPresenter field :tags, [Types::DeploymentTagType], @@ -13,3 +13,5 @@ module Types calls_gitaly: true end end + +Types::DeploymentDetailsType.prepend_mod_with('Types::DeploymentDetailsType') diff --git a/app/graphql/types/deployment_type.rb b/app/graphql/types/deployment_type.rb index 70a3a4cb574..59b59dc4e1d 100644 --- a/app/graphql/types/deployment_type.rb +++ b/app/graphql/types/deployment_type.rb @@ -11,7 +11,7 @@ module Types graphql_name 'Deployment' description 'The deployment of an environment' - present_using Deployments::DeploymentPresenter + present_using ::Deployments::DeploymentPresenter authorize :read_deployment diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb index 45357de5502..4e5ddbac8a2 100644 --- a/app/graphql/types/group_type.rb +++ b/app/graphql/types/group_type.rb @@ -231,9 +231,7 @@ module Types field :work_item_types, Types::WorkItems::TypeType.connection_type, resolver: Resolvers::WorkItems::TypesResolver, - description: 'Work item types available to the group.' \ - ' Returns `null` if `work_items` feature flag is disabled.' \ - ' This flag is disabled by default, because the feature is experimental and is subject to change without notice.' + description: 'Work item types available to the group.' def label(title:) BatchLoader::GraphQL.for(title).batch(key: group) do |titles, loader, args| diff --git a/app/graphql/types/incident_management/timeline_event_tag_type.rb b/app/graphql/types/incident_management/timeline_event_tag_type.rb new file mode 100644 index 00000000000..452294d4797 --- /dev/null +++ b/app/graphql/types/incident_management/timeline_event_tag_type.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Types + module IncidentManagement + class TimelineEventTagType < BaseObject + graphql_name 'TimelineEventTagType' + + description 'Describes a tag on an incident management timeline event.' + + authorize :read_incident_management_timeline_event_tag + + field :id, + Types::GlobalIDType[::IncidentManagement::TimelineEventTag], + null: false, + description: 'ID of the timeline event tag.' + + field :name, + GraphQL::Types::String, + null: false, + description: 'Name of the timeline event tag.' + end + end +end diff --git a/app/graphql/types/incident_management/timeline_event_type.rb b/app/graphql/types/incident_management/timeline_event_type.rb index 690facc8732..939dd9f09e5 100644 --- a/app/graphql/types/incident_management/timeline_event_type.rb +++ b/app/graphql/types/incident_management/timeline_event_type.rb @@ -53,6 +53,13 @@ module Types null: false, description: 'Timestamp when the event occurred.' + field :timeline_event_tags, + ::Types::IncidentManagement::TimelineEventTagType.connection_type, + null: true, + description: 'Tags for the incident timeline event.', + extras: [:lookahead], + resolver: Resolvers::IncidentManagement::TimelineEventTagsResolver + field :created_at, Types::TimeType, null: false, diff --git a/app/graphql/types/issue_connection.rb b/app/graphql/types/issue_connection.rb index 8e5c88648ea..2f07888b43e 100644 --- a/app/graphql/types/issue_connection.rb +++ b/app/graphql/types/issue_connection.rb @@ -1,15 +1,22 @@ # frozen_string_literal: true # Normally this wouldn't be needed and we could use +# # type Types::IssueType.connection_type, null: true -# in a resolver. However we can end up with cyclic definitions, -# which can result in errors like +# +# in a resolver. However we can end up with cyclic definitions. +# Running the spec locally can result in errors like +# # NameError: uninitialized constant Resolvers::GroupIssuesResolver # -# Now we would use +# or other errors. To fix this, we created this file and use +# # type "Types::IssueConnection", null: true +# # which gives a delayed resolution, and the proper connection type. +# # See app/graphql/resolvers/base_issues_resolver.rb # Reference: https://github.com/rmosolgo/graphql-ruby/issues/3974#issuecomment-1084444214 - +# and https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#testing-tips-and-tricks +# Types::IssueConnection = Types::IssueType.connection_type diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index 76fac831199..dd2ad26ce49 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -123,7 +123,15 @@ module Types field :alert_management_alert, Types::AlertManagement::AlertType, null: true, - description: 'Alert associated to this issue.' + description: 'Alert associated to this issue.', + deprecated: { reason: 'Use `alert_management_alerts`', milestone: '15.6' } + + field :alert_management_alerts, + Types::AlertManagement::AlertType.connection_type, + null: true, + description: 'Alert Management alerts associated to this issue.', + extras: [:lookahead], + resolver: Resolvers::AlertManagement::AlertResolver field :severity, Types::IssuableSeverityEnum, null: true, description: 'Severity level of the incident.' diff --git a/app/graphql/types/issue_type_enum.rb b/app/graphql/types/issue_type_enum.rb index 1044c2ceea4..78cd27f60c3 100644 --- a/app/graphql/types/issue_type_enum.rb +++ b/app/graphql/types/issue_type_enum.rb @@ -10,7 +10,11 @@ module Types end value 'TASK', value: 'task', - description: 'Task issue type. Available only when feature flag `work_items` is enabled.', + description: 'Task issue type.', alpha: { milestone: '15.2' } + + value 'OBJECTIVE', value: 'objective', + description: 'Objective issue type. Available only when feature flag `okrs_mvc` is enabled.', + alpha: { milestone: '15.6' } end end diff --git a/app/graphql/types/issues/unioned_issue_filter_input_type.rb b/app/graphql/types/issues/unioned_issue_filter_input_type.rb new file mode 100644 index 00000000000..9c7261279c7 --- /dev/null +++ b/app/graphql/types/issues/unioned_issue_filter_input_type.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Types + module Issues + class UnionedIssueFilterInputType < BaseInputObject + graphql_name 'UnionedIssueFilterInput' + + argument :assignee_usernames, [GraphQL::Types::String], + required: false, + description: 'Filters issues that are assigned to at least one of the given users.' + argument :author_usernames, [GraphQL::Types::String], + required: false, + description: 'Filters issues that are authored by one of the given users.' + end + end +end diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index 8cc600fc68e..49bf7aa638c 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -20,7 +20,7 @@ module Types description: 'Timestamp of when the merge request was created.' field :description, GraphQL::Types::String, null: true, description: 'Description of the merge request (Markdown rendered as HTML for caching).' - field :diff_head_sha, GraphQL::Types::String, null: true, + field :diff_head_sha, GraphQL::Types::String, null: true, calls_gitaly: true, description: 'Diff head SHA of the merge request.' field :diff_refs, Types::DiffRefsType, null: true, description: 'References of the base SHA, the head SHA, and the start SHA for this merge request.' @@ -100,8 +100,7 @@ module Types field :detailed_merge_status, ::Types::MergeRequests::DetailedMergeStatusEnum, null: true, calls_gitaly: true, - description: 'Detailed merge status of the merge request.', - alpha: { milestone: '15.3' } + description: 'Detailed merge status of the merge request.' field :mergeable_discussions_state, GraphQL::Types::Boolean, null: true, calls_gitaly: true, diff --git a/app/graphql/types/metadata_type.rb b/app/graphql/types/metadata_type.rb index b00fcfd38ad..492cca365f3 100644 --- a/app/graphql/types/metadata_type.rb +++ b/app/graphql/types/metadata_type.rb @@ -6,6 +6,8 @@ module Types authorize :read_instance_metadata + field :enterprise, GraphQL::Types::Boolean, null: false, + description: 'Enterprise edition.' field :kas, ::Types::Metadata::KasType, null: false, description: 'Metadata about KAS.' field :revision, GraphQL::Types::String, null: false, diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 5ffc1aeacad..1cbb2ede544 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -47,10 +47,11 @@ module Types mount_mutation Mutations::DependencyProxy::ImageTtlGroupPolicy::Update mount_mutation Mutations::DependencyProxy::GroupSettings::Update mount_mutation Mutations::Environments::CanaryIngress::Update - mount_mutation Mutations::IncidentManagement::TimelineEvent::Create + mount_mutation Mutations::IncidentManagement::TimelineEvent::Create, alpha: { milestone: '15.6' } mount_mutation Mutations::IncidentManagement::TimelineEvent::PromoteFromNote mount_mutation Mutations::IncidentManagement::TimelineEvent::Update mount_mutation Mutations::IncidentManagement::TimelineEvent::Destroy + mount_mutation Mutations::IncidentManagement::TimelineEventTag::Create mount_mutation Mutations::Issues::Create mount_mutation Mutations::Issues::SetAssignees mount_mutation Mutations::Issues::SetCrmContacts @@ -115,6 +116,7 @@ module Types mount_mutation Mutations::Ci::Pipeline::Destroy mount_mutation Mutations::Ci::Pipeline::Retry mount_mutation Mutations::Ci::PipelineSchedule::Delete + mount_mutation Mutations::Ci::PipelineSchedule::TakeOwnership mount_mutation Mutations::Ci::CiCdSettingsUpdate, deprecated: { reason: :renamed, replacement: 'ProjectCiCdSettingsUpdate', diff --git a/app/graphql/types/packages/package_base_type.rb b/app/graphql/types/packages/package_base_type.rb index 2dc4a2a2bb6..9ec4bb73c47 100644 --- a/app/graphql/types/packages/package_base_type.rb +++ b/app/graphql/types/packages/package_base_type.rb @@ -12,6 +12,8 @@ module Types field :id, ::Types::GlobalIDType[::Packages::Package], null: false, description: 'ID of the package.' + field :_links, Types::Packages::PackageLinksType, null: false, method: :itself, + description: 'Map of links to perform actions on the package.' field :can_destroy, GraphQL::Types::Boolean, null: false, description: 'Whether the user can destroy the package.' field :created_at, Types::TimeType, null: false, description: 'Date of creation.' field :metadata, Types::Packages::MetadataType, diff --git a/app/graphql/types/packages/package_links_type.rb b/app/graphql/types/packages/package_links_type.rb new file mode 100644 index 00000000000..f16937530b9 --- /dev/null +++ b/app/graphql/types/packages/package_links_type.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Types + module Packages + class PackageLinksType < BaseObject + graphql_name 'PackageLinks' + description 'Represents links to perform actions on the package' + authorize :read_package + + include ::Routing::PackagesHelper + + field :web_path, GraphQL::Types::String, null: true, description: 'Path to the package details page.' + + def web_path + package_path(object) + end + end + end +end diff --git a/app/graphql/types/permission_types/ci/runner.rb b/app/graphql/types/permission_types/ci/runner.rb index 2e92a4011e9..096dcd272cc 100644 --- a/app/graphql/types/permission_types/ci/runner.rb +++ b/app/graphql/types/permission_types/ci/runner.rb @@ -6,7 +6,7 @@ module Types class Runner < BasePermissionType graphql_name 'RunnerPermissions' - abilities :read_runner, :update_runner, :delete_runner + abilities :read_runner, :update_runner, :delete_runner, :assign_runner end end end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index a41af34ef4c..771dad00fb3 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -37,6 +37,10 @@ module Types null: false, description: 'Path of the project.' + field :incident_management_timeline_event_tags, [Types::IncidentManagement::TimelineEventTagType], + null: true, + description: 'Timeline event tags for the project.' + field :sast_ci_configuration, Types::CiConfiguration::Sast::Type, null: true, calls_gitaly: true, @@ -226,8 +230,7 @@ module Types Types::IssueType.connection_type, null: true, description: 'Issues of the project.', - extras: [:lookahead], - resolver: Resolvers::IssuesResolver + resolver: Resolvers::ProjectIssuesResolver field :work_items, Types::WorkItemType.connection_type, @@ -241,7 +244,6 @@ module Types Types::IssueStatusCountsType, null: true, description: 'Counts of issues by status for the project.', - extras: [:lookahead], resolver: Resolvers::IssueStatusCountsResolver field :milestones, Types::MilestoneType.connection_type, @@ -275,7 +277,7 @@ module Types Types::IssueType, null: true, description: 'A single issue of the project.', - resolver: Resolvers::IssuesResolver.single + resolver: Resolvers::ProjectIssuesResolver.single field :packages, description: 'Packages of the project.', @@ -513,9 +515,7 @@ module Types field :work_item_types, Types::WorkItems::TypeType.connection_type, resolver: Resolvers::WorkItems::TypesResolver, - description: 'Work item types available to the project.' \ - ' Returns `null` if `work_items` feature flag is disabled.' \ - ' This flag is disabled by default, because the feature is experimental and is subject to change without notice.' + description: 'Work item types available to the project.' field :timelog_categories, Types::TimeTracking::TimelogCategoryType.connection_type, null: true, @@ -532,6 +532,11 @@ module Types description: "Branch rules configured for the project.", resolver: Resolvers::Projects::BranchRulesResolver + field :languages, [Types::Projects::RepositoryLanguageType], + null: true, + description: "Programming languages used in the project.", + calls_gitaly: true + def timelog_categories object.project_namespace.timelog_categories if Feature.enabled?(:timelog_categories) end @@ -598,7 +603,7 @@ module Types end def sast_ci_configuration - return unless Ability.allowed?(current_user, :download_code, object) + return unless Ability.allowed?(current_user, :read_code, object) ::Security::CiConfiguration::SastParserService.new(object).configuration end @@ -609,6 +614,10 @@ module Types object.service_desk_address end + def languages + ::Projects::RepositoryLanguagesService.new(project, current_user).execute + end + private def project diff --git a/app/graphql/types/projects/branch_rule_type.rb b/app/graphql/types/projects/branch_rule_type.rb index e7632c17cca..1afd2cc3fef 100644 --- a/app/graphql/types/projects/branch_rule_type.rb +++ b/app/graphql/types/projects/branch_rule_type.rb @@ -8,6 +8,8 @@ module Types accepts ::ProtectedBranch authorize :read_protected_branch + alias_method :branch_rule, :object + field :name, type: GraphQL::Types::String, null: false, @@ -20,6 +22,12 @@ module Types calls_gitaly: true, description: "Check if this branch rule protects the project's default branch." + field :matching_branches_count, + type: GraphQL::Types::Int, + null: false, + calls_gitaly: true, + description: 'Number of existing branches that match this branch rule.' + field :branch_protection, type: Types::BranchRules::BranchProtectionType, null: false, @@ -35,6 +43,10 @@ module Types Types::TimeType, null: false, description: 'Timestamp of when the branch rule was last updated.' + + def matching_branches_count + branch_rule.matching(branch_rule.project.repository.branch_names).count + end end end end diff --git a/app/graphql/types/projects/repository_language_type.rb b/app/graphql/types/projects/repository_language_type.rb new file mode 100644 index 00000000000..76c645c0e85 --- /dev/null +++ b/app/graphql/types/projects/repository_language_type.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Types + module Projects + # rubocop: disable Graphql/AuthorizeTypes + class RepositoryLanguageType < BaseObject + graphql_name 'RepositoryLanguage' + + field :name, GraphQL::Types::String, null: false, + description: 'Name of the repository language.' + + field :share, GraphQL::Types::Float, null: true, + description: "Percentage of the repository's languages." + + field :color, Types::ColorType, null: true, + description: 'Color to visualize the repository language.' + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 1b39f43659e..21cb3f9e06c 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -82,6 +82,13 @@ module Types field :echo, resolver: Resolvers::EchoResolver + field :issues, + null: true, + alpha: { milestone: '15.6' }, + resolver: Resolvers::IssuesResolver, + description: 'Issues visible by the current user.' \ + ' Returns null if the `root_level_issues_query` feature flag is disabled.' + field :issue, Types::IssueType, null: true, description: 'Find an issue.' do @@ -92,7 +99,7 @@ module Types null: true, resolver: Resolvers::WorkItemResolver, alpha: { milestone: '15.1' }, - description: 'Find a work item. Returns `null` if `work_items` feature flag is disabled.' + description: 'Find a work item.' field :merge_request, Types::MergeRequestType, null: true, diff --git a/app/graphql/types/release_links_type.rb b/app/graphql/types/release_links_type.rb index 6bc767152e8..2258adc131c 100644 --- a/app/graphql/types/release_links_type.rb +++ b/app/graphql/types/release_links_type.rb @@ -14,12 +14,12 @@ module Types GraphQL::Types::String, null: true, description: 'HTTP URL of the issues page, filtered by this release and `state=closed`.', - authorize: :download_code + authorize: :read_code field :closed_merge_requests_url, GraphQL::Types::String, null: true, description: 'HTTP URL of the merge request page , filtered by this release and `state=closed`.', - authorize: :download_code + authorize: :read_code field :edit_url, GraphQL::Types::String, null: true, description: "HTTP URL of the release's edit page.", authorize: :update_release @@ -27,17 +27,17 @@ module Types GraphQL::Types::String, null: true, description: 'HTTP URL of the merge request page , filtered by this release and `state=merged`.', - authorize: :download_code + authorize: :read_code field :opened_issues_url, GraphQL::Types::String, null: true, description: 'HTTP URL of the issues page, filtered by this release and `state=open`.', - authorize: :download_code + authorize: :read_code field :opened_merge_requests_url, GraphQL::Types::String, null: true, description: 'HTTP URL of the merge request page, filtered by this release and `state=open`.', - authorize: :download_code + authorize: :read_code field :self_url, GraphQL::Types::String, null: true, description: 'HTTP URL of the release.' end diff --git a/app/graphql/types/release_source_type.rb b/app/graphql/types/release_source_type.rb index e05a2926ac1..e1959738c4b 100644 --- a/app/graphql/types/release_source_type.rb +++ b/app/graphql/types/release_source_type.rb @@ -5,7 +5,7 @@ module Types graphql_name 'ReleaseSource' description 'Represents the source code attached to a release in a particular format' - authorize :download_code + authorize :read_code field :format, GraphQL::Types::String, null: true, description: 'Format of the source.' diff --git a/app/graphql/types/release_type.rb b/app/graphql/types/release_type.rb index d70fe05c906..a20e53ad1bd 100644 --- a/app/graphql/types/release_type.rb +++ b/app/graphql/types/release_type.rb @@ -39,7 +39,7 @@ module Types description: 'Name of the tag associated with the release.' field :tag_path, GraphQL::Types::String, null: true, description: 'Relative web path to the tag associated with the release.', - authorize: :download_code + authorize: :read_code field :upcoming_release, GraphQL::Types::Boolean, null: true, method: :upcoming_release?, description: 'Indicates the release is an upcoming release.' field :historical_release, GraphQL::Types::Boolean, null: true, method: :historical_release?, diff --git a/app/graphql/types/repository_type.rb b/app/graphql/types/repository_type.rb index ba94f59ab6c..ab5d1bd8c9e 100644 --- a/app/graphql/types/repository_type.rb +++ b/app/graphql/types/repository_type.rb @@ -4,7 +4,7 @@ module Types class RepositoryType < BaseObject graphql_name 'Repository' - authorize :download_code + authorize :read_code field :blobs, Types::Repository::BlobType.connection_type, null: true, resolver: Resolvers::BlobsResolver, calls_gitaly: true, description: 'Blobs contained within the repository' diff --git a/app/graphql/types/subscription_type.rb b/app/graphql/types/subscription_type.rb index 3b8f5c64beb..9d5edec82b2 100644 --- a/app/graphql/types/subscription_type.rb +++ b/app/graphql/types/subscription_type.rb @@ -22,6 +22,9 @@ module Types field :issuable_dates_updated, subscription: Subscriptions::IssuableUpdated, null: true, description: 'Triggered when the due date or start date of an issuable is updated.' + field :issuable_milestone_updated, subscription: Subscriptions::IssuableUpdated, null: true, + description: 'Triggered when the milestone of an issuable is updated.' + field :merge_request_reviewers_updated, subscription: Subscriptions::IssuableUpdated, null: true, diff --git a/app/graphql/types/work_items/widget_interface.rb b/app/graphql/types/work_items/widget_interface.rb index a3943361114..b85d0a23535 100644 --- a/app/graphql/types/work_items/widget_interface.rb +++ b/app/graphql/types/work_items/widget_interface.rb @@ -16,7 +16,8 @@ module Types ::Types::WorkItems::Widgets::HierarchyType, ::Types::WorkItems::Widgets::LabelsType, ::Types::WorkItems::Widgets::AssigneesType, - ::Types::WorkItems::Widgets::StartAndDueDateType + ::Types::WorkItems::Widgets::StartAndDueDateType, + ::Types::WorkItems::Widgets::MilestoneType ].freeze def self.ce_orphan_types @@ -38,6 +39,8 @@ module Types ::Types::WorkItems::Widgets::LabelsType when ::WorkItems::Widgets::StartAndDueDate ::Types::WorkItems::Widgets::StartAndDueDateType + when ::WorkItems::Widgets::Milestone + ::Types::WorkItems::Widgets::MilestoneType else raise "Unknown GraphQL type for widget #{object}" end diff --git a/app/graphql/types/work_items/widgets/milestone_input_type.rb b/app/graphql/types/work_items/widgets/milestone_input_type.rb new file mode 100644 index 00000000000..996c782373f --- /dev/null +++ b/app/graphql/types/work_items/widgets/milestone_input_type.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Types + module WorkItems + module Widgets + class MilestoneInputType < BaseInputObject + graphql_name 'WorkItemWidgetMilestoneInput' + + argument :milestone_id, + Types::GlobalIDType[::Milestone], + required: :nullable, + prepare: ->(id, _) { id.model_id unless id.nil? }, + description: 'Milestone to assign to the work item.' + end + end + end +end diff --git a/app/graphql/types/work_items/widgets/milestone_type.rb b/app/graphql/types/work_items/widgets/milestone_type.rb new file mode 100644 index 00000000000..73318e58a00 --- /dev/null +++ b/app/graphql/types/work_items/widgets/milestone_type.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Types + module WorkItems + module Widgets + # Disabling widget level authorization as it might be too granular + # and we already authorize the parent work item + # rubocop:disable Graphql/AuthorizeTypes + class MilestoneType < BaseObject + graphql_name 'WorkItemWidgetMilestone' + description 'Represents a milestone widget' + + implements Types::WorkItems::WidgetInterface + + field :milestone, + ::Types::MilestoneType, + null: true, + description: 'Milestone of the work item.' + end + # rubocop:enable Graphql/AuthorizeTypes + end + end +end diff --git a/app/graphql/types/x509_certificate_type.rb b/app/graphql/types/x509_certificate_type.rb new file mode 100644 index 00000000000..806aa441af7 --- /dev/null +++ b/app/graphql/types/x509_certificate_type.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +# rubocop:disable Graphql/AuthorizeTypes + +module Types + class X509CertificateType < Types::BaseObject + graphql_name 'X509Certificate' + description 'Represents an X.509 certificate.' + + field :certificate_status, GraphQL::Types::String, + null: false, + description: 'Indicates if the certificate is good or revoked.' + + field :created_at, Types::TimeType, null: false, + description: 'Timestamp of when the certificate was saved.' + + field :email, GraphQL::Types::String, null: false, + description: 'Email associated with the cerificate.' + + field :id, GraphQL::Types::ID, null: false, description: 'ID of the certificate.' + + field :serial_number, GraphQL::Types::String, null: false, + description: 'Serial number of the certificate.' + + field :subject, GraphQL::Types::String, null: false, description: 'Subject of the certificate.' + + field :subject_key_identifier, GraphQL::Types::String, + null: false, + description: 'Subject key identifier of the certificate.' + + field :updated_at, Types::TimeType, null: false, + description: 'Timestamp of when the certificate was last updated.' + + field :x509_issuer, Types::X509IssuerType, null: false, + description: 'Issuer of the certificate.' + end +end + +# rubocop:enable Graphql/AuthorizeTypes diff --git a/app/graphql/types/x509_issuer_type.rb b/app/graphql/types/x509_issuer_type.rb new file mode 100644 index 00000000000..a5759e48ee0 --- /dev/null +++ b/app/graphql/types/x509_issuer_type.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# rubocop:disable Graphql/AuthorizeTypes + +module Types + class X509IssuerType < Types::BaseObject + graphql_name 'X509Issuer' + description 'Issuer of an X.509 certificate.' + + field :created_at, Types::TimeType, null: true, + description: 'Timestamp of when the issuer was created.' + + field :crl_url, GraphQL::Types::String, null: true, + description: 'Certificate revokation list of the issuer.' + + field :id, GraphQL::Types::ID, null: true, description: 'ID of the issuer.' + + field :subject, GraphQL::Types::String, null: true, description: 'Subject of the issuer.' + + field :subject_key_identifier, GraphQL::Types::String, + null: true, + description: 'Subject key identifier of the issuer.' + + field :updated_at, Types::TimeType, null: true, + description: 'Timestamp of when the issuer was last updated.' + end +end + +# rubocop:enable Graphql/AuthorizeTypes |