diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-19 01:45:44 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-19 01:45:44 +0000 |
commit | 85dc423f7090da0a52c73eb66faf22ddb20efff9 (patch) | |
tree | 9160f299afd8c80c038f08e1545be119f5e3f1e1 /app/graphql | |
parent | 15c2c8c66dbe422588e5411eee7e68f1fa440bb8 (diff) | |
download | gitlab-ce-85dc423f7090da0a52c73eb66faf22ddb20efff9.tar.gz |
Add latest changes from gitlab-org/gitlab@13-4-stable-ee
Diffstat (limited to 'app/graphql')
84 files changed, 778 insertions, 133 deletions
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb index d8967da9f57..2f5043f9ffa 100644 --- a/app/graphql/gitlab_schema.rb +++ b/app/graphql/gitlab_schema.rb @@ -116,11 +116,11 @@ class GitlabSchema < GraphQL::Schema expected_type = ctx[:expected_type] gid = GlobalID.parse(global_id) - raise Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab id." unless gid + raise Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab ID." unless gid if expected_type && !gid.model_class.ancestors.include?(expected_type) vars = { global_id: global_id, expected_type: expected_type } - msg = _('%{global_id} is not a valid id for %{expected_type}.') % vars + msg = _('%{global_id} is not a valid ID for %{expected_type}.') % vars raise Gitlab::Graphql::Errors::ArgumentError, msg end diff --git a/app/graphql/mutations/alert_management/alerts/set_assignees.rb b/app/graphql/mutations/alert_management/alerts/set_assignees.rb index 1e0c9fdeeaf..517c20a85d0 100644 --- a/app/graphql/mutations/alert_management/alerts/set_assignees.rb +++ b/app/graphql/mutations/alert_management/alerts/set_assignees.rb @@ -20,6 +20,8 @@ module Mutations alert = authorized_find!(project_path: args[:project_path], iid: args[:iid]) result = set_assignees(alert, args[:assignee_usernames], args[:operation_mode]) + track_usage_event(:incident_management_alert_assigned, current_user.id) + prepare_response(result) end diff --git a/app/graphql/mutations/alert_management/alerts/todo/create.rb b/app/graphql/mutations/alert_management/alerts/todo/create.rb index 3dba96e43f1..2a1056e8f64 100644 --- a/app/graphql/mutations/alert_management/alerts/todo/create.rb +++ b/app/graphql/mutations/alert_management/alerts/todo/create.rb @@ -11,6 +11,8 @@ module Mutations alert = authorized_find!(project_path: args[:project_path], iid: args[:iid]) result = ::AlertManagement::Alerts::Todo::CreateService.new(alert, current_user).execute + track_usage_event(:incident_management_alert_todo, current_user.id) + prepare_response(result) end diff --git a/app/graphql/mutations/alert_management/base.rb b/app/graphql/mutations/alert_management/base.rb index 0de4b9409e4..0ccfcf34180 100644 --- a/app/graphql/mutations/alert_management/base.rb +++ b/app/graphql/mutations/alert_management/base.rb @@ -3,6 +3,7 @@ module Mutations module AlertManagement class Base < BaseMutation + include Gitlab::Utils::UsageData include ResolvesProject argument :project_path, GraphQL::ID_TYPE, diff --git a/app/graphql/mutations/alert_management/create_alert_issue.rb b/app/graphql/mutations/alert_management/create_alert_issue.rb index adb048a4479..2ddb94700c2 100644 --- a/app/graphql/mutations/alert_management/create_alert_issue.rb +++ b/app/graphql/mutations/alert_management/create_alert_issue.rb @@ -9,6 +9,8 @@ module Mutations alert = authorized_find!(project_path: args[:project_path], iid: args[:iid]) result = create_alert_issue(alert, current_user) + track_usage_event(:incident_management_incident_created, current_user.id) + prepare_response(alert, result) end diff --git a/app/graphql/mutations/alert_management/update_alert_status.rb b/app/graphql/mutations/alert_management/update_alert_status.rb index ed61555fbd6..1e14bae048a 100644 --- a/app/graphql/mutations/alert_management/update_alert_status.rb +++ b/app/graphql/mutations/alert_management/update_alert_status.rb @@ -13,6 +13,8 @@ module Mutations alert = authorized_find!(project_path: args[:project_path], iid: args[:iid]) result = update_status(alert, args[:status]) + track_usage_event(:incident_management_alert_status_changed, current_user.id) + prepare_response(result) end diff --git a/app/graphql/mutations/award_emojis/toggle.rb b/app/graphql/mutations/award_emojis/toggle.rb index a7714e695d2..679ec7a14ff 100644 --- a/app/graphql/mutations/award_emojis/toggle.rb +++ b/app/graphql/mutations/award_emojis/toggle.rb @@ -5,7 +5,7 @@ module Mutations class Toggle < Base graphql_name 'AwardEmojiToggle' - field :toggledOn, GraphQL::BOOLEAN_TYPE, null: false, + field :toggled_on, GraphQL::BOOLEAN_TYPE, null: false, description: 'Indicates the status of the emoji. ' \ 'True if the toggle awarded the emoji, and false if the toggle removed the emoji.' diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb index 68e7853a9b1..577f10545b3 100644 --- a/app/graphql/mutations/base_mutation.rb +++ b/app/graphql/mutations/base_mutation.rb @@ -17,6 +17,10 @@ module Mutations context[:current_user] end + def api_user? + context[:is_sessionless_user] + end + # Returns Array of errors on an ActiveRecord object def errors_on_object(record) record.errors.full_messages diff --git a/app/graphql/mutations/boards/destroy.rb b/app/graphql/mutations/boards/destroy.rb new file mode 100644 index 00000000000..7c381113d38 --- /dev/null +++ b/app/graphql/mutations/boards/destroy.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Mutations + module Boards + class Destroy < ::Mutations::BaseMutation + graphql_name 'DestroyBoard' + + field :board, + Types::BoardType, + null: true, + description: 'The board after mutation' + argument :id, + ::Types::GlobalIDType[::Board], + required: true, + description: 'The global ID of the board to destroy' + + authorize :admin_board + + def resolve(id:) + board = authorized_find!(id: id) + + response = ::Boards::DestroyService.new(board.resource_parent, current_user).execute(board) + + { + board: response.success? ? nil : board, + errors: response.errors + } + end + + private + + def find_object(id:) + GitlabSchema.object_from_id(id, expected_type: ::Board) + end + end + end +end diff --git a/app/graphql/mutations/boards/issues/issue_move_list.rb b/app/graphql/mutations/boards/issues/issue_move_list.rb index d4bf47af4cf..813b6d3cb2a 100644 --- a/app/graphql/mutations/boards/issues/issue_move_list.rb +++ b/app/graphql/mutations/boards/issues/issue_move_list.rb @@ -29,11 +29,11 @@ module Mutations argument :move_before_id, GraphQL::ID_TYPE, required: false, - description: 'ID of issue before which the current issue will be positioned at' + description: 'ID of issue that should be placed before the current issue' argument :move_after_id, GraphQL::ID_TYPE, required: false, - description: 'ID of issue after which the current issue will be positioned at' + description: 'ID of issue that should be placed after the current issue' def ready?(**args) if move_arguments(args).blank? @@ -50,6 +50,8 @@ module Mutations end def resolve(board:, **args) + Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/-/issues/247861') + raise_resource_not_available_error! unless board authorize_board!(board) @@ -89,3 +91,5 @@ module Mutations end end end + +Mutations::Boards::Issues::IssueMoveList.prepend_if_ee('EE::Mutations::Boards::Issues::IssueMoveList') diff --git a/app/graphql/mutations/boards/lists/base.rb b/app/graphql/mutations/boards/lists/base.rb index 34b271ba3b8..d244d6bf8dd 100644 --- a/app/graphql/mutations/boards/lists/base.rb +++ b/app/graphql/mutations/boards/lists/base.rb @@ -8,7 +8,7 @@ module Mutations argument :board_id, ::Types::GlobalIDType[::Board], required: true, - description: 'The Global ID of the issue board to mutate' + description: 'Global ID of the issue board to mutate' field :list, Types::BoardListType, diff --git a/app/graphql/mutations/boards/lists/create.rb b/app/graphql/mutations/boards/lists/create.rb index 4f545709ee9..3fe1052315f 100644 --- a/app/graphql/mutations/boards/lists/create.rb +++ b/app/graphql/mutations/boards/lists/create.rb @@ -12,7 +12,7 @@ module Mutations argument :label_id, ::Types::GlobalIDType[::Label], required: false, - description: 'ID of an existing label' + description: 'Global ID of an existing label' def ready?(**args) if args.slice(*mutually_exclusive_args).size != 1 @@ -39,6 +39,7 @@ module Mutations private + # Overridden in EE def authorize_list_type_resource!(board, params) return unless params[:label_id] @@ -57,13 +58,15 @@ module Mutations create_list_service.execute(board) end + # Overridden in EE def create_list_params(args) params = args.slice(*mutually_exclusive_args).with_indifferent_access - params[:label_id] = GitlabSchema.parse_gid(params[:label_id]).model_id if params[:label_id] + params[:label_id] &&= ::GitlabSchema.parse_gid(params[:label_id], expected_type: ::Label).model_id params end + # Overridden in EE def mutually_exclusive_args [:backlog, :label_id] end @@ -71,3 +74,5 @@ module Mutations end end end + +Mutations::Boards::Lists::Create.prepend_if_ee('::EE::Mutations::Boards::Lists::Create') diff --git a/app/graphql/mutations/ci/base.rb b/app/graphql/mutations/ci/base.rb new file mode 100644 index 00000000000..09df4487a50 --- /dev/null +++ b/app/graphql/mutations/ci/base.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Mutations + module Ci + class Base < BaseMutation + argument :id, ::Types::GlobalIDType[::Ci::Pipeline], + required: true, + description: 'The id of the pipeline to mutate' + + private + + def find_object(id:) + GlobalID::Locator.locate(id) + end + end + end +end diff --git a/app/graphql/mutations/ci/pipeline_cancel.rb b/app/graphql/mutations/ci/pipeline_cancel.rb new file mode 100644 index 00000000000..bc881e2ac02 --- /dev/null +++ b/app/graphql/mutations/ci/pipeline_cancel.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Mutations + module Ci + class PipelineCancel < Base + graphql_name 'PipelineCancel' + + authorize :update_pipeline + + def resolve(id:) + pipeline = authorized_find!(id: id) + + if pipeline.cancelable? + pipeline.cancel_running + { success: true, errors: [] } + else + { success: false, errors: ['Pipeline is not cancelable'] } + end + end + end + end +end diff --git a/app/graphql/mutations/ci/pipeline_destroy.rb b/app/graphql/mutations/ci/pipeline_destroy.rb new file mode 100644 index 00000000000..bb24d416583 --- /dev/null +++ b/app/graphql/mutations/ci/pipeline_destroy.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Mutations + module Ci + class PipelineDestroy < Base + graphql_name 'PipelineDestroy' + + authorize :destroy_pipeline + + def resolve(id:) + pipeline = authorized_find!(id: id) + project = pipeline.project + + result = ::Ci::DestroyPipelineService.new(project, current_user).execute(pipeline) + { + success: result.success?, + errors: result.errors + } + end + end + end +end diff --git a/app/graphql/mutations/ci/pipeline_retry.rb b/app/graphql/mutations/ci/pipeline_retry.rb new file mode 100644 index 00000000000..0669bfc449c --- /dev/null +++ b/app/graphql/mutations/ci/pipeline_retry.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Mutations + module Ci + class PipelineRetry < Base + graphql_name 'PipelineRetry' + + field :pipeline, + Types::Ci::PipelineType, + null: true, + description: 'The pipeline after mutation' + + authorize :update_pipeline + + def resolve(id:) + pipeline = authorized_find!(id: id) + project = pipeline.project + + ::Ci::RetryPipelineService.new(project, current_user).execute(pipeline) + { + pipeline: pipeline, + errors: errors_on_object(pipeline) + } + end + end + end +end diff --git a/app/graphql/mutations/concerns/mutations/authorizes_project.rb b/app/graphql/mutations/concerns/mutations/authorizes_project.rb new file mode 100644 index 00000000000..87341525d6c --- /dev/null +++ b/app/graphql/mutations/concerns/mutations/authorizes_project.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Mutations + module AuthorizesProject + include ResolvesProject + + def authorized_find_project!(full_path:) + authorized_find!(full_path: full_path) + end + + private + + def find_object(full_path:) + resolve_project(full_path: full_path) + end + end +end diff --git a/app/graphql/mutations/design_management/move.rb b/app/graphql/mutations/design_management/move.rb index 0b654447844..6126af8b68b 100644 --- a/app/graphql/mutations/design_management/move.rb +++ b/app/graphql/mutations/design_management/move.rb @@ -20,10 +20,6 @@ module Mutations null: true, description: "The current state of the collection" - def ready(*) - raise ::Gitlab::Graphql::Errors::ResourceNotAvailable unless ::Feature.enabled?(:reorder_designs, default_enabled: true) - end - def resolve(**args) service = ::DesignManagement::MoveDesignsService.new(current_user, parameters(args)) diff --git a/app/graphql/mutations/issues/set_severity.rb b/app/graphql/mutations/issues/set_severity.rb new file mode 100644 index 00000000000..bc386e07178 --- /dev/null +++ b/app/graphql/mutations/issues/set_severity.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Mutations + module Issues + class SetSeverity < Base + graphql_name 'IssueSetSeverity' + + argument :severity, Types::IssuableSeverityEnum, required: true, + description: 'Set the incident severity level.' + + def resolve(project_path:, iid:, severity:) + issue = authorized_find!(project_path: project_path, iid: iid) + project = issue.project + + ::Issues::UpdateService.new(project, current_user, severity: severity) + .execute(issue) + + { + issue: issue, + errors: errors_on_object(issue) + } + end + end + end +end diff --git a/app/graphql/mutations/merge_requests/set_wip.rb b/app/graphql/mutations/merge_requests/set_wip.rb index 5d2077c12f2..0b5c20de377 100644 --- a/app/graphql/mutations/merge_requests/set_wip.rb +++ b/app/graphql/mutations/merge_requests/set_wip.rb @@ -9,8 +9,8 @@ module Mutations GraphQL::BOOLEAN_TYPE, required: true, description: <<~DESC - Whether or not to set the merge request as a WIP. - DESC + Whether or not to set the merge request as a WIP. + DESC def resolve(project_path:, iid:, wip: nil) merge_request = authorized_find!(project_path: project_path, iid: iid) diff --git a/app/graphql/mutations/metrics/dashboard/annotations/delete.rb b/app/graphql/mutations/metrics/dashboard/annotations/delete.rb index fb828ba0e2f..6e183e78d9b 100644 --- a/app/graphql/mutations/metrics/dashboard/annotations/delete.rb +++ b/app/graphql/mutations/metrics/dashboard/annotations/delete.rb @@ -12,7 +12,7 @@ module Mutations argument :id, GraphQL::ID_TYPE, required: true, - description: 'The global id of the annotation to delete' + description: 'The global ID of the annotation to delete' def resolve(id:) annotation = authorized_find!(id: id) diff --git a/app/graphql/mutations/snippets/create.rb b/app/graphql/mutations/snippets/create.rb index a068fd806f5..a8aeb15afcd 100644 --- a/app/graphql/mutations/snippets/create.rb +++ b/app/graphql/mutations/snippets/create.rb @@ -16,14 +16,6 @@ module Mutations required: true, description: 'Title of the snippet' - argument :file_name, GraphQL::STRING_TYPE, - required: false, - description: 'File name of the snippet' - - argument :content, GraphQL::STRING_TYPE, - required: false, - description: 'Content of the snippet' - argument :description, GraphQL::STRING_TYPE, required: false, description: 'Description of the snippet' @@ -59,6 +51,11 @@ module Mutations snippet = service_response.payload[:snippet] + # Only when the user is not an api user and the operation was successful + if !api_user? && service_response.success? + ::Gitlab::UsageDataCounters::EditorUniqueCounter.track_snippet_editor_edit_action(author: current_user) + end + { snippet: service_response.success? ? snippet : nil, errors: errors_on_object(snippet) diff --git a/app/graphql/mutations/snippets/update.rb b/app/graphql/mutations/snippets/update.rb index 6ff632ec008..d0db5fa2eb9 100644 --- a/app/graphql/mutations/snippets/update.rb +++ b/app/graphql/mutations/snippets/update.rb @@ -14,14 +14,6 @@ module Mutations required: false, description: 'Title of the snippet' - argument :file_name, GraphQL::STRING_TYPE, - required: false, - description: 'File name of the snippet' - - argument :content, GraphQL::STRING_TYPE, - required: false, - description: 'Content of the snippet' - argument :description, GraphQL::STRING_TYPE, required: false, description: 'Description of the snippet' @@ -42,6 +34,11 @@ module Mutations update_params(args)).execute(snippet) snippet = result.payload[:snippet] + # Only when the user is not an api user and the operation was successful + if !api_user? && result.success? + ::Gitlab::UsageDataCounters::EditorUniqueCounter.track_snippet_editor_edit_action(author: current_user) + end + { snippet: result.success? ? snippet : snippet.reset, errors: errors_on_object(snippet) diff --git a/app/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver.rb b/app/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver.rb new file mode 100644 index 00000000000..aea3afa8ec5 --- /dev/null +++ b/app/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Resolvers + module Admin + module Analytics + module InstanceStatistics + class MeasurementsResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + type Types::Admin::Analytics::InstanceStatistics::MeasurementType, null: true + + argument :identifier, Types::Admin::Analytics::InstanceStatistics::MeasurementIdentifierEnum, + required: true, + description: 'The type of measurement/statistics to retrieve' + + def resolve(identifier:) + authorize! + + ::Analytics::InstanceStatistics::Measurement + .with_identifier(identifier) + .order_by_latest + end + + private + + def authorize! + admin? || raise_resource_not_available_error! + end + + def admin? + context[:current_user].present? && context[:current_user].admin? + end + end + end + end + end +end diff --git a/app/graphql/resolvers/board_list_issues_resolver.rb b/app/graphql/resolvers/board_list_issues_resolver.rb index a7cc367379d..dba9f99edeb 100644 --- a/app/graphql/resolvers/board_list_issues_resolver.rb +++ b/app/graphql/resolvers/board_list_issues_resolver.rb @@ -2,12 +2,20 @@ module Resolvers class BoardListIssuesResolver < BaseResolver + include BoardIssueFilterable + + argument :filters, Types::Boards::BoardIssueInputType, + required: false, + description: 'Filters applied when selecting issues in the board list' + type Types::IssueType, null: true alias_method :list, :object def resolve(**args) - service = Boards::Issues::ListService.new(list.board.resource_parent, context[:current_user], { board_id: list.board.id, id: list.id }) + filter_params = issue_filters(args[:filters]).merge(board_id: list.board.id, id: list.id) + service = Boards::Issues::ListService.new(list.board.resource_parent, context[:current_user], filter_params) + Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection.new(service.execute) end diff --git a/app/graphql/resolvers/concerns/board_issue_filterable.rb b/app/graphql/resolvers/concerns/board_issue_filterable.rb new file mode 100644 index 00000000000..1541738f46c --- /dev/null +++ b/app/graphql/resolvers/concerns/board_issue_filterable.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module BoardIssueFilterable + extend ActiveSupport::Concern + + private + + def issue_filters(args) + filters = args.to_h + set_filter_values(filters) + + if filters[:not] + filters[:not] = filters[:not].to_h + set_filter_values(filters[:not]) + end + + filters + end + + def set_filter_values(filters) + end +end + +::BoardIssueFilterable.prepend_if_ee('::EE::Resolvers::BoardIssueFilterable') diff --git a/app/graphql/resolvers/concerns/issue_resolver_fields.rb b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb index bf2f510dd89..2b14d8275d1 100644 --- a/app/graphql/resolvers/concerns/issue_resolver_fields.rb +++ b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true -module IssueResolverFields +module IssueResolverArguments extend ActiveSupport::Concern prepended do + include LooksAhead + argument :iid, GraphQL::STRING_TYPE, required: false, description: 'IID of the issue. For example, "1"' @@ -49,7 +51,7 @@ module IssueResolverFields required: false end - def resolve(**args) + def resolve_with_lookahead(**args) # 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. diff --git a/app/graphql/resolvers/concerns/looks_ahead.rb b/app/graphql/resolvers/concerns/looks_ahead.rb index becc6debd33..e7230287e13 100644 --- a/app/graphql/resolvers/concerns/looks_ahead.rb +++ b/app/graphql/resolvers/concerns/looks_ahead.rb @@ -46,7 +46,7 @@ module LooksAhead if lookahead.selects?(:nodes) lookahead.selection(:nodes) elsif lookahead.selects?(:edges) - lookahead.selection(:edges).selection(:nodes) + lookahead.selection(:edges).selection(:node) end end end diff --git a/app/graphql/resolvers/group_members_resolver.rb b/app/graphql/resolvers/group_members_resolver.rb new file mode 100644 index 00000000000..f34c873a8a9 --- /dev/null +++ b/app/graphql/resolvers/group_members_resolver.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Resolvers + class GroupMembersResolver < MembersResolver + authorize :read_group_member + + private + + def preloads + { + user: [:user, :source] + } + end + + def finder_class + GroupMembersFinder + end + end +end diff --git a/app/graphql/resolvers/issue_status_counts_resolver.rb b/app/graphql/resolvers/issue_status_counts_resolver.rb index 0b26b9def54..5d0d5693244 100644 --- a/app/graphql/resolvers/issue_status_counts_resolver.rb +++ b/app/graphql/resolvers/issue_status_counts_resolver.rb @@ -2,26 +2,13 @@ module Resolvers class IssueStatusCountsResolver < BaseResolver - prepend IssueResolverFields + prepend IssueResolverArguments type Types::IssueStatusCountsType, null: true def continue_issue_resolve(parent, finder, **args) - finder.params[parent_param(parent)] = parent if parent - Gitlab::IssuablesCountForState.new(finder, parent) - end - - private - - def parent_param(parent) - case parent - when Project - :project_id - when Group - :group_id - else - raise "Unexpected type of parent: #{parent.class}. Must be Project or Group" - end + finder.parent_param = parent + apply_lookahead(Gitlab::IssuablesCountForState.new(finder, parent)) end end end diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb index e2874f6643c..396ae02ae13 100644 --- a/app/graphql/resolvers/issues_resolver.rb +++ b/app/graphql/resolvers/issues_resolver.rb @@ -2,7 +2,7 @@ module Resolvers class IssuesResolver < BaseResolver - prepend IssueResolverFields + prepend IssueResolverArguments argument :state, Types::IssuableStateEnum, required: false, @@ -19,7 +19,7 @@ module Resolvers milestone_due_asc milestone_due_desc].freeze def continue_issue_resolve(parent, finder, **args) - issues = Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all + issues = apply_lookahead(Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all) if non_stable_cursor_sort?(args[:sort]) # Certain complex sorts are not supported by the stable cursor pagination yet. @@ -30,6 +30,16 @@ module Resolvers end end + private + + def preloads + { + alert_management_alert: [:alert_management_alert], + labels: [:labels], + assignees: [:assignees] + } + end + def non_stable_cursor_sort?(sort) NON_STABLE_CURSOR_SORTS.include?(sort) end diff --git a/app/graphql/resolvers/members_resolver.rb b/app/graphql/resolvers/members_resolver.rb new file mode 100644 index 00000000000..88a1ab71c45 --- /dev/null +++ b/app/graphql/resolvers/members_resolver.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Resolvers + class MembersResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + include LooksAhead + + argument :search, GraphQL::STRING_TYPE, + required: false, + description: 'Search query' + + def resolve_with_lookahead(**args) + authorize!(object) + + apply_lookahead(finder_class.new(object, current_user, params: args).execute) + end + + private + + def finder_class + # override in subclass + end + end +end diff --git a/app/graphql/resolvers/merge_request_pipelines_resolver.rb b/app/graphql/resolvers/merge_request_pipelines_resolver.rb index b371f1335f8..b95e46d9cff 100644 --- a/app/graphql/resolvers/merge_request_pipelines_resolver.rb +++ b/app/graphql/resolvers/merge_request_pipelines_resolver.rb @@ -7,6 +7,8 @@ module Resolvers alias_method :merge_request, :object def resolve(**args) + return unless project + resolve_pipelines(project, args) .merge(merge_request.all_pipelines) end diff --git a/app/graphql/resolvers/merge_requests_resolver.rb b/app/graphql/resolvers/merge_requests_resolver.rb index d15a1ede6fe..677f84e5795 100644 --- a/app/graphql/resolvers/merge_requests_resolver.rb +++ b/app/graphql/resolvers/merge_requests_resolver.rb @@ -29,11 +29,18 @@ module Resolvers as: :label_name, description: 'Array of label names. All resolved merge requests will have all of these labels.' argument :merged_after, Types::TimeType, - required: false, - description: 'Merge requests merged after this date' + required: false, + description: 'Merge requests merged after this date' argument :merged_before, Types::TimeType, - required: false, - description: 'Merge requests merged before this date' + required: false, + description: 'Merge requests merged before this date' + argument :milestone_title, GraphQL::STRING_TYPE, + required: false, + description: 'Title of the milestone' + argument :sort, Types::MergeRequestSortEnum, + description: 'Sort merge requests by this criteria', + required: false, + default_value: 'created_desc' def self.single ::Resolvers::MergeRequestResolver diff --git a/app/graphql/resolvers/metrics/dashboard_resolver.rb b/app/graphql/resolvers/metrics/dashboard_resolver.rb index 05d82ca0f46..18a654c7dc5 100644 --- a/app/graphql/resolvers/metrics/dashboard_resolver.rb +++ b/app/graphql/resolvers/metrics/dashboard_resolver.rb @@ -14,7 +14,8 @@ module Resolvers def resolve(**args) return unless environment - ::PerformanceMonitoring::PrometheusDashboard.find_for(project: environment.project, user: context[:current_user], path: args[:path], options: { environment: environment }) + ::PerformanceMonitoring::PrometheusDashboard + .find_for(project: environment.project, user: context[:current_user], path: args[:path], options: { environment: environment }) end end end diff --git a/app/graphql/resolvers/namespace_projects_resolver.rb b/app/graphql/resolvers/namespace_projects_resolver.rb index e841132eea7..c221cb9aed6 100644 --- a/app/graphql/resolvers/namespace_projects_resolver.rb +++ b/app/graphql/resolvers/namespace_projects_resolver.rb @@ -7,19 +7,33 @@ module Resolvers default_value: false, description: 'Include also subgroup projects' + argument :search, GraphQL::STRING_TYPE, + required: false, + default_value: nil, + description: 'Search project with most similar names or paths' + + argument :sort, Types::Projects::NamespaceProjectSortEnum, + required: false, + default_value: nil, + description: 'Sort projects by this criteria' + type Types::ProjectType, null: true - def resolve(include_subgroups:) + def resolve(include_subgroups:, sort:, search:) # The namespace could have been loaded in batch by `BatchLoader`. # At this point we need the `id` or the `full_path` of the namespace # to query for projects, so make sure it's loaded and not `nil` before continuing. namespace = object.respond_to?(:sync) ? object.sync : object return Project.none if namespace.nil? - if include_subgroups - namespace.all_projects.with_route + query = include_subgroups ? namespace.all_projects.with_route : namespace.projects.with_route + + return query unless search.present? + + if sort == :similarity + query.sorted_by_similarity_desc(search, include_in_select: true).merge(Project.search(search)) else - namespace.projects.with_route + query.merge(Project.search(search)) end end diff --git a/app/graphql/resolvers/project_members_resolver.rb b/app/graphql/resolvers/project_members_resolver.rb index 3846531762e..1ca4e81f397 100644 --- a/app/graphql/resolvers/project_members_resolver.rb +++ b/app/graphql/resolvers/project_members_resolver.rb @@ -1,21 +1,15 @@ # frozen_string_literal: true module Resolvers - class ProjectMembersResolver < BaseResolver - argument :search, GraphQL::STRING_TYPE, - required: false, - description: 'Search query' + class ProjectMembersResolver < MembersResolver + type Types::MemberInterface, null: true - type Types::ProjectMemberType, null: true + authorize :read_project_member - alias_method :project, :object - - def resolve(**args) - return Member.none unless project.present? + private + def finder_class MembersFinder - .new(project, context[:current_user], params: args) - .execute end end end diff --git a/app/graphql/resolvers/project_merge_requests_resolver.rb b/app/graphql/resolvers/project_merge_requests_resolver.rb new file mode 100644 index 00000000000..0526ccd315f --- /dev/null +++ b/app/graphql/resolvers/project_merge_requests_resolver.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Resolvers + class ProjectMergeRequestsResolver < MergeRequestsResolver + argument :assignee_username, GraphQL::STRING_TYPE, + required: false, + description: 'Username of the assignee' + argument :author_username, GraphQL::STRING_TYPE, + required: false, + description: 'Username of the author' + end +end diff --git a/app/graphql/resolvers/projects_resolver.rb b/app/graphql/resolvers/projects_resolver.rb index f75f591b381..3bbadf87a71 100644 --- a/app/graphql/resolvers/projects_resolver.rb +++ b/app/graphql/resolvers/projects_resolver.rb @@ -12,9 +12,13 @@ module Resolvers required: false, description: 'Search query for project name, path, or description' + argument :ids, [GraphQL::ID_TYPE], + required: false, + description: 'Filter projects by IDs' + def resolve(**args) ProjectsFinder - .new(current_user: current_user, params: project_finder_params(args)) + .new(current_user: current_user, params: project_finder_params(args), project_ids_relation: parse_gids(args[:ids])) .execute end @@ -27,5 +31,9 @@ module Resolvers search: params[:search] }.compact end + + def parse_gids(gids) + gids&.map { |gid| GitlabSchema.parse_gid(gid, expected_type: ::Project).model_id } + end end end diff --git a/app/graphql/resolvers/user_starred_projects_resolver.rb b/app/graphql/resolvers/user_starred_projects_resolver.rb new file mode 100644 index 00000000000..cc3bb90decf --- /dev/null +++ b/app/graphql/resolvers/user_starred_projects_resolver.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Resolvers + class UserStarredProjectsResolver < BaseResolver + type Types::ProjectType, null: true + + argument :search, GraphQL::STRING_TYPE, + required: false, + description: 'Search query' + + alias_method :user, :object + + def resolve(**args) + StarredProjectsFinder.new(user, params: args, current_user: current_user).execute + end + end +end diff --git a/app/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum.rb b/app/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum.rb new file mode 100644 index 00000000000..13c67442c2e --- /dev/null +++ b/app/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Types + module Admin + module Analytics + module InstanceStatistics + class MeasurementIdentifierEnum < BaseEnum + graphql_name 'MeasurementIdentifier' + description 'Possible identifier types for a measurement' + + value 'PROJECTS', 'Project count', value: :projects + value 'USERS', 'User count', value: :users + value 'ISSUES', 'Issue count', value: :issues + value 'MERGE_REQUESTS', 'Merge request count', value: :merge_requests + value 'GROUPS', 'Group count', value: :groups + value 'PIPELINES', 'Pipeline count', value: :pipelines + end + end + end + end +end diff --git a/app/graphql/types/admin/analytics/instance_statistics/measurement_type.rb b/app/graphql/types/admin/analytics/instance_statistics/measurement_type.rb new file mode 100644 index 00000000000..d45341077a4 --- /dev/null +++ b/app/graphql/types/admin/analytics/instance_statistics/measurement_type.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +# rubocop:disable Graphql/AuthorizeTypes + +module Types + module Admin + module Analytics + module InstanceStatistics + class MeasurementType < BaseObject + graphql_name 'InstanceStatisticsMeasurement' + description 'Represents a recorded measurement (object count) for the Admins' + + field :recorded_at, Types::TimeType, null: true, + description: 'The time the measurement was recorded' + + field :count, GraphQL::INT_TYPE, null: false, + description: 'Object count' + + field :identifier, Types::Admin::Analytics::InstanceStatistics::MeasurementIdentifierEnum, null: false, + description: 'The type of objects being measured' + end + end + end + end +end diff --git a/app/graphql/types/admin/sidekiq_queues/delete_jobs_response_type.rb b/app/graphql/types/admin/sidekiq_queues/delete_jobs_response_type.rb index 69af9d463bb..93dd49b3c38 100644 --- a/app/graphql/types/admin/sidekiq_queues/delete_jobs_response_type.rb +++ b/app/graphql/types/admin/sidekiq_queues/delete_jobs_response_type.rb @@ -7,7 +7,7 @@ module Types # a plain hash. class DeleteJobsResponseType < BaseObject # rubocop:disable Graphql/AuthorizeTypes graphql_name 'DeleteJobsResponse' - description 'The response from the AdminSidekiqQueuesDeleteJobs mutation.' + description 'The response from the AdminSidekiqQueuesDeleteJobs mutation' field :completed, GraphQL::BOOLEAN_TYPE, diff --git a/app/graphql/types/alert_management/alert_type.rb b/app/graphql/types/alert_management/alert_type.rb index 1a0b0685ffe..2da97030b88 100644 --- a/app/graphql/types/alert_management/alert_type.rb +++ b/app/graphql/types/alert_management/alert_type.rb @@ -6,6 +6,8 @@ module Types graphql_name 'AlertManagementAlert' description "Describes an alert from the project's Alert Management" + present_using ::AlertManagement::AlertPresenter + implements(Types::Notes::NoteableType) authorize :read_alert_management_alert diff --git a/app/graphql/types/award_emojis/award_emoji_type.rb b/app/graphql/types/award_emojis/award_emoji_type.rb index 0247ec767c8..fe7affa50cc 100644 --- a/app/graphql/types/award_emojis/award_emoji_type.rb +++ b/app/graphql/types/award_emojis/award_emoji_type.rb @@ -4,7 +4,7 @@ module Types module AwardEmojis class AwardEmojiType < BaseObject graphql_name 'AwardEmoji' - description 'An emoji awarded by a user.' + description 'An emoji awarded by a user' authorize :read_emoji diff --git a/app/graphql/types/base_enum.rb b/app/graphql/types/base_enum.rb index 94f6c47e876..159443641bc 100644 --- a/app/graphql/types/base_enum.rb +++ b/app/graphql/types/base_enum.rb @@ -2,9 +2,12 @@ module Types class BaseEnum < GraphQL::Schema::Enum + extend GitlabStyleDeprecations + class << self def value(*args, **kwargs, &block) enum[args[0].downcase] = kwargs[:value] || args[0] + kwargs = gitlab_deprecation(kwargs) super(*args, **kwargs, &block) end diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb index c8ec4b3f897..1e72a4cddf5 100644 --- a/app/graphql/types/base_field.rb +++ b/app/graphql/types/base_field.rb @@ -3,6 +3,7 @@ module Types class BaseField < GraphQL::Schema::Field prepend Gitlab::Graphql::Authorize + include GitlabStyleDeprecations DEFAULT_COMPLEXITY = 1 @@ -12,11 +13,39 @@ module Types kwargs[:complexity] = field_complexity(kwargs[:resolver_class], kwargs[:complexity]) @feature_flag = kwargs[:feature_flag] kwargs = check_feature_flag(kwargs) - kwargs = handle_deprecated(kwargs) + kwargs = gitlab_deprecation(kwargs) super(*args, **kwargs, &block) end + # Based on https://github.com/rmosolgo/graphql-ruby/blob/v1.11.4/lib/graphql/schema/field.rb#L538-L563 + # Modified to fix https://github.com/rmosolgo/graphql-ruby/issues/3113 + def resolve_field(obj, args, ctx) + ctx.schema.after_lazy(obj) do |after_obj| + query_ctx = ctx.query.context + inner_obj = after_obj && after_obj.object + + ctx.schema.after_lazy(to_ruby_args(after_obj, args, ctx)) do |ruby_args| + if authorized?(inner_obj, ruby_args, query_ctx) + if @resolve_proc + # We pass `after_obj` here instead of `inner_obj` because extensions expect a GraphQL::Schema::Object + with_extensions(after_obj, ruby_args, query_ctx) do |extended_obj, extended_args| + # Since `extended_obj` is now a GraphQL::Schema::Object, we need to get the inner object and pass that to `@resolve_proc` + extended_obj = extended_obj.object if extended_obj.is_a?(GraphQL::Schema::Object) + + @resolve_proc.call(extended_obj, args, ctx) + end + else + public_send_field(after_obj, ruby_args, query_ctx) + end + else + err = GraphQL::UnauthorizedFieldError.new(object: inner_obj, type: obj.class, context: ctx, field: self) + query_ctx.schema.unauthorized_field(err) + end + end + end + end + def base_complexity complexity = DEFAULT_COMPLEXITY complexity += 1 if calls_gitaly? @@ -52,28 +81,6 @@ module Types args end - def handle_deprecated(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-fields' - end - - deprecation = kwargs.delete(:deprecated) - return kwargs unless deprecation - - milestone, reason = deprecation.values_at(:milestone, :reason).map(&:presence) - - raise ArgumentError, 'Please provide a `milestone` within `deprecated`' unless milestone - raise ArgumentError, 'Please provide a `reason` within `deprecated`' unless reason - raise ArgumentError, '`milestone` must be a `String`' unless milestone.is_a?(String) - - deprecated_in = "Deprecated in #{milestone}" - kwargs[:deprecation_reason] = "#{reason}. #{deprecated_in}" - kwargs[:description] += ". #{deprecated_in}: #{reason}" if kwargs[:description] - - kwargs - end - def field_complexity(resolver_class, current) return current if current.present? && current > 0 diff --git a/app/graphql/types/board_list_type.rb b/app/graphql/types/board_list_type.rb index 70c0794fc90..24faf1fe8bc 100644 --- a/app/graphql/types/board_list_type.rb +++ b/app/graphql/types/board_list_type.rb @@ -41,7 +41,7 @@ module Types list = self.object user = context[:current_user] - Boards::Issues::ListService + ::Boards::Issues::ListService .new(list.board.resource_parent, user, board_id: list.board_id, id: list.id) .metadata end diff --git a/app/graphql/types/boards/board_issue_input_base_type.rb b/app/graphql/types/boards/board_issue_input_base_type.rb new file mode 100644 index 00000000000..1187b3352cd --- /dev/null +++ b/app/graphql/types/boards/board_issue_input_base_type.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Types + module Boards + # rubocop: disable Graphql/AuthorizeTypes + class BoardIssueInputBaseType < BaseInputObject + argument :label_name, GraphQL::STRING_TYPE.to_list_type, + required: false, + description: 'Filter by label name' + + argument :milestone_title, GraphQL::STRING_TYPE, + required: false, + description: 'Filter by milestone title' + + argument :assignee_username, GraphQL::STRING_TYPE.to_list_type, + required: false, + description: 'Filter by assignee username' + + argument :author_username, GraphQL::STRING_TYPE, + required: false, + description: 'Filter by author username' + + argument :release_tag, GraphQL::STRING_TYPE, + required: false, + description: 'Filter by release tag' + + argument :my_reaction_emoji, GraphQL::STRING_TYPE, + required: false, + description: 'Filter by reaction emoji' + end + # rubocop: enable Graphql/AuthorizeTypes + end +end + +Types::Boards::BoardIssueInputBaseType.prepend_if_ee('::EE::Types::Boards::BoardIssueInputBaseType') diff --git a/app/graphql/types/boards/board_issue_input_type.rb b/app/graphql/types/boards/board_issue_input_type.rb new file mode 100644 index 00000000000..40d065d8ea9 --- /dev/null +++ b/app/graphql/types/boards/board_issue_input_type.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Types + module Boards + # rubocop: disable Graphql/AuthorizeTypes + class NegatedBoardIssueInputType < BoardIssueInputBaseType + end + + class BoardIssueInputType < BoardIssueInputBaseType + graphql_name 'BoardIssueInput' + + argument :not, NegatedBoardIssueInputType, + required: false, + description: 'List of negated params. Warning: this argument is experimental and a subject to change in future' + + argument :search, GraphQL::STRING_TYPE, + required: false, + description: 'Search query for issue title or description' + end + # rubocop: enable Graphql/AuthorizeTypes + end +end + +Types::Boards::BoardIssueInputType.prepend_if_ee('::EE::Types::Boards::BoardIssueInputType') diff --git a/app/graphql/types/ci/pipeline_config_source_enum.rb b/app/graphql/types/ci/pipeline_config_source_enum.rb index 48f88c133b4..e1575cb2f99 100644 --- a/app/graphql/types/ci/pipeline_config_source_enum.rb +++ b/app/graphql/types/ci/pipeline_config_source_enum.rb @@ -3,7 +3,7 @@ module Types module Ci class PipelineConfigSourceEnum < BaseEnum - ::Ci::PipelineEnums.config_sources.keys.each do |state_symbol| + ::Enums::Ci::Pipeline.config_sources.keys.each do |state_symbol| value state_symbol.to_s.upcase, value: state_symbol.to_s end end diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb index 82a9f8495ce..c508b746317 100644 --- a/app/graphql/types/ci/pipeline_type.rb +++ b/app/graphql/types/ci/pipeline_type.rb @@ -26,7 +26,7 @@ module Types description: 'Detailed status of the pipeline', resolve: -> (obj, _args, ctx) { obj.detailed_status(ctx[:current_user]) } field :config_source, PipelineConfigSourceEnum, null: true, - description: "Config source of the pipeline (#{::Ci::PipelineEnums.config_sources.keys.join(', ').upcase})" + description: "Config source of the pipeline (#{::Enums::Ci::Pipeline.config_sources.keys.join(', ').upcase})" field :duration, GraphQL::INT_TYPE, null: true, description: 'Duration of the pipeline in seconds' field :coverage, GraphQL::FLOAT_TYPE, null: true, @@ -48,6 +48,14 @@ module Types field :user, Types::UserType, null: true, description: 'Pipeline user', resolve: -> (pipeline, _args, _context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, pipeline.user_id).find } + field :retryable, GraphQL::BOOLEAN_TYPE, + description: 'Specifies if a pipeline can be retried', + method: :retryable?, + null: false + field :cancelable, GraphQL::BOOLEAN_TYPE, + description: 'Specifies if a pipeline can be canceled', + method: :cancelable?, + null: false end end end diff --git a/app/graphql/types/concerns/gitlab_style_deprecations.rb b/app/graphql/types/concerns/gitlab_style_deprecations.rb new file mode 100644 index 00000000000..2c932f4214b --- /dev/null +++ b/app/graphql/types/concerns/gitlab_style_deprecations.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# Concern for handling deprecation arguments. +# https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-fields-and-enum-values +module GitlabStyleDeprecations + extend ActiveSupport::Concern + + private + + def 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-fields-and-enum-values' + end + + deprecation = kwargs.delete(:deprecated) + return kwargs unless deprecation + + milestone, reason = deprecation.values_at(:milestone, :reason).map(&:presence) + + raise ArgumentError, 'Please provide a `milestone` within `deprecated`' unless milestone + raise ArgumentError, 'Please provide a `reason` within `deprecated`' unless reason + raise ArgumentError, '`milestone` must be a `String`' unless milestone.is_a?(String) + + deprecated_in = "Deprecated in #{milestone}" + kwargs[:deprecation_reason] = "#{reason}. #{deprecated_in}" + kwargs[:description] += ". #{deprecated_in}: #{reason}" if kwargs[:description] + + kwargs + end +end diff --git a/app/graphql/types/current_user_todos.rb b/app/graphql/types/current_user_todos.rb new file mode 100644 index 00000000000..e610286c1a9 --- /dev/null +++ b/app/graphql/types/current_user_todos.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Interface to expose todos for the current_user on the `object` +module Types + module CurrentUserTodos + include BaseInterface + + field_class Types::BaseField + + field :current_user_todos, Types::TodoType.connection_type, + description: 'Todos for the current user', + null: false do + argument :state, Types::TodoStateEnum, + description: 'State of the todos', + required: false + end + + def current_user_todos(state: nil) + state ||= %i(done pending) # TodosFinder treats a `nil` state param as `pending` + + TodosFinder.new(current_user, state: state, type: object.class.name, target_id: object.id).execute + end + end +end diff --git a/app/graphql/types/design_management/design_at_version_type.rb b/app/graphql/types/design_management/design_at_version_type.rb index 343d4cf4ff4..e10a0de1715 100644 --- a/app/graphql/types/design_management/design_at_version_type.rb +++ b/app/graphql/types/design_management/design_at_version_type.rb @@ -6,7 +6,7 @@ module Types graphql_name 'DesignAtVersion' description 'A design pinned to a specific version. ' \ - 'The image field reflects the design as of the associated version.' + 'The image field reflects the design as of the associated version' authorize :read_design @@ -23,7 +23,7 @@ module Types field :design, Types::DesignManagement::DesignType, null: false, - description: 'The underlying design.' + description: 'The underlying design' def cached_stateful_version(_parent) version diff --git a/app/graphql/types/design_management/design_collection_type.rb b/app/graphql/types/design_management/design_collection_type.rb index 194910831c6..904fb270e11 100644 --- a/app/graphql/types/design_management/design_collection_type.rb +++ b/app/graphql/types/design_management/design_collection_type.rb @@ -4,7 +4,7 @@ module Types module DesignManagement class DesignCollectionType < BaseObject graphql_name 'DesignCollection' - description 'A collection of designs.' + description 'A collection of designs' authorize :read_design diff --git a/app/graphql/types/design_management/design_type.rb b/app/graphql/types/design_management/design_type.rb index 3c84dc151bd..4e11a7aaf09 100644 --- a/app/graphql/types/design_management/design_type.rb +++ b/app/graphql/types/design_management/design_type.rb @@ -12,6 +12,7 @@ module Types implements(Types::Notes::NoteableType) implements(Types::DesignManagement::DesignFields) + implements(Types::CurrentUserTodos) field :versions, Types::DesignManagement::VersionType.connection_type, diff --git a/app/graphql/types/error_tracking/sentry_detailed_error_type.rb b/app/graphql/types/error_tracking/sentry_detailed_error_type.rb index 8bdd8afcbff..cfde9fa0d6a 100644 --- a/app/graphql/types/error_tracking/sentry_detailed_error_type.rb +++ b/app/graphql/types/error_tracking/sentry_detailed_error_type.rb @@ -4,7 +4,7 @@ module Types module ErrorTracking class SentryDetailedErrorType < ::Types::BaseObject graphql_name 'SentryDetailedError' - description 'A Sentry error.' + description 'A Sentry error' present_using SentryErrorPresenter diff --git a/app/graphql/types/error_tracking/sentry_error_collection_type.rb b/app/graphql/types/error_tracking/sentry_error_collection_type.rb index f423fcb1b9f..798e0433d06 100644 --- a/app/graphql/types/error_tracking/sentry_error_collection_type.rb +++ b/app/graphql/types/error_tracking/sentry_error_collection_type.rb @@ -4,7 +4,7 @@ module Types module ErrorTracking class SentryErrorCollectionType < ::Types::BaseObject graphql_name 'SentryErrorCollection' - description 'An object containing a collection of Sentry errors, and a detailed error.' + description 'An object containing a collection of Sentry errors, and a detailed error' authorize :read_sentry_issue @@ -21,7 +21,7 @@ module Types required: false argument :sort, String, - description: 'Attribute to sort on. Options are frequency, first_seen, last_seen. last_seen is default.', + description: 'Attribute to sort on. Options are frequency, first_seen, last_seen. last_seen is default', required: false end field :detailed_error, Types::ErrorTracking::SentryDetailedErrorType, diff --git a/app/graphql/types/error_tracking/sentry_error_stack_trace_entry_type.rb b/app/graphql/types/error_tracking/sentry_error_stack_trace_entry_type.rb index 0747e41e9fb..2e6c40b233b 100644 --- a/app/graphql/types/error_tracking/sentry_error_stack_trace_entry_type.rb +++ b/app/graphql/types/error_tracking/sentry_error_stack_trace_entry_type.rb @@ -5,7 +5,7 @@ module Types # rubocop: disable Graphql/AuthorizeTypes class SentryErrorStackTraceEntryType < ::Types::BaseObject graphql_name 'SentryErrorStackTraceEntry' - description 'An object containing a stack trace entry for a Sentry error.' + description 'An object containing a stack trace entry for a Sentry error' field :function, GraphQL::STRING_TYPE, null: true, diff --git a/app/graphql/types/error_tracking/sentry_error_stack_trace_type.rb b/app/graphql/types/error_tracking/sentry_error_stack_trace_type.rb index 0e6105d1ff2..1bbe7e0c77b 100644 --- a/app/graphql/types/error_tracking/sentry_error_stack_trace_type.rb +++ b/app/graphql/types/error_tracking/sentry_error_stack_trace_type.rb @@ -4,7 +4,7 @@ module Types module ErrorTracking class SentryErrorStackTraceType < ::Types::BaseObject graphql_name 'SentryErrorStackTrace' - description 'An object containing a stack trace entry for a Sentry error.' + description 'An object containing a stack trace entry for a Sentry error' authorize :read_sentry_issue diff --git a/app/graphql/types/error_tracking/sentry_error_type.rb b/app/graphql/types/error_tracking/sentry_error_type.rb index 7a842025e45..693ab0c4f8f 100644 --- a/app/graphql/types/error_tracking/sentry_error_type.rb +++ b/app/graphql/types/error_tracking/sentry_error_type.rb @@ -5,7 +5,7 @@ module Types # rubocop: disable Graphql/AuthorizeTypes class SentryErrorType < ::Types::BaseObject graphql_name 'SentryError' - description 'A Sentry error. A simplified version of SentryDetailedError.' + description 'A Sentry error. A simplified version of SentryDetailedError' present_using SentryErrorPresenter diff --git a/app/graphql/types/group_member_type.rb b/app/graphql/types/group_member_type.rb index ffffa3247db..6cca0a50647 100644 --- a/app/graphql/types/group_member_type.rb +++ b/app/graphql/types/group_member_type.rb @@ -8,7 +8,7 @@ module Types implements MemberInterface graphql_name 'GroupMember' - description 'Represents a Group Member' + description 'Represents a Group Membership' field :group, Types::GroupType, null: true, description: 'Group that a User is a member of', diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb index cc8cd7c01f9..60b2e3c7b6e 100644 --- a/app/graphql/types/group_type.rb +++ b/app/graphql/types/group_type.rb @@ -75,6 +75,12 @@ module Types description: 'Title of the label' end + field :group_members, + Types::GroupMemberType.connection_type, + description: 'A membership of a user within this group', + extras: [:lookahead], + resolver: Resolvers::GroupMembersResolver + def label(title:) BatchLoader::GraphQL.for(title).batch(key: group) do |titles, loader, args| LabelsFinder diff --git a/app/graphql/types/issuable_severity_enum.rb b/app/graphql/types/issuable_severity_enum.rb new file mode 100644 index 00000000000..563965d991e --- /dev/null +++ b/app/graphql/types/issuable_severity_enum.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Types + class IssuableSeverityEnum < BaseEnum + graphql_name 'IssuableSeverity' + description 'Incident severity' + + ::IssuableSeverity.severities.keys.each do |severity| + value severity.upcase, value: severity, description: "#{severity.titleize} severity" + end + end +end diff --git a/app/graphql/types/issue_status_counts_type.rb b/app/graphql/types/issue_status_counts_type.rb index f2b1ba8e655..77429f9ea12 100644 --- a/app/graphql/types/issue_status_counts_type.rb +++ b/app/graphql/types/issue_status_counts_type.rb @@ -3,7 +3,7 @@ module Types class IssueStatusCountsType < BaseObject graphql_name 'IssueStatusCountsType' - description "Represents total number of issues for the represented statuses." + description 'Represents total number of issues for the represented statuses' authorize :read_issue diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index 0a73ce95424..d6253f74ce5 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -7,6 +7,7 @@ module Types connection_type_class(Types::CountableConnectionType) implements(Types::Notes::NoteableType) + implements(Types::CurrentUserTodos) authorize :read_issue @@ -38,12 +39,10 @@ module Types description: 'User that created the issue', resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find } - # Remove complexity when BatchLoader is used - field :assignees, Types::UserType.connection_type, null: true, complexity: 5, + field :assignees, Types::UserType.connection_type, null: true, description: 'Assignees of the issue' - # Remove complexity when BatchLoader is used - field :labels, Types::LabelType.connection_type, null: true, complexity: 5, + field :labels, Types::LabelType.connection_type, null: true, description: 'Labels of the issue' field :milestone, Types::MilestoneType, null: true, description: 'Milestone of the issue', @@ -101,6 +100,14 @@ module Types field :type, Types::IssueTypeEnum, null: true, method: :issue_type, description: 'Type of the issue' + + field :alert_management_alert, + Types::AlertManagement::AlertType, + null: true, + description: 'Alert associated to this issue' + + field :severity, Types::IssuableSeverityEnum, null: true, + description: 'Severity level of the incident' end end diff --git a/app/graphql/types/jira_user_type.rb b/app/graphql/types/jira_user_type.rb index 999526a920e..394820d23be 100644 --- a/app/graphql/types/jira_user_type.rb +++ b/app/graphql/types/jira_user_type.rb @@ -7,7 +7,7 @@ module Types graphql_name 'JiraUser' field :jira_account_id, GraphQL::STRING_TYPE, null: false, - description: 'Account id of the Jira user' + description: 'Account ID of the Jira user' field :jira_display_name, GraphQL::STRING_TYPE, null: false, description: 'Display name of the Jira user' field :jira_email, GraphQL::STRING_TYPE, null: true, diff --git a/app/graphql/types/member_interface.rb b/app/graphql/types/member_interface.rb index 976836221bc..615a45413cb 100644 --- a/app/graphql/types/member_interface.rb +++ b/app/graphql/types/member_interface.rb @@ -4,6 +4,9 @@ module Types module MemberInterface include BaseInterface + field :id, GraphQL::ID_TYPE, null: false, + description: 'ID of the member' + field :access_level, Types::AccessLevelType, null: true, description: 'GitLab::Access level' @@ -18,5 +21,21 @@ module Types field :expires_at, Types::TimeType, null: true, description: 'Date and time the membership expires' + + field :user, Types::UserType, null: false, + description: 'User that is associated with the member object' + + definition_methods do + def resolve_type(object, context) + case object + when GroupMember + Types::GroupMemberType + when ProjectMember + Types::ProjectMemberType + else + raise ::Gitlab::Graphql::Errors::BaseError, "Unknown member type #{object.class.name}" + end + end + end end end diff --git a/app/graphql/types/merge_request_sort_enum.rb b/app/graphql/types/merge_request_sort_enum.rb new file mode 100644 index 00000000000..c64ae367a76 --- /dev/null +++ b/app/graphql/types/merge_request_sort_enum.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Types + class MergeRequestSortEnum < IssuableSortEnum + graphql_name 'MergeRequestSort' + description 'Values for sorting merge requests' + + value 'MERGED_AT_ASC', 'Merge time by ascending order', value: :merged_at_asc + value 'MERGED_AT_DESC', 'Merge time by descending order', value: :merged_at_desc + end +end diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index 01b02b7976f..56c88491684 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -7,6 +7,7 @@ module Types connection_type_class(Types::CountableConnectionType) implements(Types::Notes::NoteableType) + implements(Types::CurrentUserTodos) authorize :read_merge_request @@ -79,7 +80,7 @@ module Types description: 'Error message due to a merge error' field :allow_collaboration, GraphQL::BOOLEAN_TYPE, null: true, description: 'Indicates if members of the target project can push to the fork' - field :should_be_rebased, GraphQL::BOOLEAN_TYPE, method: :should_be_rebased?, null: false, + field :should_be_rebased, GraphQL::BOOLEAN_TYPE, method: :should_be_rebased?, null: false, calls_gitaly: true, description: 'Indicates if the merge request will be rebased' field :rebase_commit_sha, GraphQL::STRING_TYPE, null: true, description: 'Rebase commit SHA of the merge request' @@ -112,6 +113,7 @@ module Types field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline, description: 'The pipeline running on the branch HEAD of the merge request' field :pipelines, Types::Ci::PipelineType.connection_type, + null: true, description: 'Pipelines for the merge request', resolver: Resolvers::MergeRequestPipelinesResolver @@ -145,6 +147,10 @@ module Types description: Types::TaskCompletionStatus.description field :commit_count, GraphQL::INT_TYPE, null: true, description: 'Number of commits in the merge request' + field :conflicts, GraphQL::BOOLEAN_TYPE, null: false, method: :cannot_be_merged?, + description: 'Indicates if the merge request has conflicts' + field :auto_merge_enabled, GraphQL::BOOLEAN_TYPE, null: false, + description: 'Indicates if auto merge is enabled for the merge request' def diff_stats(path: nil) stats = Array.wrap(object.diff_stats&.to_a) diff --git a/app/graphql/types/metrics/dashboard_type.rb b/app/graphql/types/metrics/dashboard_type.rb index bbcce2d9596..47502356773 100644 --- a/app/graphql/types/metrics/dashboard_type.rb +++ b/app/graphql/types/metrics/dashboard_type.rb @@ -16,6 +16,13 @@ module Types field :annotations, Types::Metrics::Dashboards::AnnotationType.connection_type, null: true, description: 'Annotations added to the dashboard', resolver: Resolvers::Metrics::Dashboards::AnnotationResolver + + # In order to maintain backward compatibility we need to return NULL when there are no warnings + # and dashboard validation returns an empty array when there are no issues. + def schema_validation_warnings + warnings = object.schema_validation_warnings + warnings unless warnings.empty? + end end # rubocop: enable Graphql/AuthorizeTypes end diff --git a/app/graphql/types/milestone_type.rb b/app/graphql/types/milestone_type.rb index ca606c9da44..8603043804e 100644 --- a/app/graphql/types/milestone_type.rb +++ b/app/graphql/types/milestone_type.rb @@ -3,7 +3,7 @@ module Types class MilestoneType < BaseObject graphql_name 'Milestone' - description 'Represents a milestone.' + description 'Represents a milestone' present_using MilestonePresenter @@ -60,3 +60,5 @@ module Types end end end + +Types::MilestoneType.prepend_if_ee('::EE::Types::MilestoneType') diff --git a/app/graphql/types/mutation_operation_mode_enum.rb b/app/graphql/types/mutation_operation_mode_enum.rb index 90a29d2b0e5..37e83e7a2e1 100644 --- a/app/graphql/types/mutation_operation_mode_enum.rb +++ b/app/graphql/types/mutation_operation_mode_enum.rb @@ -3,7 +3,7 @@ module Types class MutationOperationModeEnum < BaseEnum graphql_name 'MutationOperationMode' - description 'Different toggles for changing mutator behavior.' + description 'Different toggles for changing mutator behavior' # Suggested param name for the enum: `operation_mode` diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index e143d14676e..b2732d83aac 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -14,6 +14,7 @@ module Types mount_mutation Mutations::AwardEmojis::Add mount_mutation Mutations::AwardEmojis::Remove mount_mutation Mutations::AwardEmojis::Toggle + mount_mutation Mutations::Boards::Destroy mount_mutation Mutations::Boards::Issues::IssueMoveList mount_mutation Mutations::Boards::Lists::Create mount_mutation Mutations::Boards::Lists::Update @@ -24,6 +25,7 @@ module Types mount_mutation Mutations::Issues::SetConfidential mount_mutation Mutations::Issues::SetLocked mount_mutation Mutations::Issues::SetDueDate + mount_mutation Mutations::Issues::SetSeverity mount_mutation Mutations::Issues::SetSubscription mount_mutation Mutations::Issues::Update mount_mutation Mutations::MergeRequests::Create @@ -62,6 +64,9 @@ module Types mount_mutation Mutations::DesignManagement::Delete, calls_gitaly: true mount_mutation Mutations::DesignManagement::Move mount_mutation Mutations::ContainerExpirationPolicies::Update + mount_mutation Mutations::Ci::PipelineCancel + mount_mutation Mutations::Ci::PipelineDestroy + mount_mutation Mutations::Ci::PipelineRetry end end diff --git a/app/graphql/types/permission_types/merge_request.rb b/app/graphql/types/permission_types/merge_request.rb index 28b7ebd2af6..e9c89b0c92e 100644 --- a/app/graphql/types/permission_types/merge_request.rb +++ b/app/graphql/types/permission_types/merge_request.rb @@ -18,6 +18,10 @@ module Types PERMISSION_FIELDS.each do |field_name| permission_field field_name, method: :"can_#{field_name}?", calls_gitaly: true end + + permission_field :can_merge, calls_gitaly: true, resolve: -> (object, args, context) do + object.can_be_merged_by?(context[:current_user]) + end end end end diff --git a/app/graphql/types/project_member_type.rb b/app/graphql/types/project_member_type.rb index e9ccb51886b..f08781238d0 100644 --- a/app/graphql/types/project_member_type.rb +++ b/app/graphql/types/project_member_type.rb @@ -3,7 +3,7 @@ module Types class ProjectMemberType < BaseObject graphql_name 'ProjectMember' - description 'Represents a Project Member' + description 'Represents a Project Membership' expose_permissions Types::PermissionTypes::Project @@ -11,13 +11,6 @@ module Types authorize :read_project - field :id, GraphQL::ID_TYPE, null: false, - description: 'ID of the member' - - field :user, Types::UserType, null: false, - description: 'User that is associated with the member object', - resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.user_id).find } - field :project, Types::ProjectType, null: true, description: 'Project that User is a member of', resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, obj.source_id).find } diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 5562db69de6..0fd54af1538 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -54,7 +54,7 @@ module Types field :container_registry_enabled, GraphQL::BOOLEAN_TYPE, null: true, description: 'Indicates if the project stores Docker container images in a container registry' field :shared_runners_enabled, GraphQL::BOOLEAN_TYPE, null: true, - description: 'Indicates if Shared Runners are enabled for the project' + description: 'Indicates if shared runners are enabled for the project' field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, description: 'Indicates if the project has Large File Storage (LFS) enabled' field :merge_requests_ff_only_enabled, GraphQL::BOOLEAN_TYPE, null: true, @@ -134,7 +134,7 @@ module Types null: true, description: 'Merge requests of the project', extras: [:lookahead], - resolver: Resolvers::MergeRequestsResolver + resolver: Resolvers::ProjectMergeRequestsResolver field :merge_request, Types::MergeRequestType, @@ -146,12 +146,14 @@ module Types Types::IssueType.connection_type, null: true, description: 'Issues of the project', + extras: [:lookahead], resolver: Resolvers::IssuesResolver field :issue_status_counts, Types::IssueStatusCountsType, null: true, description: 'Counts of issues by status for the project', + extras: [:lookahead], resolver: Resolvers::IssueStatusCountsResolver field :milestones, Types::MilestoneType.connection_type, null: true, @@ -159,7 +161,7 @@ module Types resolver: Resolvers::ProjectMilestonesResolver field :project_members, - Types::ProjectMemberType.connection_type, + Types::MemberInterface.connection_type, description: 'Members of the project', resolver: Resolvers::ProjectMembersResolver diff --git a/app/graphql/types/projects/namespace_project_sort_enum.rb b/app/graphql/types/projects/namespace_project_sort_enum.rb new file mode 100644 index 00000000000..1e13deb6508 --- /dev/null +++ b/app/graphql/types/projects/namespace_project_sort_enum.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Types + module Projects + class NamespaceProjectSortEnum < BaseEnum + graphql_name 'NamespaceProjectSort' + description 'Values for sorting projects' + + value 'SIMILARITY', 'Most similar to the search query', value: :similarity + end + end +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index c04f4da70cf..447ac63a294 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -70,9 +70,24 @@ module Types description: 'Text to echo back', resolver: Resolvers::EchoResolver + field :issue, Types::IssueType, + null: true, + description: 'Find an issue' do + argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'The global ID of the Issue' + end + + field :instance_statistics_measurements, Types::Admin::Analytics::InstanceStatistics::MeasurementType.connection_type, + null: true, + description: 'Get statistics on the instance', + resolver: Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsResolver + def design_management DesignManagementObject.new(nil) end + + def issue(id:) + GitlabSchema.object_from_id(id, expected_type: ::Issue) + end end end diff --git a/app/graphql/types/release_asset_link_type.rb b/app/graphql/types/release_asset_link_type.rb index 21f1bd50cff..0e519ece791 100644 --- a/app/graphql/types/release_asset_link_type.rb +++ b/app/graphql/types/release_asset_link_type.rb @@ -17,5 +17,17 @@ module Types description: 'Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`' field :external, GraphQL::BOOLEAN_TYPE, null: true, method: :external?, description: 'Indicates the link points to an external resource' + + field :direct_asset_url, GraphQL::STRING_TYPE, null: true, + description: 'Direct asset URL of the link' + + def direct_asset_url + return object.url unless object.filepath + + release = object.release + project = release.project + + Gitlab::Routing.url_helpers.project_release_url(project, release) << object.filepath + end end end diff --git a/app/graphql/types/release_type.rb b/app/graphql/types/release_type.rb index a0703b96a36..b715b981483 100644 --- a/app/graphql/types/release_type.rb +++ b/app/graphql/types/release_type.rb @@ -5,6 +5,8 @@ module Types graphql_name 'Release' description 'Represents a release' + connection_type_class(Types::CountableConnectionType) + authorize :read_release alias_method :release, :object @@ -26,6 +28,8 @@ module Types description: 'Timestamp of when the release was created' field :released_at, Types::TimeType, null: true, description: 'Timestamp of when the release was released' + field :upcoming_release, GraphQL::BOOLEAN_TYPE, null: true, method: :upcoming_release?, + description: 'Indicates the release is an upcoming release' field :assets, Types::ReleaseAssetsType, null: true, method: :itself, description: 'Assets of the release' field :links, Types::ReleaseLinksType, null: true, method: :itself, diff --git a/app/graphql/types/todo_type.rb b/app/graphql/types/todo_type.rb index 08e7fabeb74..4f21da3d897 100644 --- a/app/graphql/types/todo_type.rb +++ b/app/graphql/types/todo_type.rb @@ -26,7 +26,7 @@ module Types resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, todo.group_id).find } field :author, Types::UserType, - description: 'The owner of this todo', + description: 'The author of this todo', null: false, resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, todo.author_id).find } diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb index cb3575b41d1..8047708776d 100644 --- a/app/graphql/types/user_type.rb +++ b/app/graphql/types/user_type.rb @@ -37,6 +37,9 @@ module Types field :project_memberships, Types::ProjectMemberType.connection_type, null: true, description: 'Project memberships of the user', method: :project_members + field :starred_projects, Types::ProjectType.connection_type, null: true, + description: 'Projects starred by the user', + resolver: Resolvers::UserStarredProjectsResolver # Merge request field: MRs can be either authored or assigned: field :authored_merge_requests, Types::MergeRequestType.connection_type, null: true, |