diff options
Diffstat (limited to 'app/graphql/mutations')
29 files changed, 452 insertions, 54 deletions
diff --git a/app/graphql/mutations/alert_management/alerts/set_assignees.rb b/app/graphql/mutations/alert_management/alerts/set_assignees.rb new file mode 100644 index 00000000000..1e0c9fdeeaf --- /dev/null +++ b/app/graphql/mutations/alert_management/alerts/set_assignees.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Mutations + module AlertManagement + module Alerts + class SetAssignees < Base + graphql_name 'AlertSetAssignees' + + argument :assignee_usernames, + [GraphQL::STRING_TYPE], + required: true, + description: 'The usernames to assign to the alert. Replaces existing assignees by default.' + + argument :operation_mode, + Types::MutationOperationModeEnum, + required: false, + description: 'The operation to perform. Defaults to REPLACE.' + + def resolve(args) + alert = authorized_find!(project_path: args[:project_path], iid: args[:iid]) + result = set_assignees(alert, args[:assignee_usernames], args[:operation_mode]) + + prepare_response(result) + end + + private + + def set_assignees(alert, assignee_usernames, operation_mode) + operation_mode ||= Types::MutationOperationModeEnum.enum[:replace] + + original_assignees = alert.assignees + target_users = find_target_users(assignee_usernames) + + assignees = case Types::MutationOperationModeEnum.enum.key(operation_mode).to_sym + when :replace then target_users.uniq + when :append then (original_assignees + target_users).uniq + when :remove then (original_assignees - target_users) + end + + ::AlertManagement::Alerts::UpdateService.new(alert, current_user, assignees: assignees).execute + end + + def find_target_users(assignee_usernames) + UsersFinder.new(current_user, username: assignee_usernames).execute + end + + def prepare_response(result) + { + alert: result.payload[:alert], + errors: result.error? ? [result.message] : [] + } + end + end + end + end +end diff --git a/app/graphql/mutations/alert_management/base.rb b/app/graphql/mutations/alert_management/base.rb index ca2057d4845..7fcca63db51 100644 --- a/app/graphql/mutations/alert_management/base.rb +++ b/app/graphql/mutations/alert_management/base.rb @@ -3,7 +3,7 @@ module Mutations module AlertManagement class Base < BaseMutation - include Mutations::ResolvesProject + include ResolvesProject argument :project_path, GraphQL::ID_TYPE, required: true, @@ -32,7 +32,7 @@ module Mutations return unless project - resolver = Resolvers::AlertManagementAlertResolver.single.new(object: project, context: context, field: nil) + resolver = Resolvers::AlertManagement::AlertResolver.single.new(object: project, context: context, field: nil) resolver.resolve(iid: iid) end end diff --git a/app/graphql/mutations/alert_management/update_alert_status.rb b/app/graphql/mutations/alert_management/update_alert_status.rb index e73a591378a..d820124d26f 100644 --- a/app/graphql/mutations/alert_management/update_alert_status.rb +++ b/app/graphql/mutations/alert_management/update_alert_status.rb @@ -27,7 +27,7 @@ module Mutations def prepare_response(result) { alert: result.payload[:alert], - errors: result.error? ? [result.message] : [] + errors: result.errors } end end diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb index 30510cfab50..33f3f33a440 100644 --- a/app/graphql/mutations/base_mutation.rb +++ b/app/graphql/mutations/base_mutation.rb @@ -9,7 +9,7 @@ module Mutations field :errors, [GraphQL::STRING_TYPE], null: false, - description: "Errors encountered during execution of the mutation." + description: 'Errors encountered during execution of the mutation.' def current_user context[:current_user] diff --git a/app/graphql/mutations/branches/create.rb b/app/graphql/mutations/branches/create.rb index 127d5447d0a..214fead2e80 100644 --- a/app/graphql/mutations/branches/create.rb +++ b/app/graphql/mutations/branches/create.rb @@ -3,7 +3,7 @@ module Mutations module Branches class Create < BaseMutation - include Mutations::ResolvesProject + include ResolvesProject graphql_name 'CreateBranch' diff --git a/app/graphql/mutations/commits/create.rb b/app/graphql/mutations/commits/create.rb new file mode 100644 index 00000000000..9ed1bb819c8 --- /dev/null +++ b/app/graphql/mutations/commits/create.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module Mutations + module Commits + class Create < BaseMutation + include ResolvesProject + + graphql_name 'CommitCreate' + + argument :project_path, GraphQL::ID_TYPE, + required: true, + description: 'Project full path the branch is associated with' + + argument :branch, GraphQL::STRING_TYPE, + required: true, + description: 'Name of the branch' + + argument :message, + GraphQL::STRING_TYPE, + required: true, + description: copy_field_description(Types::CommitType, :message) + + argument :actions, + [Types::CommitActionType], + required: true, + description: 'Array of action hashes to commit as a batch' + + field :commit, + Types::CommitType, + null: true, + description: 'The commit after mutation' + + authorize :push_code + + def resolve(project_path:, branch:, message:, actions:) + project = authorized_find!(full_path: project_path) + + attributes = { + commit_message: message, + branch_name: branch, + start_branch: branch, + actions: actions.map { |action| action.to_h } + } + + result = ::Files::MultiService.new(project, current_user, attributes).execute + + { + commit: (project.repository.commit(result[:result]) if result[:status] == :success), + errors: Array.wrap(result[:message]) + } + end + + private + + def find_object(full_path:) + resolve_project(full_path: full_path) + end + end + end +end diff --git a/app/graphql/mutations/concerns/mutations/resolves_issuable.rb b/app/graphql/mutations/concerns/mutations/resolves_issuable.rb index d63cc27a450..13a56f2e709 100644 --- a/app/graphql/mutations/concerns/mutations/resolves_issuable.rb +++ b/app/graphql/mutations/concerns/mutations/resolves_issuable.rb @@ -3,12 +3,22 @@ module Mutations module ResolvesIssuable extend ActiveSupport::Concern - include Mutations::ResolvesProject + + included do + include ResolvesProject + end def resolve_issuable(type:, parent_path:, iid:) parent = resolve_issuable_parent(type, parent_path) + key = type == :merge_request ? :iids : :iid + args = { key => iid.to_s } + + resolver = issuable_resolver(type, parent, context) + ready, early_return = resolver.ready?(**args) + + return early_return unless ready - issuable_resolver(type, parent, context).resolve(iid: iid.to_s) + resolver.resolve(**args) end private @@ -22,7 +32,7 @@ module Mutations def resolve_issuable_parent(type, parent_path) return unless type == :issue || type == :merge_request - resolve_project(full_path: parent_path) + resolve_project(full_path: parent_path) if parent_path.present? end end end diff --git a/app/graphql/mutations/concerns/mutations/resolves_project.rb b/app/graphql/mutations/concerns/mutations/resolves_project.rb deleted file mode 100644 index e223e3edd94..00000000000 --- a/app/graphql/mutations/concerns/mutations/resolves_project.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module Mutations - module ResolvesProject - extend ActiveSupport::Concern - - def resolve_project(full_path:) - project_resolver.resolve(full_path: full_path) - end - - def project_resolver - Resolvers::ProjectResolver.new(object: nil, context: context, field: nil) - end - end -end diff --git a/app/graphql/mutations/container_expiration_policies/update.rb b/app/graphql/mutations/container_expiration_policies/update.rb new file mode 100644 index 00000000000..c210571c6ca --- /dev/null +++ b/app/graphql/mutations/container_expiration_policies/update.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module Mutations + module ContainerExpirationPolicies + class Update < Mutations::BaseMutation + include ResolvesProject + + graphql_name 'UpdateContainerExpirationPolicy' + + authorize :destroy_container_image + + argument :project_path, + GraphQL::ID_TYPE, + required: true, + description: 'The project path where the container expiration policy is located' + + argument :enabled, + GraphQL::BOOLEAN_TYPE, + required: false, + description: copy_field_description(Types::ContainerExpirationPolicyType, :enabled) + + argument :cadence, + Types::ContainerExpirationPolicyCadenceEnum, + required: false, + description: copy_field_description(Types::ContainerExpirationPolicyType, :cadence) + + argument :older_than, + Types::ContainerExpirationPolicyOlderThanEnum, + required: false, + description: copy_field_description(Types::ContainerExpirationPolicyType, :older_than) + + argument :keep_n, + Types::ContainerExpirationPolicyKeepEnum, + required: false, + description: copy_field_description(Types::ContainerExpirationPolicyType, :keep_n) + + field :container_expiration_policy, + Types::ContainerExpirationPolicyType, + null: true, + description: 'The container expiration policy after mutation' + + def resolve(project_path:, **args) + project = authorized_find!(full_path: project_path) + + result = ::ContainerExpirationPolicies::UpdateService + .new(container: project, current_user: current_user, params: args) + .execute + + { + container_expiration_policy: result.payload[:container_expiration_policy], + errors: result.errors + } + end + + private + + def find_object(full_path:) + resolve_project(full_path: full_path) + end + end + end +end diff --git a/app/graphql/mutations/discussions/toggle_resolve.rb b/app/graphql/mutations/discussions/toggle_resolve.rb new file mode 100644 index 00000000000..41fd22c6b55 --- /dev/null +++ b/app/graphql/mutations/discussions/toggle_resolve.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module Mutations + module Discussions + class ToggleResolve < BaseMutation + graphql_name 'DiscussionToggleResolve' + + description 'Toggles the resolved state of a discussion' + + argument :id, + GraphQL::ID_TYPE, + required: true, + description: 'The global id of the discussion' + + argument :resolve, + GraphQL::BOOLEAN_TYPE, + required: true, + description: 'Will resolve the discussion when true, and unresolve the discussion when false' + + field :discussion, + Types::Notes::DiscussionType, + null: true, + description: 'The discussion after mutation' + + def resolve(id:, resolve:) + discussion = authorized_find_discussion!(id: id) + errors = [] + + begin + if resolve + resolve!(discussion) + else + unresolve!(discussion) + end + rescue ActiveRecord::RecordNotSaved + errors << "Discussion failed to be #{'un' unless resolve}resolved" + end + + { + discussion: discussion, + errors: errors + } + end + + private + + # `Discussion` permissions are checked through `Discussion#can_resolve?`, + # so we use this method of checking permissions rather than by defining + # an `authorize` permission and calling `authorized_find!`. + def authorized_find_discussion!(id:) + find_object(id: id).tap do |discussion| + raise_resource_not_available_error! unless discussion&.can_resolve?(current_user) + end + end + + def find_object(id:) + GitlabSchema.object_from_id(id, expected_type: ::Discussion) + end + + def resolve!(discussion) + ::Discussions::ResolveService.new( + discussion.project, + current_user, + one_or_more_discussions: discussion + ).execute + end + + def unresolve!(discussion) + discussion.unresolve! + end + end + end +end diff --git a/app/graphql/mutations/issues/set_confidential.rb b/app/graphql/mutations/issues/set_confidential.rb index 0fff5518665..75befddc261 100644 --- a/app/graphql/mutations/issues/set_confidential.rb +++ b/app/graphql/mutations/issues/set_confidential.rb @@ -19,7 +19,7 @@ module Mutations { issue: issue, - errors: issue.errors.full_messages + errors: errors_on_object(issue) } end end diff --git a/app/graphql/mutations/issues/set_due_date.rb b/app/graphql/mutations/issues/set_due_date.rb index 1855c6f053b..effd863c541 100644 --- a/app/graphql/mutations/issues/set_due_date.rb +++ b/app/graphql/mutations/issues/set_due_date.rb @@ -19,7 +19,7 @@ module Mutations { issue: issue, - errors: issue.errors.full_messages + errors: errors_on_object(issue) } end end diff --git a/app/graphql/mutations/issues/update.rb b/app/graphql/mutations/issues/update.rb index 3710144fff5..7f6d9b0f988 100644 --- a/app/graphql/mutations/issues/update.rb +++ b/app/graphql/mutations/issues/update.rb @@ -33,7 +33,7 @@ module Mutations { issue: issue, - errors: issue.errors.full_messages + errors: errors_on_object(issue) } end end diff --git a/app/graphql/mutations/jira_import/import_users.rb b/app/graphql/mutations/jira_import/import_users.rb new file mode 100644 index 00000000000..c7225e1a99c --- /dev/null +++ b/app/graphql/mutations/jira_import/import_users.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Mutations + module JiraImport + class ImportUsers < BaseMutation + include ResolvesProject + + graphql_name 'JiraImportUsers' + + field :jira_users, + [Types::JiraUserType], + null: true, + description: 'Users returned from Jira, matched by email and name if possible.' + + argument :project_path, GraphQL::ID_TYPE, + required: true, + description: 'The project to import the Jira users into' + argument :start_at, GraphQL::INT_TYPE, + required: false, + description: 'The index of the record the import should started at, default 0 (50 records returned)' + + def resolve(project_path:, start_at:) + project = authorized_find!(full_path: project_path) + + service_response = ::JiraImport::UsersImporter.new(context[:current_user], project, start_at).execute + + { + jira_users: service_response.payload, + errors: service_response.errors + } + end + + private + + def find_object(full_path:) + resolve_project(full_path: full_path) + end + + def authorized_resource?(project) + Ability.allowed?(context[:current_user], :admin_project, project) + end + end + end +end diff --git a/app/graphql/mutations/jira_import/start.rb b/app/graphql/mutations/jira_import/start.rb index 6b80c9f8ca4..3df26d33711 100644 --- a/app/graphql/mutations/jira_import/start.rb +++ b/app/graphql/mutations/jira_import/start.rb @@ -3,7 +3,7 @@ module Mutations module JiraImport class Start < BaseMutation - include Mutations::ResolvesProject + include ResolvesProject graphql_name 'JiraImportStart' @@ -23,29 +23,21 @@ module Mutations description: 'Project name of the importer Jira project' def resolve(project_path:, jira_project_key:) - project = find_project!(project_path: project_path) - - raise_resource_not_available_error! unless project + project = authorized_find!(full_path: project_path) service_response = ::JiraImport::StartImportService .new(context[:current_user], project, jira_project_key) .execute jira_import = service_response.success? ? service_response.payload[:import_data] : nil - errors = service_response.error? ? [service_response.message] : [] + { jira_import: jira_import, - errors: errors + errors: service_response.errors } end private - def find_project!(project_path:) - return unless project_path.present? - - authorized_find!(full_path: project_path) - end - def find_object(full_path:) resolve_project(full_path: full_path) end diff --git a/app/graphql/mutations/merge_requests/create.rb b/app/graphql/mutations/merge_requests/create.rb new file mode 100644 index 00000000000..e210987f259 --- /dev/null +++ b/app/graphql/mutations/merge_requests/create.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module Mutations + module MergeRequests + class Create < BaseMutation + include ResolvesProject + + graphql_name 'MergeRequestCreate' + + argument :project_path, GraphQL::ID_TYPE, + required: true, + description: 'Project full path the merge request is associated with' + + argument :title, GraphQL::STRING_TYPE, + required: true, + description: copy_field_description(Types::MergeRequestType, :title) + + argument :source_branch, GraphQL::STRING_TYPE, + required: true, + description: copy_field_description(Types::MergeRequestType, :source_branch) + + argument :target_branch, GraphQL::STRING_TYPE, + required: true, + description: copy_field_description(Types::MergeRequestType, :target_branch) + + argument :description, GraphQL::STRING_TYPE, + required: false, + description: copy_field_description(Types::MergeRequestType, :description) + + field :merge_request, + Types::MergeRequestType, + null: true, + description: 'The merge request after mutation' + + authorize :create_merge_request_from + + def resolve(project_path:, title:, source_branch:, target_branch:, description: nil) + project = authorized_find!(full_path: project_path) + + attributes = { + title: title, + source_branch: source_branch, + target_branch: target_branch, + author_id: current_user.id, + description: description + } + + merge_request = ::MergeRequests::CreateService.new(project, current_user, attributes).execute + + { + merge_request: merge_request.valid? ? merge_request : nil, + errors: errors_on_object(merge_request) + } + end + + private + + def find_object(full_path:) + resolve_project(full_path: full_path) + end + end + end +end diff --git a/app/graphql/mutations/merge_requests/set_assignees.rb b/app/graphql/mutations/merge_requests/set_assignees.rb index 8f0025f0a58..de244b62d0f 100644 --- a/app/graphql/mutations/merge_requests/set_assignees.rb +++ b/app/graphql/mutations/merge_requests/set_assignees.rb @@ -40,7 +40,7 @@ module Mutations { merge_request: merge_request, - errors: merge_request.errors.full_messages + errors: errors_on_object(merge_request) } end end diff --git a/app/graphql/mutations/merge_requests/set_labels.rb b/app/graphql/mutations/merge_requests/set_labels.rb index 71f7a353bc9..c1e45808593 100644 --- a/app/graphql/mutations/merge_requests/set_labels.rb +++ b/app/graphql/mutations/merge_requests/set_labels.rb @@ -24,8 +24,9 @@ module Mutations project = merge_request.project label_ids = label_ids + .map { |gid| GlobalID.parse(gid) } .select(&method(:label_descendant?)) - .map { |gid| GlobalID.parse(gid).model_id } # MergeRequests::UpdateService expects integers + .map(&:model_id) # MergeRequests::UpdateService expects integers attribute_name = case operation_mode when Types::MutationOperationModeEnum.enum[:append] @@ -41,12 +42,12 @@ module Mutations { merge_request: merge_request, - errors: merge_request.errors.full_messages + errors: errors_on_object(merge_request) } end def label_descendant?(gid) - GlobalID.parse(gid)&.model_class&.ancestors&.include?(Label) + gid&.model_class&.ancestors&.include?(Label) end end end diff --git a/app/graphql/mutations/merge_requests/set_locked.rb b/app/graphql/mutations/merge_requests/set_locked.rb index 09aaa0b39aa..c49d5186a03 100644 --- a/app/graphql/mutations/merge_requests/set_locked.rb +++ b/app/graphql/mutations/merge_requests/set_locked.rb @@ -21,7 +21,7 @@ module Mutations { merge_request: merge_request, - errors: merge_request.errors.full_messages + errors: errors_on_object(merge_request) } end end diff --git a/app/graphql/mutations/merge_requests/set_milestone.rb b/app/graphql/mutations/merge_requests/set_milestone.rb index 707d6677952..b3412dd9ed2 100644 --- a/app/graphql/mutations/merge_requests/set_milestone.rb +++ b/app/graphql/mutations/merge_requests/set_milestone.rb @@ -22,7 +22,7 @@ module Mutations { merge_request: merge_request, - errors: merge_request.errors.full_messages + errors: errors_on_object(merge_request) } end end diff --git a/app/graphql/mutations/merge_requests/set_subscription.rb b/app/graphql/mutations/merge_requests/set_subscription.rb index 86750152775..1535481ab37 100644 --- a/app/graphql/mutations/merge_requests/set_subscription.rb +++ b/app/graphql/mutations/merge_requests/set_subscription.rb @@ -18,7 +18,7 @@ module Mutations { merge_request: merge_request, - errors: merge_request.errors.full_messages + errors: errors_on_object(merge_request) } end end diff --git a/app/graphql/mutations/merge_requests/set_wip.rb b/app/graphql/mutations/merge_requests/set_wip.rb index a2aa0c84ee4..5d2077c12f2 100644 --- a/app/graphql/mutations/merge_requests/set_wip.rb +++ b/app/graphql/mutations/merge_requests/set_wip.rb @@ -21,7 +21,7 @@ module Mutations { merge_request: merge_request, - errors: merge_request.errors.full_messages + errors: errors_on_object(merge_request) } end diff --git a/app/graphql/mutations/metrics/dashboard/annotations/base.rb b/app/graphql/mutations/metrics/dashboard/annotations/base.rb new file mode 100644 index 00000000000..3126267da64 --- /dev/null +++ b/app/graphql/mutations/metrics/dashboard/annotations/base.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Mutations + module Metrics + module Dashboard + module Annotations + class Base < BaseMutation + private + + # This method is defined here in order to be used by `authorized_find!` in the subclasses. + def find_object(id:) + GitlabSchema.object_from_id(id) + end + end + end + end + end +end diff --git a/app/graphql/mutations/metrics/dashboard/annotations/delete.rb b/app/graphql/mutations/metrics/dashboard/annotations/delete.rb new file mode 100644 index 00000000000..fb828ba0e2f --- /dev/null +++ b/app/graphql/mutations/metrics/dashboard/annotations/delete.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Mutations + module Metrics + module Dashboard + module Annotations + class Delete < Base + graphql_name 'DeleteAnnotation' + + authorize :delete_metrics_dashboard_annotation + + argument :id, + GraphQL::ID_TYPE, + required: true, + description: 'The global id of the annotation to delete' + + def resolve(id:) + annotation = authorized_find!(id: id) + + result = ::Metrics::Dashboard::Annotations::DeleteService.new(context[:current_user], annotation).execute + + errors = Array.wrap(result[:message]) + + { + errors: errors + } + end + end + end + end + end +end diff --git a/app/graphql/mutations/snippets/create.rb b/app/graphql/mutations/snippets/create.rb index 6fc223fbee7..e1022358c09 100644 --- a/app/graphql/mutations/snippets/create.rb +++ b/app/graphql/mutations/snippets/create.rb @@ -3,7 +3,7 @@ module Mutations module Snippets class Create < BaseMutation - include Mutations::ResolvesProject + include ResolvesProject graphql_name 'CreateSnippet' @@ -60,7 +60,7 @@ module Mutations snippet = service_response.payload[:snippet] { - snippet: snippet.valid? ? snippet : nil, + snippet: service_response.success? ? snippet : nil, errors: errors_on_object(snippet) } end diff --git a/app/graphql/mutations/todos/mark_all_done.rb b/app/graphql/mutations/todos/mark_all_done.rb index 5694985717c..d30d1bcbcf0 100644 --- a/app/graphql/mutations/todos/mark_all_done.rb +++ b/app/graphql/mutations/todos/mark_all_done.rb @@ -28,7 +28,9 @@ module Mutations def mark_all_todos_done return [] unless current_user - TodoService.new.mark_all_todos_as_done_by_user(current_user) + todos = TodosFinder.new(current_user).execute + + TodoService.new.resolve_todos(todos, current_user, resolved_by_action: :api_all_done) end end end diff --git a/app/graphql/mutations/todos/mark_done.rb b/app/graphql/mutations/todos/mark_done.rb index d738e387c43..748e02d8782 100644 --- a/app/graphql/mutations/todos/mark_done.rb +++ b/app/graphql/mutations/todos/mark_done.rb @@ -30,7 +30,7 @@ module Mutations private def mark_done(todo) - TodoService.new.mark_todo_as_done(todo, current_user) + TodoService.new.resolve_todo(todo, current_user, resolved_by_action: :api_done) end end end diff --git a/app/graphql/mutations/todos/restore.rb b/app/graphql/mutations/todos/restore.rb index c4597bd84a2..a0a1772db0a 100644 --- a/app/graphql/mutations/todos/restore.rb +++ b/app/graphql/mutations/todos/restore.rb @@ -18,7 +18,7 @@ module Mutations def resolve(id:) todo = authorized_find!(id: id) - restore(todo.id) if todo.done? + restore(todo) { todo: todo.reset, @@ -28,8 +28,8 @@ module Mutations private - def restore(id) - TodoService.new.mark_todos_as_pending_by_ids([id], current_user) + def restore(todo) + TodoService.new.restore_todo(todo, current_user) end end end diff --git a/app/graphql/mutations/todos/restore_many.rb b/app/graphql/mutations/todos/restore_many.rb index 8a6265207cd..e95651b232f 100644 --- a/app/graphql/mutations/todos/restore_many.rb +++ b/app/graphql/mutations/todos/restore_many.rb @@ -68,7 +68,7 @@ module Mutations end def restore(todos) - TodoService.new.mark_todos_as_pending(todos, current_user) + TodoService.new.restore_todos(todos, current_user) end end end |