From 9f46488805e86b1bc341ea1620b866016c2ce5ed Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 20 May 2020 14:34:42 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-0-stable-ee --- app/graphql/mutations/alert_management/base.rb | 40 ++++++++ .../alert_management/create_alert_issue.rb | 30 ++++++ .../alert_management/update_alert_status.rb | 35 +++++++ app/graphql/mutations/base_mutation.rb | 2 +- app/graphql/mutations/branches/create.rb | 51 ++++++++++ app/graphql/mutations/design_management/base.rb | 23 +++++ app/graphql/mutations/design_management/delete.rb | 66 +++++++++++++ app/graphql/mutations/design_management/upload.rb | 38 ++++++++ .../metrics/dashboard/annotations/create.rb | 106 +++++++++++++++++++++ app/graphql/mutations/snippets/base.rb | 2 + app/graphql/mutations/snippets/create.rb | 9 ++ 11 files changed, 401 insertions(+), 1 deletion(-) create mode 100644 app/graphql/mutations/alert_management/base.rb create mode 100644 app/graphql/mutations/alert_management/create_alert_issue.rb create mode 100644 app/graphql/mutations/alert_management/update_alert_status.rb create mode 100644 app/graphql/mutations/branches/create.rb create mode 100644 app/graphql/mutations/design_management/base.rb create mode 100644 app/graphql/mutations/design_management/delete.rb create mode 100644 app/graphql/mutations/design_management/upload.rb create mode 100644 app/graphql/mutations/metrics/dashboard/annotations/create.rb (limited to 'app/graphql/mutations') diff --git a/app/graphql/mutations/alert_management/base.rb b/app/graphql/mutations/alert_management/base.rb new file mode 100644 index 00000000000..ca2057d4845 --- /dev/null +++ b/app/graphql/mutations/alert_management/base.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Mutations + module AlertManagement + class Base < BaseMutation + include Mutations::ResolvesProject + + argument :project_path, GraphQL::ID_TYPE, + required: true, + description: "The project the alert to mutate is in" + + argument :iid, GraphQL::STRING_TYPE, + required: true, + description: "The iid of the alert to mutate" + + field :alert, + Types::AlertManagement::AlertType, + null: true, + description: "The alert after mutation" + + field :issue, + Types::IssueType, + null: true, + description: "The issue created after mutation" + + authorize :update_alert_management_alert + + private + + def find_object(project_path:, iid:) + project = resolve_project(full_path: project_path) + + return unless project + + resolver = Resolvers::AlertManagementAlertResolver.single.new(object: project, context: context, field: nil) + resolver.resolve(iid: iid) + end + end + end +end diff --git a/app/graphql/mutations/alert_management/create_alert_issue.rb b/app/graphql/mutations/alert_management/create_alert_issue.rb new file mode 100644 index 00000000000..adb048a4479 --- /dev/null +++ b/app/graphql/mutations/alert_management/create_alert_issue.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Mutations + module AlertManagement + class CreateAlertIssue < Base + graphql_name 'CreateAlertIssue' + + def resolve(args) + alert = authorized_find!(project_path: args[:project_path], iid: args[:iid]) + result = create_alert_issue(alert, current_user) + + prepare_response(alert, result) + end + + private + + def create_alert_issue(alert, user) + ::AlertManagement::CreateAlertIssueService.new(alert, user).execute + end + + def prepare_response(alert, result) + { + alert: alert, + issue: result.payload[:issue], + errors: Array(result.message) + } + end + end + end +end diff --git a/app/graphql/mutations/alert_management/update_alert_status.rb b/app/graphql/mutations/alert_management/update_alert_status.rb new file mode 100644 index 00000000000..e73a591378a --- /dev/null +++ b/app/graphql/mutations/alert_management/update_alert_status.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Mutations + module AlertManagement + class UpdateAlertStatus < Base + graphql_name 'UpdateAlertStatus' + + argument :status, Types::AlertManagement::StatusEnum, + required: true, + description: 'The status to set the alert' + + def resolve(args) + alert = authorized_find!(project_path: args[:project_path], iid: args[:iid]) + result = update_status(alert, args[:status]) + + prepare_response(result) + end + + private + + def update_status(alert, status) + ::AlertManagement::UpdateAlertStatusService + .new(alert, current_user, status) + .execute + end + + def prepare_response(result) + { + alert: result.payload[:alert], + errors: result.error? ? [result.message] : [] + } + end + end + end +end diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb index 623f7c27584..30510cfab50 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: "Reasons why the mutation failed." + 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 new file mode 100644 index 00000000000..127d5447d0a --- /dev/null +++ b/app/graphql/mutations/branches/create.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Mutations + module Branches + class Create < BaseMutation + include Mutations::ResolvesProject + + graphql_name 'CreateBranch' + + argument :project_path, GraphQL::ID_TYPE, + required: true, + description: 'Project full path the branch is associated with' + + argument :name, GraphQL::STRING_TYPE, + required: true, + description: 'Name of the branch' + + argument :ref, + GraphQL::STRING_TYPE, + required: true, + description: 'Branch name or commit SHA to create branch from' + + field :branch, + Types::BranchType, + null: true, + description: 'Branch after mutation' + + authorize :push_code + + def resolve(project_path:, name:, ref:) + project = authorized_find!(full_path: project_path) + + context.scoped_set!(:branch_project, project) + + result = ::Branches::CreateService.new(project, current_user) + .execute(name, ref) + + { + branch: (result[:branch] 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/design_management/base.rb b/app/graphql/mutations/design_management/base.rb new file mode 100644 index 00000000000..918e5709b94 --- /dev/null +++ b/app/graphql/mutations/design_management/base.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Mutations + module DesignManagement + class Base < ::Mutations::BaseMutation + include Mutations::ResolvesIssuable + + argument :project_path, GraphQL::ID_TYPE, + required: true, + description: "The project where the issue is to upload designs for" + + argument :iid, GraphQL::ID_TYPE, + required: true, + description: "The iid of the issue to modify designs for" + + private + + def find_object(project_path:, iid:) + resolve_issuable(type: :issue, parent_path: project_path, iid: iid) + end + end + end +end diff --git a/app/graphql/mutations/design_management/delete.rb b/app/graphql/mutations/design_management/delete.rb new file mode 100644 index 00000000000..d2ef2c9bcca --- /dev/null +++ b/app/graphql/mutations/design_management/delete.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module Mutations + module DesignManagement + class Delete < Base + Errors = ::Gitlab::Graphql::Errors + + graphql_name "DesignManagementDelete" + + argument :filenames, [GraphQL::STRING_TYPE], + required: true, + description: "The filenames of the designs to delete", + prepare: ->(names, _ctx) do + names.presence || (raise Errors::ArgumentError, 'no filenames') + end + + field :version, Types::DesignManagement::VersionType, + null: true, # null on error + description: 'The new version in which the designs are deleted' + + authorize :destroy_design + + def resolve(project_path:, iid:, filenames:) + issue = authorized_find!(project_path: project_path, iid: iid) + project = issue.project + designs = resolve_designs(issue, filenames) + + result = ::DesignManagement::DeleteDesignsService + .new(project, current_user, issue: issue, designs: designs) + .execute + + { + version: result[:version], + errors: Array.wrap(result[:message]) + } + end + + private + + # Here we check that: + # * we find exactly as many designs as filenames + def resolve_designs(issue, filenames) + designs = issue.design_collection.designs_by_filename(filenames) + + validate_all_were_found!(designs, filenames) + + designs + end + + def validate_all_were_found!(designs, filenames) + found_filenames = designs.map(&:filename) + missing = filenames.difference(found_filenames) + + if missing.present? + raise Errors::ArgumentError, <<~MSG + Not all the designs you named currently exist. + The following filenames were not found: + #{missing.join(', ')} + + They may have already been deleted. + MSG + end + end + end + end +end diff --git a/app/graphql/mutations/design_management/upload.rb b/app/graphql/mutations/design_management/upload.rb new file mode 100644 index 00000000000..1ed7f8e49e6 --- /dev/null +++ b/app/graphql/mutations/design_management/upload.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Mutations + module DesignManagement + class Upload < Base + graphql_name "DesignManagementUpload" + + argument :files, [ApolloUploadServer::Upload], + required: true, + description: "The files to upload" + + authorize :create_design + + field :designs, [Types::DesignManagement::DesignType], + null: false, + description: "The designs that were uploaded by the mutation" + + field :skipped_designs, [Types::DesignManagement::DesignType], + null: false, + description: "Any designs that were skipped from the upload due to there " \ + "being no change to their content since their last version" + + def resolve(project_path:, iid:, files:) + issue = authorized_find!(project_path: project_path, iid: iid) + project = issue.project + + result = ::DesignManagement::SaveDesignsService.new(project, current_user, issue: issue, files: files) + .execute + + { + designs: Array.wrap(result[:designs]), + skipped_designs: Array.wrap(result[:skipped_designs]), + errors: Array.wrap(result[:message]) + } + end + end + end +end diff --git a/app/graphql/mutations/metrics/dashboard/annotations/create.rb b/app/graphql/mutations/metrics/dashboard/annotations/create.rb new file mode 100644 index 00000000000..f99688aeac6 --- /dev/null +++ b/app/graphql/mutations/metrics/dashboard/annotations/create.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module Mutations + module Metrics + module Dashboard + module Annotations + class Create < BaseMutation + graphql_name 'CreateAnnotation' + + ANNOTATION_SOURCE_ARGUMENT_ERROR = 'Either a cluster or environment global id is required' + INVALID_ANNOTATION_SOURCE_ERROR = 'Invalid cluster or environment id' + + authorize :create_metrics_dashboard_annotation + + field :annotation, + Types::Metrics::Dashboards::AnnotationType, + null: true, + description: 'The created annotation' + + argument :environment_id, + GraphQL::ID_TYPE, + required: false, + description: 'The global id of the environment to add an annotation to' + + argument :cluster_id, + GraphQL::ID_TYPE, + required: false, + description: 'The global id of the cluster to add an annotation to' + + argument :starting_at, Types::TimeType, + required: true, + description: 'Timestamp indicating starting moment to which the annotation relates' + + argument :ending_at, Types::TimeType, + required: false, + description: 'Timestamp indicating ending moment to which the annotation relates' + + argument :dashboard_path, + GraphQL::STRING_TYPE, + required: true, + description: 'The path to a file defining the dashboard on which the annotation should be added' + + argument :description, + GraphQL::STRING_TYPE, + required: true, + description: 'The description of the annotation' + + AnnotationSource = Struct.new(:object, keyword_init: true) do + def type_keys + { 'Clusters::Cluster' => :cluster, 'Environment' => :environment } + end + + def klass + object.class.name + end + + def type + raise Gitlab::Graphql::Errors::ArgumentError, INVALID_ANNOTATION_SOURCE_ERROR unless type_keys[klass] + + type_keys[klass] + end + end + + def resolve(args) + annotation_response = ::Metrics::Dashboard::Annotations::CreateService.new(context[:current_user], annotation_create_params(args)).execute + + annotation = annotation_response[:annotation] + + { + annotation: annotation.valid? ? annotation : nil, + errors: errors_on_object(annotation) + } + end + + private + + def ready?(**args) + # Raise error if both cluster_id and environment_id are present or neither is present + unless args[:cluster_id].present? ^ args[:environment_id].present? + raise Gitlab::Graphql::Errors::ArgumentError, ANNOTATION_SOURCE_ARGUMENT_ERROR + end + + super(args) + end + + def find_object(id:) + GitlabSchema.object_from_id(id) + end + + def annotation_create_params(args) + annotation_source = AnnotationSource.new(object: annotation_source(args)) + + args[annotation_source.type] = annotation_source.object + + args + end + + def annotation_source(args) + annotation_source_id = args[:cluster_id] || args[:environment_id] + authorized_find!(id: annotation_source_id) + end + end + end + end + end +end diff --git a/app/graphql/mutations/snippets/base.rb b/app/graphql/mutations/snippets/base.rb index 9dc6d49774e..c8cc721b2e0 100644 --- a/app/graphql/mutations/snippets/base.rb +++ b/app/graphql/mutations/snippets/base.rb @@ -15,6 +15,8 @@ module Mutations end def authorized_resource?(snippet) + return false if snippet.nil? + Ability.allowed?(context[:current_user], ability_for(snippet), snippet) end diff --git a/app/graphql/mutations/snippets/create.rb b/app/graphql/mutations/snippets/create.rb index 266a123de82..6fc223fbee7 100644 --- a/app/graphql/mutations/snippets/create.rb +++ b/app/graphql/mutations/snippets/create.rb @@ -36,6 +36,10 @@ module Mutations required: false, description: 'The project full path the snippet is associated with' + argument :uploaded_files, [GraphQL::STRING_TYPE], + required: false, + description: 'The paths to files uploaded in the snippet description' + def resolve(args) project_path = args.delete(:project_path) @@ -45,9 +49,14 @@ module Mutations raise_resource_not_available_error! end + # We need to rename `uploaded_files` into `files` because + # it's the expected key param + args[:files] = args.delete(:uploaded_files) + service_response = ::Snippets::CreateService.new(project, context[:current_user], args).execute + snippet = service_response.payload[:snippet] { -- cgit v1.2.1