diff options
Diffstat (limited to 'app/graphql/mutations')
22 files changed, 276 insertions, 32 deletions
diff --git a/app/graphql/mutations/achievements/create.rb b/app/graphql/mutations/achievements/create.rb index 6cfe6c0e643..310a653c705 100644 --- a/app/graphql/mutations/achievements/create.rb +++ b/app/graphql/mutations/achievements/create.rb @@ -28,10 +28,6 @@ module Mutations required: false, description: 'Description of or notes for the achievement.' - argument :revokeable, GraphQL::Types::Boolean, - required: true, - description: 'Revokeability for the achievement.' - authorize :admin_achievement def resolve(args) diff --git a/app/graphql/mutations/ci/job_token_scope/add_project.rb b/app/graphql/mutations/ci/job_token_scope/add_project.rb index e16c08cb116..6f0f87b47a1 100644 --- a/app/graphql/mutations/ci/job_token_scope/add_project.rb +++ b/app/graphql/mutations/ci/job_token_scope/add_project.rb @@ -18,18 +18,23 @@ module Mutations required: true, description: 'Project to be added to the CI job token scope.' + argument :direction, + ::Types::Ci::JobTokenScope::DirectionEnum, + required: false, + description: 'Direction of access, which defaults to outbound.' + field :ci_job_token_scope, - Types::Ci::JobTokenScopeType, - null: true, - description: "CI job token's scope of access." + Types::Ci::JobTokenScopeType, + null: true, + description: "CI job token's access scope." - def resolve(project_path:, target_project_path:) + def resolve(project_path:, target_project_path:, direction: :outbound) project = authorized_find!(project_path) target_project = Project.find_by_full_path(target_project_path) result = ::Ci::JobTokenScope::AddProjectService .new(project, current_user) - .execute(target_project) + .execute(target_project, direction: direction) if result.success? { diff --git a/app/graphql/mutations/ci/job_token_scope/remove_project.rb b/app/graphql/mutations/ci/job_token_scope/remove_project.rb index f503b4f2f7a..20e991f5388 100644 --- a/app/graphql/mutations/ci/job_token_scope/remove_project.rb +++ b/app/graphql/mutations/ci/job_token_scope/remove_project.rb @@ -18,18 +18,23 @@ module Mutations required: true, description: 'Project to be removed from the CI job token scope.' + argument :direction, + ::Types::Ci::JobTokenScope::DirectionEnum, + required: false, + description: 'Direction of access, which defaults to outbound.' + field :ci_job_token_scope, - Types::Ci::JobTokenScopeType, - null: true, - description: "CI job token's scope of access." + Types::Ci::JobTokenScopeType, + null: true, + description: "CI job token's scope of access." - def resolve(project_path:, target_project_path:) + def resolve(project_path:, target_project_path:, direction: :outbound) project = authorized_find!(project_path) target_project = Project.find_by_full_path(target_project_path) result = ::Ci::JobTokenScope::RemoveProjectService .new(project, current_user) - .execute(target_project) + .execute(target_project, direction) if result.success? { diff --git a/app/graphql/mutations/ci/pipeline_schedule/update.rb b/app/graphql/mutations/ci/pipeline_schedule/update.rb new file mode 100644 index 00000000000..a0b5e793ecb --- /dev/null +++ b/app/graphql/mutations/ci/pipeline_schedule/update.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module Mutations + module Ci + module PipelineSchedule + class Update < Base + graphql_name 'PipelineScheduleUpdate' + + authorize :update_pipeline_schedule + + argument :description, GraphQL::Types::String, + required: false, + description: 'Description of the pipeline schedule.' + + argument :cron, GraphQL::Types::String, + required: false, + description: 'Cron expression of the pipeline schedule.' + + argument :cron_timezone, GraphQL::Types::String, + required: false, + description: + <<-STR + Cron time zone supported by ActiveSupport::TimeZone. + For example: "Pacific Time (US & Canada)" (default: "UTC"). + STR + + argument :ref, GraphQL::Types::String, + required: false, + description: 'Ref of the pipeline schedule.' + + argument :active, GraphQL::Types::Boolean, + required: false, + description: 'Indicates if the pipeline schedule should be active or not.' + + argument :variables, [Mutations::Ci::PipelineSchedule::VariableInputType], + required: false, + description: 'Variables for the pipeline schedule.' + + field :pipeline_schedule, + Types::Ci::PipelineScheduleType, + description: 'Updated pipeline schedule.' + + def resolve(id:, variables: [], **pipeline_schedule_attrs) + schedule = authorized_find!(id: id) + + params = pipeline_schedule_attrs.merge(variables_attributes: variables.map(&:to_h)) + + service_response = ::Ci::PipelineSchedules::UpdateService + .new(schedule, current_user, params) + .execute + + { + pipeline_schedule: schedule, + errors: service_response.errors + } + end + end + end + end +end diff --git a/app/graphql/mutations/ci/project_ci_cd_settings_update.rb b/app/graphql/mutations/ci/project_ci_cd_settings_update.rb index 934d62e92cf..d214aa46cfc 100644 --- a/app/graphql/mutations/ci/project_ci_cd_settings_update.rb +++ b/app/graphql/mutations/ci/project_ci_cd_settings_update.rb @@ -27,6 +27,10 @@ module Mutations description: 'Indicates CI/CD job tokens generated in other projects ' \ 'have restricted access to this project.' + argument :opt_in_jwt, GraphQL::Types::Boolean, + required: false, + description: 'When disabled, the JSON Web Token is always available in all jobs in the pipeline.' + field :ci_cd_settings, Types::Ci::CiCdSettingType, null: false, diff --git a/app/graphql/mutations/concerns/mutations/assignable.rb b/app/graphql/mutations/concerns/mutations/assignable.rb index 86f37207a2d..189c926fcc4 100644 --- a/app/graphql/mutations/concerns/mutations/assignable.rb +++ b/app/graphql/mutations/concerns/mutations/assignable.rb @@ -33,7 +33,7 @@ module Mutations def assign!(resource, users, operation_mode) update_service_class.new( - project: resource.project, + **update_service_class.constructor_container_arg(resource.project), current_user: current_user, params: { assignee_ids: assignee_ids(resource, users, operation_mode) } ).execute(resource) diff --git a/app/graphql/mutations/concerns/mutations/work_items/widgetable.rb b/app/graphql/mutations/concerns/mutations/work_items/widgetable.rb index 508e1627032..3f32cd51ae7 100644 --- a/app/graphql/mutations/concerns/mutations/work_items/widgetable.rb +++ b/app/graphql/mutations/concerns/mutations/work_items/widgetable.rb @@ -7,7 +7,7 @@ module Mutations def extract_widget_params!(work_item_type, attributes) # Get the list of widgets for the work item's type to extract only the supported attributes - widget_keys = ::WorkItems::Type.available_widgets.map(&:api_symbol) + widget_keys = ::WorkItems::WidgetDefinition.available_widgets.map(&:api_symbol) widget_params = attributes.extract!(*widget_keys) not_supported_keys = widget_params.keys - work_item_type.widgets.map(&:api_symbol) diff --git a/app/graphql/mutations/issues/bulk_update.rb b/app/graphql/mutations/issues/bulk_update.rb new file mode 100644 index 00000000000..7f3d5f6ffb2 --- /dev/null +++ b/app/graphql/mutations/issues/bulk_update.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module Mutations + module Issues + class BulkUpdate < BaseMutation + graphql_name 'IssuesBulkUpdate' + + include Gitlab::Graphql::Authorize::AuthorizeResource + + MAX_ISSUES = 100 + + description 'Allows updating several properties for a set of issues. ' \ + 'Does nothing if the `bulk_update_issues_mutation` feature flag is disabled.' + + argument :parent_id, ::Types::GlobalIDType[::IssueParent], + required: true, + description: 'Global ID of the parent that the bulk update will be scoped to . ' \ + 'Example `IssueParentID` are `"gid://gitlab/Project/1"` and `"gid://gitlab/Group/1"`.' + + argument :ids, [::Types::GlobalIDType[::Issue]], + required: true, + description: 'Global ID array of the issues that will be updated. ' \ + "IDs that the user can\'t update will be ignored. A max of #{MAX_ISSUES} can be provided." + + argument :assignee_ids, [::Types::GlobalIDType[::User]], + required: false, + description: 'Global ID array of the users that will be assigned to the given issues. ' \ + 'Existing assignees will be replaced with the ones on this list.' + + argument :milestone_id, ::Types::GlobalIDType[::Milestone], + required: false, + description: 'Global ID of the milestone that will be assigned to the issues.' + + field :updated_issue_count, GraphQL::Types::Int, + null: true, + description: 'Number of issues that were successfully updated.' + + def ready?(**args) + if Feature.disabled?(:bulk_update_issues_mutation) + raise Gitlab::Graphql::Errors::ResourceNotAvailable, '`bulk_update_issues_mutation` feature flag is disabled.' + end + + if args[:ids].size > MAX_ISSUES + raise Gitlab::Graphql::Errors::ArgumentError, + format(_('No more than %{max_issues} issues can be updated at the same time'), max_issues: MAX_ISSUES) + end + + super + end + + def resolve(ids:, parent_id:, **attributes) + parent = find_parent!(parent_id) + + result = Issuable::BulkUpdateService.new( + parent, + current_user, + prepared_params(attributes, ids) + ).execute('issue') + + if result.success? + { updated_issue_count: result.payload[:count], errors: [] } + else + { errors: result.errors } + end + end + + private + + def find_parent!(parent_id) + parent = GitlabSchema.find_by_gid(parent_id).sync + raise_resource_not_available_error! unless current_user.can?("read_#{parent.to_ability_name}", parent) + + parent + end + + def prepared_params(attributes, ids) + prepared = { issuable_ids: model_ids_from(ids).uniq } + + global_id_arguments.each do |argument| + next unless attributes.key?(argument) + + prepared[argument] = model_ids_from(attributes[argument]) + end + + prepared.transform_keys(param_mappings) + end + + def param_mappings + {} + end + + def global_id_arguments + %i[assignee_ids milestone_id] + end + + def model_ids_from(attributes) + return if attributes.nil? + return attributes.map(&:model_id) if attributes.is_a?(Array) + + attributes.model_id + end + end + end +end + +Mutations::Issues::BulkUpdate.prepend_mod diff --git a/app/graphql/mutations/issues/create.rb b/app/graphql/mutations/issues/create.rb index 0389a482822..0c1acdf316e 100644 --- a/app/graphql/mutations/issues/create.rb +++ b/app/graphql/mutations/issues/create.rb @@ -83,7 +83,7 @@ module Mutations params = build_create_issue_params(attributes.merge(author_id: current_user.id), project) spam_params = ::Spam::SpamParams.new_from_request(request: context[:request]) - result = ::Issues::CreateService.new(project: project, current_user: current_user, params: params, spam_params: spam_params).execute + result = ::Issues::CreateService.new(container: project, current_user: current_user, params: params, spam_params: spam_params).execute check_spam_action_response!(result[:issue]) if result[:issue] diff --git a/app/graphql/mutations/issues/move.rb b/app/graphql/mutations/issues/move.rb index 63bc9dabbf9..ef3f70c78b9 100644 --- a/app/graphql/mutations/issues/move.rb +++ b/app/graphql/mutations/issues/move.rb @@ -18,7 +18,7 @@ module Mutations target_project = resolve_project(full_path: target_project_path).sync begin - moved_issue = ::Issues::MoveService.new(project: source_project, current_user: current_user).execute(issue, target_project) + moved_issue = ::Issues::MoveService.new(container: source_project, current_user: current_user).execute(issue, target_project) rescue ::Issues::MoveService::MoveError => e errors = e.message end diff --git a/app/graphql/mutations/issues/set_confidential.rb b/app/graphql/mutations/issues/set_confidential.rb index b795d66c16f..08578881a13 100644 --- a/app/graphql/mutations/issues/set_confidential.rb +++ b/app/graphql/mutations/issues/set_confidential.rb @@ -19,7 +19,7 @@ module Mutations # spam_params so a check can be performed. spam_params = ::Spam::SpamParams.new_from_request(request: context[:request]) - ::Issues::UpdateService.new(project: project, current_user: current_user, params: { confidential: confidential }, spam_params: spam_params) + ::Issues::UpdateService.new(container: project, current_user: current_user, params: { confidential: confidential }, spam_params: spam_params) .execute(issue) check_spam_action_response!(issue) diff --git a/app/graphql/mutations/issues/set_due_date.rb b/app/graphql/mutations/issues/set_due_date.rb index 70b76da4fcb..e361d241083 100644 --- a/app/graphql/mutations/issues/set_due_date.rb +++ b/app/graphql/mutations/issues/set_due_date.rb @@ -14,7 +14,7 @@ module Mutations issue = authorized_find!(project_path: project_path, iid: iid) project = issue.project - ::Issues::UpdateService.new(project: project, current_user: current_user, params: { due_date: due_date }) + ::Issues::UpdateService.new(container: project, current_user: current_user, params: { due_date: due_date }) .execute(issue) { diff --git a/app/graphql/mutations/issues/set_escalation_status.rb b/app/graphql/mutations/issues/set_escalation_status.rb index 4f3fcb4886d..13286034ada 100644 --- a/app/graphql/mutations/issues/set_escalation_status.rb +++ b/app/graphql/mutations/issues/set_escalation_status.rb @@ -17,7 +17,7 @@ module Mutations check_feature_availability!(issue) ::Issues::UpdateService.new( - project: project, + container: project, current_user: current_user, params: { escalation_status: { status: status } } ).execute(issue) diff --git a/app/graphql/mutations/issues/set_locked.rb b/app/graphql/mutations/issues/set_locked.rb index 93b31350bbf..86ad129f4cb 100644 --- a/app/graphql/mutations/issues/set_locked.rb +++ b/app/graphql/mutations/issues/set_locked.rb @@ -13,7 +13,7 @@ module Mutations def resolve(project_path:, iid:, locked:) issue = authorized_find!(project_path: project_path, iid: iid) - ::Issues::UpdateService.new(project: issue.project, current_user: current_user, params: { discussion_locked: locked }) + ::Issues::UpdateService.new(container: issue.project, current_user: current_user, params: { discussion_locked: locked }) .execute(issue) { diff --git a/app/graphql/mutations/issues/set_severity.rb b/app/graphql/mutations/issues/set_severity.rb index 4a24bfd18ef..68d7fb7d0c0 100644 --- a/app/graphql/mutations/issues/set_severity.rb +++ b/app/graphql/mutations/issues/set_severity.rb @@ -15,7 +15,7 @@ module Mutations issue = authorized_find!(project_path: project_path, iid: iid) project = issue.project - ::Issues::UpdateService.new(project: project, current_user: current_user, params: { severity: severity }) + ::Issues::UpdateService.new(container: project, current_user: current_user, params: { severity: severity }) .execute(issue) { diff --git a/app/graphql/mutations/issues/update.rb b/app/graphql/mutations/issues/update.rb index 6cab1214d24..b5af048dc07 100644 --- a/app/graphql/mutations/issues/update.rb +++ b/app/graphql/mutations/issues/update.rb @@ -31,6 +31,10 @@ module Mutations description: 'Close or reopen an issue.', required: false + argument :time_estimate, GraphQL::Types::String, + required: false, + description: 'Estimated time to complete the issue, or `0` to remove the current estimate.' + def resolve(project_path:, iid:, **args) issue = authorized_find!(project_path: project_path, iid: iid) project = issue.project @@ -38,7 +42,7 @@ module Mutations args = parse_arguments(args) spam_params = ::Spam::SpamParams.new_from_request(request: context[:request]) - ::Issues::UpdateService.new(project: project, current_user: current_user, params: args, spam_params: spam_params).execute(issue) + ::Issues::UpdateService.new(container: project, current_user: current_user, params: args, spam_params: spam_params).execute(issue) { issue: issue, @@ -46,11 +50,15 @@ module Mutations } end - def ready?(label_ids: [], add_label_ids: [], remove_label_ids: [], **args) + def ready?(label_ids: [], add_label_ids: [], remove_label_ids: [], time_estimate: nil, **args) if label_ids.any? && (add_label_ids.any? || remove_label_ids.any?) raise Gitlab::Graphql::Errors::ArgumentError, 'labelIds is mutually exclusive with any of addLabelIds or removeLabelIds' end + if !time_estimate.nil? && Gitlab::TimeTrackingFormatter.parse(time_estimate, keep_zero: true).nil? + raise Gitlab::Graphql::Errors::ArgumentError, 'timeEstimate must be formatted correctly, for example `1h 30m`' + end + super end @@ -61,6 +69,10 @@ module Mutations args[:remove_label_ids] = parse_label_ids(args[:remove_label_ids]) args[:label_ids] = parse_label_ids(args[:label_ids]) + unless args[:time_estimate].nil? + args[:time_estimate] = Gitlab::TimeTrackingFormatter.parse(args[:time_estimate], keep_zero: true) + end + args end diff --git a/app/graphql/mutations/merge_requests/set_milestone.rb b/app/graphql/mutations/merge_requests/set_milestone.rb index bf40c12aec5..320aa423ce3 100644 --- a/app/graphql/mutations/merge_requests/set_milestone.rb +++ b/app/graphql/mutations/merge_requests/set_milestone.rb @@ -17,7 +17,7 @@ module Mutations merge_request = authorized_find!(project_path: project_path, iid: iid) project = merge_request.project - ::MergeRequests::UpdateService.new(project: project, current_user: current_user, params: { milestone: milestone }) + ::MergeRequests::UpdateService.new(project: project, current_user: current_user, params: { milestone_id: milestone&.id }) .execute(merge_request) { diff --git a/app/graphql/mutations/merge_requests/update.rb b/app/graphql/mutations/merge_requests/update.rb index 0f4923e15a1..da4db7342a3 100644 --- a/app/graphql/mutations/merge_requests/update.rb +++ b/app/graphql/mutations/merge_requests/update.rb @@ -24,12 +24,16 @@ module Mutations as: :state_event, description: 'Action to perform to change the state.' + argument :time_estimate, GraphQL::Types::String, + required: false, + description: 'Estimated time to complete the merge request, or `0` to remove the current estimate.' + def resolve(project_path:, iid:, **args) merge_request = authorized_find!(project_path: project_path, iid: iid) - attributes = args.compact + args = parse_arguments(args) ::MergeRequests::UpdateService - .new(project: merge_request.project, current_user: current_user, params: attributes) + .new(project: merge_request.project, current_user: current_user, params: args) .execute(merge_request) errors = errors_on_object(merge_request) @@ -39,6 +43,25 @@ module Mutations errors: errors } end + + def ready?(time_estimate: nil, **args) + if !time_estimate.nil? && Gitlab::TimeTrackingFormatter.parse(time_estimate, keep_zero: true).nil? + raise Gitlab::Graphql::Errors::ArgumentError, + 'timeEstimate must be formatted correctly, for example `1h 30m`' + end + + super + end + + private + + def parse_arguments(args) + unless args[:time_estimate].nil? + args[:time_estimate] = Gitlab::TimeTrackingFormatter.parse(args[:time_estimate], keep_zero: true) + end + + args.compact + end end end end diff --git a/app/graphql/mutations/work_items/create.rb b/app/graphql/mutations/work_items/create.rb index a4efffb69c1..9f124de7ab2 100644 --- a/app/graphql/mutations/work_items/create.rb +++ b/app/graphql/mutations/work_items/create.rb @@ -48,7 +48,7 @@ module Mutations widget_params = extract_widget_params!(type, params) create_result = ::WorkItems::CreateService.new( - project: project, + container: project, current_user: current_user, params: params, spam_params: spam_params, diff --git a/app/graphql/mutations/work_items/delete.rb b/app/graphql/mutations/work_items/delete.rb index 4b0067d40d4..ec0244fa65e 100644 --- a/app/graphql/mutations/work_items/delete.rb +++ b/app/graphql/mutations/work_items/delete.rb @@ -20,7 +20,7 @@ module Mutations work_item = authorized_find!(id: id) result = ::WorkItems::DeleteService.new( - project: work_item.project, + container: work_item.project, current_user: current_user ).execute(work_item) diff --git a/app/graphql/mutations/work_items/update.rb b/app/graphql/mutations/work_items/update.rb index 04c63d8e876..db6af38d82e 100644 --- a/app/graphql/mutations/work_items/update.rb +++ b/app/graphql/mutations/work_items/update.rb @@ -22,8 +22,10 @@ module Mutations spam_params = ::Spam::SpamParams.new_from_request(request: context[:request]) widget_params = extract_widget_params!(work_item.work_item_type, attributes) + interpret_quick_actions!(work_item, current_user, widget_params, attributes) + update_result = ::WorkItems::UpdateService.new( - project: work_item.project, + container: work_item.project, current_user: current_user, params: attributes, widget_params: widget_params, @@ -43,6 +45,37 @@ module Mutations def find_object(id:) GitlabSchema.find_by_gid(id) end + + def interpret_quick_actions!(work_item, current_user, widget_params, attributes = {}) + return unless work_item.work_item_type.widgets.include?(::WorkItems::Widgets::Description) + + description_param = widget_params[::WorkItems::Widgets::Description.api_symbol] + return unless description_param + + original_description = description_param.fetch(:description, work_item.description) + + description, command_params = QuickActions::InterpretService + .new(work_item.project, current_user, {}) + .execute(original_description, work_item) + + description_param[:description] = description if description && description != original_description + + # Widgets have a set of quick action params that they must process. + # Map them to widget_params so they can be picked up by widget services. + work_item.work_item_type.widgets + .filter { |widget| widget.respond_to?(:quick_action_params) } + .each do |widget| + widget.quick_action_params + .filter { |param_name| command_params.key?(param_name) } + .each do |param_name| + widget_params[widget.api_symbol] ||= {} + widget_params[widget.api_symbol][param_name] = command_params.delete(param_name) + end + end + + # The command_params not processed by widgets (e.g. title) should be placed in 'attributes'. + attributes.merge!(command_params || {}) + end end end end diff --git a/app/graphql/mutations/work_items/update_task.rb b/app/graphql/mutations/work_items/update_task.rb index aeb4f1d0f06..8dcc4c325ea 100644 --- a/app/graphql/mutations/work_items/update_task.rb +++ b/app/graphql/mutations/work_items/update_task.rb @@ -32,7 +32,7 @@ module Mutations spam_params = ::Spam::SpamParams.new_from_request(request: context[:request]) ::WorkItems::UpdateService.new( - project: task.project, + container: task.project, current_user: current_user, params: task_data_hash.except(:id), spam_params: spam_params |