diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-13 18:08:23 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-13 18:08:23 +0300 |
commit | 868e4e69bba7d3ddc2bf4899ee45d6c377a8e536 (patch) | |
tree | 921098180de1fbf8e58cfaeade0d0999177b0ce6 /app/graphql | |
parent | 41e8b05e8d06f4b13a984e4a3ad26e9a48294543 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/graphql')
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 |