Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-05-13 18:08:23 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-13 18:08:23 +0300
commit868e4e69bba7d3ddc2bf4899ee45d6c377a8e536 (patch)
tree921098180de1fbf8e58cfaeade0d0999177b0ce6 /app/graphql
parent41e8b05e8d06f4b13a984e4a3ad26e9a48294543 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/graphql')
-rw-r--r--app/graphql/mutations/design_management/base.rb23
-rw-r--r--app/graphql/mutations/design_management/delete.rb66
-rw-r--r--app/graphql/mutations/design_management/upload.rb38
-rw-r--r--app/graphql/resolvers/design_management/design_at_version_resolver.rb46
-rw-r--r--app/graphql/resolvers/design_management/design_resolver.rb57
-rw-r--r--app/graphql/resolvers/design_management/designs_resolver.rb50
-rw-r--r--app/graphql/resolvers/design_management/version/design_at_version_resolver.rb95
-rw-r--r--app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb60
-rw-r--r--app/graphql/resolvers/design_management/version_in_collection_resolver.rb45
-rw-r--r--app/graphql/resolvers/design_management/version_resolver.rb25
-rw-r--r--app/graphql/resolvers/design_management/versions_resolver.rb76
-rw-r--r--app/graphql/types/design_management/design_at_version_type.rb37
-rw-r--r--app/graphql/types/design_management/design_collection_type.rb44
-rw-r--r--app/graphql/types/design_management/design_fields.rb78
-rw-r--r--app/graphql/types/design_management/design_type.rb44
-rw-r--r--app/graphql/types/design_management/design_version_event_enum.rb18
-rw-r--r--app/graphql/types/design_management/version_type.rb37
-rw-r--r--app/graphql/types/design_management_type.rb18
-rw-r--r--app/graphql/types/issue_type.rb8
-rw-r--r--app/graphql/types/mutation_type.rb2
-rw-r--r--app/graphql/types/notes/noteable_type.rb4
-rw-r--r--app/graphql/types/permission_types/issue.rb8
-rw-r--r--app/graphql/types/permission_types/project.rb4
-rw-r--r--app/graphql/types/query_type.rb11
-rw-r--r--app/graphql/types/todo_target_enum.rb1
25 files changed, 885 insertions, 10 deletions
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/resolvers/design_management/design_at_version_resolver.rb b/app/graphql/resolvers/design_management/design_at_version_resolver.rb
new file mode 100644
index 00000000000..fd9b349f974
--- /dev/null
+++ b/app/graphql/resolvers/design_management/design_at_version_resolver.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module DesignManagement
+ class DesignAtVersionResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ type Types::DesignManagement::DesignAtVersionType, null: false
+
+ authorize :read_design
+
+ argument :id, GraphQL::ID_TYPE,
+ required: true,
+ description: 'The Global ID of the design at this version'
+
+ def resolve(id:)
+ authorized_find!(id: id)
+ end
+
+ def find_object(id:)
+ dav = GitlabSchema.object_from_id(id, expected_type: ::DesignManagement::DesignAtVersion)
+ return unless consistent?(dav)
+
+ dav
+ end
+
+ def self.single
+ self
+ end
+
+ private
+
+ # If this resolver is mounted on something that has an issue
+ # (such as design collection for instance), then we should check
+ # that the DesignAtVersion as found by its ID does in fact belong
+ # to this issue.
+ def consistent?(dav)
+ issue.nil? || (dav&.design&.issue_id == issue.id)
+ end
+
+ def issue
+ object&.issue
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/design_management/design_resolver.rb b/app/graphql/resolvers/design_management/design_resolver.rb
new file mode 100644
index 00000000000..05bdbbbe407
--- /dev/null
+++ b/app/graphql/resolvers/design_management/design_resolver.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module DesignManagement
+ class DesignResolver < BaseResolver
+ argument :id, GraphQL::ID_TYPE,
+ required: false,
+ description: 'Find a design by its ID'
+
+ argument :filename, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Find a design by its filename'
+
+ def resolve(filename: nil, id: nil)
+ params = parse_args(filename, id)
+
+ build_finder(params).execute.first
+ end
+
+ def self.single
+ self
+ end
+
+ private
+
+ def issue
+ object.issue
+ end
+
+ def build_finder(params)
+ ::DesignManagement::DesignsFinder.new(issue, current_user, params)
+ end
+
+ def error(msg)
+ raise ::Gitlab::Graphql::Errors::ArgumentError, msg
+ end
+
+ def parse_args(filename, id)
+ provided = [filename, id].map(&:present?)
+
+ if provided.none?
+ error('one of id or filename must be passed')
+ elsif provided.all?
+ error('only one of id or filename may be passed')
+ elsif filename.present?
+ { filenames: [filename] }
+ else
+ { ids: [parse_gid(id)] }
+ end
+ end
+
+ def parse_gid(gid)
+ GitlabSchema.parse_gid(gid, expected_type: ::DesignManagement::Design).model_id
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/design_management/designs_resolver.rb b/app/graphql/resolvers/design_management/designs_resolver.rb
new file mode 100644
index 00000000000..81f94d5cb30
--- /dev/null
+++ b/app/graphql/resolvers/design_management/designs_resolver.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module DesignManagement
+ class DesignsResolver < BaseResolver
+ argument :ids,
+ [GraphQL::ID_TYPE],
+ required: false,
+ description: 'Filters designs by their ID'
+ argument :filenames,
+ [GraphQL::STRING_TYPE],
+ required: false,
+ description: 'Filters designs by their filename'
+ argument :at_version,
+ GraphQL::ID_TYPE,
+ required: false,
+ description: 'Filters designs to only those that existed at the version. ' \
+ 'If argument is omitted or nil then all designs will reflect the latest version'
+
+ def self.single
+ ::Resolvers::DesignManagement::DesignResolver
+ end
+
+ def resolve(ids: nil, filenames: nil, at_version: nil)
+ ::DesignManagement::DesignsFinder.new(
+ issue,
+ current_user,
+ ids: design_ids(ids),
+ filenames: filenames,
+ visible_at_version: version(at_version),
+ order: :id
+ ).execute
+ end
+
+ private
+
+ def version(at_version)
+ GitlabSchema.object_from_id(at_version)&.sync if at_version
+ end
+
+ def design_ids(ids)
+ ids&.map { |id| GlobalID.parse(id).model_id }
+ end
+
+ def issue
+ object.issue
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/design_management/version/design_at_version_resolver.rb b/app/graphql/resolvers/design_management/version/design_at_version_resolver.rb
new file mode 100644
index 00000000000..03f7908780c
--- /dev/null
+++ b/app/graphql/resolvers/design_management/version/design_at_version_resolver.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module DesignManagement
+ module Version
+ # Resolver for a DesignAtVersion object given an implicit version context
+ class DesignAtVersionResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ type Types::DesignManagement::DesignAtVersionType, null: true
+
+ authorize :read_design
+
+ argument :id, GraphQL::ID_TYPE,
+ required: false,
+ as: :design_at_version_id,
+ description: 'The ID of the DesignAtVersion'
+ argument :design_id, GraphQL::ID_TYPE,
+ required: false,
+ description: 'The ID of a specific design'
+ argument :filename, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'The filename of a specific design'
+
+ def self.single
+ self
+ end
+
+ def resolve(design_id: nil, filename: nil, design_at_version_id: nil)
+ validate_arguments(design_id, filename, design_at_version_id)
+
+ return unless Ability.allowed?(current_user, :read_design, issue)
+ return specific_design_at_version(design_at_version_id) if design_at_version_id
+
+ find(design_id, filename).map { |d| make(d) }.first
+ end
+
+ private
+
+ def validate_arguments(design_id, filename, design_at_version_id)
+ args = { filename: filename, id: design_at_version_id, design_id: design_id }
+ passed = args.compact.keys
+
+ return if passed.size == 1
+
+ msg = "Exactly one of #{args.keys.join(', ')} expected, got #{passed}"
+
+ raise Gitlab::Graphql::Errors::ArgumentError, msg
+ end
+
+ def specific_design_at_version(id)
+ dav = GitlabSchema.object_from_id(id, expected_type: ::DesignManagement::DesignAtVersion)
+ return unless consistent?(dav)
+
+ dav
+ end
+
+ # Test that the DAV found by ID actually belongs on this version, and
+ # that it is visible at this version.
+ def consistent?(dav)
+ return false unless dav.present?
+
+ dav.design.issue_id == issue.id &&
+ dav.version.id == version.id &&
+ dav.design.visible_in?(version)
+ end
+
+ def find(id, filename)
+ ids = [parse_design_id(id).model_id] if id
+ filenames = [filename] if filename
+
+ ::DesignManagement::DesignsFinder
+ .new(issue, current_user, ids: ids, filenames: filenames, visible_at_version: version)
+ .execute
+ end
+
+ def parse_design_id(id)
+ GitlabSchema.parse_gid(id, expected_type: ::DesignManagement::Design)
+ end
+
+ def issue
+ version.issue
+ end
+
+ def version
+ object
+ end
+
+ def make(design)
+ ::DesignManagement::DesignAtVersion.new(design: design, version: version)
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb b/app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb
new file mode 100644
index 00000000000..5ccb2f3e311
--- /dev/null
+++ b/app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module DesignManagement
+ module Version
+ # Resolver for DesignAtVersion objects given an implicit version context
+ class DesignsAtVersionResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ type Types::DesignManagement::DesignAtVersionType, null: true
+
+ authorize :read_design
+
+ argument :ids,
+ [GraphQL::ID_TYPE],
+ required: false,
+ description: 'Filters designs by their ID'
+ argument :filenames,
+ [GraphQL::STRING_TYPE],
+ required: false,
+ description: 'Filters designs by their filename'
+
+ def self.single
+ ::Resolvers::DesignManagement::Version::DesignAtVersionResolver
+ end
+
+ def resolve(ids: nil, filenames: nil)
+ find(ids, filenames).execute.map { |d| make(d) }
+ end
+
+ private
+
+ def find(ids, filenames)
+ ids = ids&.map { |id| parse_design_id(id).model_id }
+
+ ::DesignManagement::DesignsFinder.new(issue, current_user,
+ ids: ids,
+ filenames: filenames,
+ visible_at_version: version)
+ end
+
+ def parse_design_id(id)
+ GitlabSchema.parse_gid(id, expected_type: ::DesignManagement::Design)
+ end
+
+ def issue
+ version.issue
+ end
+
+ def version
+ object
+ end
+
+ def make(design)
+ ::DesignManagement::DesignAtVersion.new(design: design, version: version)
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/design_management/version_in_collection_resolver.rb b/app/graphql/resolvers/design_management/version_in_collection_resolver.rb
new file mode 100644
index 00000000000..9e729172881
--- /dev/null
+++ b/app/graphql/resolvers/design_management/version_in_collection_resolver.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module DesignManagement
+ class VersionInCollectionResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ type Types::DesignManagement::VersionType, null: true
+
+ authorize :read_design
+
+ alias_method :collection, :object
+
+ argument :sha, GraphQL::STRING_TYPE,
+ required: false,
+ description: "The SHA256 of a specific version"
+ argument :id, GraphQL::ID_TYPE,
+ required: false,
+ description: 'The Global ID of the version'
+
+ def resolve(id: nil, sha: nil)
+ check_args(id, sha)
+
+ gid = GitlabSchema.parse_gid(id, expected_type: ::DesignManagement::Version) if id
+
+ ::DesignManagement::VersionsFinder
+ .new(collection, current_user, sha: sha, version_id: gid&.model_id)
+ .execute
+ .first
+ end
+
+ def self.single
+ self
+ end
+
+ private
+
+ def check_args(id, sha)
+ return if id.present? || sha.present?
+
+ raise ::Gitlab::Graphql::Errors::ArgumentError, 'one of id or sha is required'
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/design_management/version_resolver.rb b/app/graphql/resolvers/design_management/version_resolver.rb
new file mode 100644
index 00000000000..b0e0843e6c8
--- /dev/null
+++ b/app/graphql/resolvers/design_management/version_resolver.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module DesignManagement
+ class VersionResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ type Types::DesignManagement::VersionType, null: true
+
+ authorize :read_design
+
+ argument :id, GraphQL::ID_TYPE,
+ required: true,
+ description: 'The Global ID of the version'
+
+ def resolve(id:)
+ authorized_find!(id: id)
+ end
+
+ def find_object(id:)
+ GitlabSchema.object_from_id(id, expected_type: ::DesignManagement::Version)
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/design_management/versions_resolver.rb b/app/graphql/resolvers/design_management/versions_resolver.rb
new file mode 100644
index 00000000000..a62258dad5c
--- /dev/null
+++ b/app/graphql/resolvers/design_management/versions_resolver.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module DesignManagement
+ class VersionsResolver < BaseResolver
+ type Types::DesignManagement::VersionType.connection_type, null: false
+
+ alias_method :design_or_collection, :object
+
+ argument :earlier_or_equal_to_sha, GraphQL::STRING_TYPE,
+ as: :sha,
+ required: false,
+ description: 'The SHA256 of the most recent acceptable version'
+
+ argument :earlier_or_equal_to_id, GraphQL::ID_TYPE,
+ as: :id,
+ required: false,
+ description: 'The Global ID of the most recent acceptable version'
+
+ # This resolver has a custom singular resolver
+ def self.single
+ ::Resolvers::DesignManagement::VersionInCollectionResolver
+ end
+
+ def resolve(parent: nil, id: nil, sha: nil)
+ version = cutoff(parent, id, sha)
+
+ raise ::Gitlab::Graphql::Errors::ResourceNotAvailable, 'cutoff not found' unless version.present?
+
+ if version == :unconstrained
+ find
+ else
+ find(earlier_or_equal_to: version)
+ end
+ end
+
+ private
+
+ # Find the most recent version that the client will accept
+ def cutoff(parent, id, sha)
+ if sha.present? || id.present?
+ specific_version(id, sha)
+ elsif at_version = at_version_arg(parent)
+ by_id(at_version)
+ else
+ :unconstrained
+ end
+ end
+
+ def specific_version(id, sha)
+ gid = GitlabSchema.parse_gid(id, expected_type: ::DesignManagement::Version) if id
+ find(sha: sha, version_id: gid&.model_id).first
+ end
+
+ def find(**params)
+ ::DesignManagement::VersionsFinder
+ .new(design_or_collection, current_user, params)
+ .execute
+ end
+
+ def by_id(id)
+ GitlabSchema.object_from_id(id, expected_type: ::DesignManagement::Version).sync
+ end
+
+ # Find an `at_version` argument passed to a parent node.
+ #
+ # If one is found, then a design collection further up the AST
+ # has been filtered to reflect designs at that version, and so
+ # for consistency we should only present versions up to the given
+ # version here.
+ def at_version_arg(parent)
+ ::Gitlab::Graphql::FindArgumentInParent.find(parent, :at_version, limit_depth: 4)
+ end
+ 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
new file mode 100644
index 00000000000..343d4cf4ff4
--- /dev/null
+++ b/app/graphql/types/design_management/design_at_version_type.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Types
+ module DesignManagement
+ class DesignAtVersionType < BaseObject
+ graphql_name 'DesignAtVersion'
+
+ description 'A design pinned to a specific version. ' \
+ 'The image field reflects the design as of the associated version.'
+
+ authorize :read_design
+
+ delegate :design, :version, to: :object
+ delegate :issue, :filename, :full_path, :diff_refs, to: :design
+
+ implements ::Types::DesignManagement::DesignFields
+
+ field :version,
+ Types::DesignManagement::VersionType,
+ null: false,
+ description: 'The version this design-at-versions is pinned to'
+
+ field :design,
+ Types::DesignManagement::DesignType,
+ null: false,
+ description: 'The underlying design.'
+
+ def cached_stateful_version(_parent)
+ version
+ end
+
+ def notes_count
+ design.user_notes_count
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/design_management/design_collection_type.rb b/app/graphql/types/design_management/design_collection_type.rb
new file mode 100644
index 00000000000..194910831c6
--- /dev/null
+++ b/app/graphql/types/design_management/design_collection_type.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Types
+ module DesignManagement
+ class DesignCollectionType < BaseObject
+ graphql_name 'DesignCollection'
+ description 'A collection of designs.'
+
+ authorize :read_design
+
+ field :project, Types::ProjectType, null: false,
+ description: 'Project associated with the design collection'
+ field :issue, Types::IssueType, null: false,
+ description: 'Issue associated with the design collection'
+
+ field :designs,
+ Types::DesignManagement::DesignType.connection_type,
+ null: false,
+ resolver: Resolvers::DesignManagement::DesignsResolver,
+ description: 'All designs for the design collection',
+ complexity: 5
+
+ field :versions,
+ Types::DesignManagement::VersionType.connection_type,
+ resolver: Resolvers::DesignManagement::VersionsResolver,
+ description: 'All versions related to all designs, ordered newest first'
+
+ field :version,
+ Types::DesignManagement::VersionType,
+ resolver: Resolvers::DesignManagement::VersionsResolver.single,
+ description: 'A specific version'
+
+ field :design_at_version, ::Types::DesignManagement::DesignAtVersionType,
+ null: true,
+ resolver: ::Resolvers::DesignManagement::DesignAtVersionResolver,
+ description: 'Find a design as of a version'
+
+ field :design, ::Types::DesignManagement::DesignType,
+ null: true,
+ resolver: ::Resolvers::DesignManagement::DesignResolver,
+ description: 'Find a specific design'
+ end
+ end
+end
diff --git a/app/graphql/types/design_management/design_fields.rb b/app/graphql/types/design_management/design_fields.rb
new file mode 100644
index 00000000000..b03b3927392
--- /dev/null
+++ b/app/graphql/types/design_management/design_fields.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Types
+ module DesignManagement
+ module DesignFields
+ include BaseInterface
+
+ field_class Types::BaseField
+
+ field :id, GraphQL::ID_TYPE, description: 'The ID of this design', null: false
+ field :project, Types::ProjectType, null: false, description: 'The project the design belongs to'
+ field :issue, Types::IssueType, null: false, description: 'The issue the design belongs to'
+ field :filename, GraphQL::STRING_TYPE, null: false, description: 'The filename of the design'
+ field :full_path, GraphQL::STRING_TYPE, null: false, description: 'The full path to the design file'
+ field :image, GraphQL::STRING_TYPE, null: false, extras: [:parent], description: 'The URL of the full-sized image'
+ field :image_v432x230, GraphQL::STRING_TYPE, null: true, extras: [:parent],
+ description: 'The URL of the design resized to fit within the bounds of 432x230. ' \
+ 'This will be `null` if the image has not been generated'
+ field :diff_refs, Types::DiffRefsType,
+ null: false,
+ calls_gitaly: true,
+ extras: [:parent],
+ description: 'The diff refs for this design'
+ field :event, Types::DesignManagement::DesignVersionEventEnum,
+ null: false,
+ extras: [:parent],
+ description: 'How this design was changed in the current version'
+ field :notes_count,
+ GraphQL::INT_TYPE,
+ null: false,
+ method: :user_notes_count,
+ description: 'The total count of user-created notes for this design'
+
+ def diff_refs(parent:)
+ version = cached_stateful_version(parent)
+ version.diff_refs
+ end
+
+ def image(parent:)
+ sha = cached_stateful_version(parent).sha
+
+ Gitlab::UrlBuilder.build(design, ref: sha)
+ end
+
+ def image_v432x230(parent:)
+ version = cached_stateful_version(parent)
+ action = design.actions.up_to_version(version).most_recent.first
+
+ # A `nil` return value indicates that the image has not been processed
+ return unless action.image_v432x230.file
+
+ Gitlab::UrlBuilder.build(design, ref: version.sha, size: :v432x230)
+ end
+
+ def event(parent:)
+ version = cached_stateful_version(parent)
+
+ action = cached_actions_for_version(version)[design.id]
+
+ action&.event || ::Types::DesignManagement::DesignVersionEventEnum::NONE
+ end
+
+ def cached_actions_for_version(version)
+ Gitlab::SafeRequestStore.fetch(['DesignFields', 'actions_for_version', version.id]) do
+ version.actions.to_h { |dv| [dv.design_id, dv] }
+ end
+ end
+
+ def project
+ ::Gitlab::Graphql::Loaders::BatchModelLoader.new(::Project, design.project_id).find
+ end
+
+ def issue
+ ::Gitlab::Graphql::Loaders::BatchModelLoader.new(::Issue, design.issue_id).find
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/design_management/design_type.rb b/app/graphql/types/design_management/design_type.rb
new file mode 100644
index 00000000000..3c84dc151bd
--- /dev/null
+++ b/app/graphql/types/design_management/design_type.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Types
+ module DesignManagement
+ class DesignType < BaseObject
+ graphql_name 'Design'
+ description 'A single design'
+
+ authorize :read_design
+
+ alias_method :design, :object
+
+ implements(Types::Notes::NoteableType)
+ implements(Types::DesignManagement::DesignFields)
+
+ field :versions,
+ Types::DesignManagement::VersionType.connection_type,
+ resolver: Resolvers::DesignManagement::VersionsResolver,
+ description: "All versions related to this design ordered newest first",
+ extras: [:parent]
+
+ # Returns a `DesignManagement::Version` for this query based on the
+ # `atVersion` argument passed to a parent node if present, or otherwise
+ # the most recent `Version` for the issue.
+ def cached_stateful_version(parent_node)
+ version_gid = Gitlab::Graphql::FindArgumentInParent.find(parent_node, :at_version)
+
+ # Caching is scoped to an `issue_id` to allow us to cache the
+ # most recent `Version` for an issue
+ Gitlab::SafeRequestStore.fetch([request_cache_base_key, 'stateful_version', object.issue_id, version_gid]) do
+ if version_gid
+ GitlabSchema.object_from_id(version_gid)&.sync
+ else
+ object.issue.design_versions.most_recent
+ end
+ end
+ end
+
+ def request_cache_base_key
+ self.class.name
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/design_management/design_version_event_enum.rb b/app/graphql/types/design_management/design_version_event_enum.rb
new file mode 100644
index 00000000000..ea4bc1ffbfa
--- /dev/null
+++ b/app/graphql/types/design_management/design_version_event_enum.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Types
+ module DesignManagement
+ class DesignVersionEventEnum < BaseEnum
+ graphql_name 'DesignVersionEvent'
+ description 'Mutation event of a design within a version'
+
+ NONE = 'NONE'
+
+ value NONE, 'No change'
+
+ ::DesignManagement::Action.events.keys.each do |event_name|
+ value event_name.upcase, value: event_name, description: "A #{event_name} event"
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/design_management/version_type.rb b/app/graphql/types/design_management/version_type.rb
new file mode 100644
index 00000000000..c774f5d1bdf
--- /dev/null
+++ b/app/graphql/types/design_management/version_type.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Types
+ module DesignManagement
+ class VersionType < ::Types::BaseObject
+ # Just `Version` might be a bit to general to expose globally so adding
+ # a `Design` prefix to specify the class exposed in GraphQL
+ graphql_name 'DesignVersion'
+
+ description 'A specific version in which designs were added, modified or deleted'
+
+ authorize :read_design
+
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: 'ID of the design version'
+ field :sha, GraphQL::ID_TYPE, null: false,
+ description: 'SHA of the design version'
+
+ field :designs,
+ ::Types::DesignManagement::DesignType.connection_type,
+ null: false,
+ description: 'All designs that were changed in the version'
+
+ field :designs_at_version,
+ ::Types::DesignManagement::DesignAtVersionType.connection_type,
+ null: false,
+ description: 'All designs that are visible at this version, as of this version',
+ resolver: ::Resolvers::DesignManagement::Version::DesignsAtVersionResolver
+
+ field :design_at_version,
+ ::Types::DesignManagement::DesignAtVersionType,
+ null: false,
+ description: 'A particular design as of this version, provided it is visible at this version',
+ resolver: ::Resolvers::DesignManagement::Version::DesignsAtVersionResolver.single
+ end
+ end
+end
diff --git a/app/graphql/types/design_management_type.rb b/app/graphql/types/design_management_type.rb
new file mode 100644
index 00000000000..ec85b8a0c1f
--- /dev/null
+++ b/app/graphql/types/design_management_type.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# rubocop: disable Graphql/AuthorizeTypes
+module Types
+ class DesignManagementType < BaseObject
+ graphql_name 'DesignManagement'
+
+ field :version, ::Types::DesignManagement::VersionType,
+ null: true,
+ resolver: ::Resolvers::DesignManagement::VersionResolver,
+ description: 'Find a version'
+
+ field :design_at_version, ::Types::DesignManagement::DesignAtVersionType,
+ null: true,
+ resolver: ::Resolvers::DesignManagement::DesignAtVersionResolver,
+ description: 'Find a design as of a version'
+ end
+end
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index 11850e5865f..73219ca9e1e 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -85,6 +85,14 @@ module Types
field :task_completion_status, Types::TaskCompletionStatus, null: false,
description: 'Task completion status of the issue'
+
+ field :designs, Types::DesignManagement::DesignCollectionType, null: true,
+ method: :design_collection,
+ deprecated: { reason: 'Use `designCollection`', milestone: '12.2' },
+ description: 'The designs associated with this issue'
+
+ field :design_collection, Types::DesignManagement::DesignCollectionType, null: true,
+ description: 'Collection of design images associated with this issue'
end
end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index b18a3968a03..6e1bc962cd2 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -42,6 +42,8 @@ module Types
mount_mutation Mutations::Snippets::Create
mount_mutation Mutations::Snippets::MarkAsSpam
mount_mutation Mutations::JiraImport::Start
+ mount_mutation Mutations::DesignManagement::Upload, calls_gitaly: true
+ mount_mutation Mutations::DesignManagement::Delete, calls_gitaly: true
end
end
diff --git a/app/graphql/types/notes/noteable_type.rb b/app/graphql/types/notes/noteable_type.rb
index 2ac66452841..187c9109f8c 100644
--- a/app/graphql/types/notes/noteable_type.rb
+++ b/app/graphql/types/notes/noteable_type.rb
@@ -17,6 +17,8 @@ module Types
Types::MergeRequestType
when Snippet
Types::SnippetType
+ when ::DesignManagement::Design
+ Types::DesignManagement::DesignType
else
raise "Unknown GraphQL type for #{object}"
end
@@ -25,5 +27,3 @@ module Types
end
end
end
-
-Types::Notes::NoteableType.extend_if_ee('::EE::Types::Notes::NoteableType')
diff --git a/app/graphql/types/permission_types/issue.rb b/app/graphql/types/permission_types/issue.rb
index e26c5950e73..94e1bffd685 100644
--- a/app/graphql/types/permission_types/issue.rb
+++ b/app/graphql/types/permission_types/issue.rb
@@ -6,11 +6,9 @@ module Types
description 'Check permissions for the current user on a issue'
graphql_name 'IssuePermissions'
- abilities :read_issue, :admin_issue,
- :update_issue, :create_note,
- :reopen_issue
+ abilities :read_issue, :admin_issue, :update_issue, :reopen_issue,
+ :read_design, :create_design, :destroy_design,
+ :create_note
end
end
end
-
-Types::PermissionTypes::Issue.prepend_if_ee('::EE::Types::PermissionTypes::Issue')
diff --git a/app/graphql/types/permission_types/project.rb b/app/graphql/types/permission_types/project.rb
index f773fce0c63..5747e63d195 100644
--- a/app/graphql/types/permission_types/project.rb
+++ b/app/graphql/types/permission_types/project.rb
@@ -17,7 +17,7 @@ module Types
:admin_wiki, :admin_project, :update_pages,
:admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki,
:create_pages, :destroy_pages, :read_pages_content, :admin_operations,
- :read_merge_request
+ :read_merge_request, :read_design, :create_design, :destroy_design
permission_field :create_snippet
@@ -27,5 +27,3 @@ module Types
end
end
end
-
-Types::PermissionTypes::Project.prepend_if_ee('EE::Types::PermissionTypes::Project')
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index e0479c8227b..70cdcb62bc6 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -4,6 +4,9 @@ module Types
class QueryType < ::Types::BaseObject
graphql_name 'Query'
+ # The design management context object needs to implement #issue
+ DesignManagementObject = Struct.new(:issue)
+
field :project, Types::ProjectType,
null: true,
resolver: Resolvers::ProjectResolver,
@@ -40,9 +43,17 @@ module Types
resolver: Resolvers::SnippetsResolver,
description: 'Find Snippets visible to the current user'
+ field :design_management, Types::DesignManagementType,
+ null: false,
+ description: 'Fields related to design management'
+
field :echo, GraphQL::STRING_TYPE, null: false,
description: 'Text to echo back',
resolver: Resolvers::EchoResolver
+
+ def design_management
+ DesignManagementObject.new(nil)
+ end
end
end
diff --git a/app/graphql/types/todo_target_enum.rb b/app/graphql/types/todo_target_enum.rb
index 8358a86b35c..a377c3aafdc 100644
--- a/app/graphql/types/todo_target_enum.rb
+++ b/app/graphql/types/todo_target_enum.rb
@@ -5,6 +5,7 @@ module Types
value 'COMMIT', value: 'Commit', description: 'A Commit'
value 'ISSUE', value: 'Issue', description: 'An Issue'
value 'MERGEREQUEST', value: 'MergeRequest', description: 'A MergeRequest'
+ value 'DESIGN', value: 'DesignManagement::Design', description: 'A Design'
end
end