diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-20 14:34:42 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-20 14:34:42 +0000 |
commit | 9f46488805e86b1bc341ea1620b866016c2ce5ed (patch) | |
tree | f9748c7e287041e37d6da49e0a29c9511dc34768 /app/graphql/resolvers | |
parent | dfc92d081ea0332d69c8aca2f0e745cb48ae5e6d (diff) | |
download | gitlab-ce-9f46488805e86b1bc341ea1620b866016c2ce5ed.tar.gz |
Add latest changes from gitlab-org/gitlab@13-0-stable-ee
Diffstat (limited to 'app/graphql/resolvers')
19 files changed, 677 insertions, 5 deletions
diff --git a/app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb b/app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb new file mode 100644 index 00000000000..7f4346632ca --- /dev/null +++ b/app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Resolvers + module AlertManagement + class AlertStatusCountsResolver < BaseResolver + type Types::AlertManagement::AlertStatusCountsType, null: true + + def resolve(**args) + ::Gitlab::AlertManagement::AlertStatusCounts.new(context[:current_user], object, args) + end + end + end +end diff --git a/app/graphql/resolvers/alert_management_alert_resolver.rb b/app/graphql/resolvers/alert_management_alert_resolver.rb new file mode 100644 index 00000000000..51ebbb96476 --- /dev/null +++ b/app/graphql/resolvers/alert_management_alert_resolver.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Resolvers + class AlertManagementAlertResolver < BaseResolver + argument :iid, GraphQL::STRING_TYPE, + required: false, + description: 'IID of the alert. For example, "1"' + + argument :statuses, [Types::AlertManagement::StatusEnum], + as: :status, + required: false, + description: 'Alerts with the specified statues. For example, [TRIGGERED]' + + argument :sort, Types::AlertManagement::AlertSortEnum, + description: 'Sort alerts by this criteria', + required: false + + argument :search, GraphQL::STRING_TYPE, + description: 'Search criteria for filtering alerts. This will search on title, description, service, monitoring_tool.', + required: false + + type Types::AlertManagement::AlertType, null: true + + def resolve(**args) + parent = object.respond_to?(:sync) ? object.sync : object + return ::AlertManagement::Alert.none if parent.nil? + + ::AlertManagement::AlertsFinder.new(context[:current_user], parent, args).execute + end + end +end diff --git a/app/graphql/resolvers/board_lists_resolver.rb b/app/graphql/resolvers/board_lists_resolver.rb new file mode 100644 index 00000000000..f8d62ba86af --- /dev/null +++ b/app/graphql/resolvers/board_lists_resolver.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Resolvers + class BoardListsResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + type Types::BoardListType, null: true + + alias_method :board, :object + + def resolve(lookahead: nil) + authorize!(board) + + lists = board_lists + + if load_preferences?(lookahead) + List.preload_preferences_for_user(lists, context[:current_user]) + end + + Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection.new(lists) + end + + private + + def board_lists + service = Boards::Lists::ListService.new(board.resource_parent, context[:current_user]) + service.execute(board, create_default_lists: false) + end + + def authorized_resource?(board) + Ability.allowed?(context[:current_user], :read_list, board) + end + + def load_preferences?(lookahead) + lookahead&.selection(:edges)&.selection(:node)&.selects?(:collapsed) + end + end +end diff --git a/app/graphql/resolvers/branch_commit_resolver.rb b/app/graphql/resolvers/branch_commit_resolver.rb new file mode 100644 index 00000000000..11c49e17bc5 --- /dev/null +++ b/app/graphql/resolvers/branch_commit_resolver.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Resolvers + class BranchCommitResolver < BaseResolver + type Types::CommitType, null: true + + alias_method :branch, :object + + def resolve(**args) + return unless branch + + commit = branch.dereferenced_target + + ::Commit.new(commit, context[:branch_project]) if commit + 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/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb index 04da54a6bb6..f103da07666 100644 --- a/app/graphql/resolvers/issues_resolver.rb +++ b/app/graphql/resolvers/issues_resolver.rb @@ -52,6 +52,10 @@ module Resolvers type Types::IssueType, null: true + NON_STABLE_CURSOR_SORTS = %i[priority_asc priority_desc + label_priority_asc label_priority_desc + milestone_due_asc milestone_due_desc].freeze + def resolve(**args) # The project could have been loaded in batch by `BatchLoader`. # At this point we need the `id` of the project to query for issues, so @@ -70,7 +74,15 @@ module Resolvers args[:iids] ||= [args[:iid]].compact args[:attempt_project_search_optimizations] = args[:search].present? - IssuesFinder.new(context[:current_user], args).execute + issues = IssuesFinder.new(context[:current_user], args).execute + + if non_stable_cursor_sort?(args[:sort]) + # Certain complex sorts are not supported by the stable cursor pagination yet. + # In these cases, we use offset pagination, so we return the correct connection. + Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection.new(issues) + else + issues + end end def self.resolver_complexity(args, child_complexity:) @@ -79,5 +91,9 @@ module Resolvers complexity end + + def non_stable_cursor_sort?(sort) + NON_STABLE_CURSOR_SORTS.include?(sort) + end end end diff --git a/app/graphql/resolvers/metrics/dashboards/annotation_resolver.rb b/app/graphql/resolvers/metrics/dashboards/annotation_resolver.rb index 068323a3073..2dd224bb17b 100644 --- a/app/graphql/resolvers/metrics/dashboards/annotation_resolver.rb +++ b/app/graphql/resolvers/metrics/dashboards/annotation_resolver.rb @@ -18,7 +18,6 @@ module Resolvers def resolve(**args) return [] unless dashboard - return [] unless Feature.enabled?(:metrics_dashboard_annotations, dashboard.environment&.project) ::Metrics::Dashboards::AnnotationsFinder.new(dashboard: dashboard, params: args).execute end diff --git a/app/graphql/resolvers/milestone_resolver.rb b/app/graphql/resolvers/milestone_resolver.rb index 2e7b6fdfd5f..6c6513e0ee4 100644 --- a/app/graphql/resolvers/milestone_resolver.rb +++ b/app/graphql/resolvers/milestone_resolver.rb @@ -9,6 +9,10 @@ module Resolvers required: false, description: 'Filter milestones by state' + argument :include_descendants, GraphQL::BOOLEAN_TYPE, + required: false, + description: 'Return also milestones in all subgroups and subprojects' + type Types::MilestoneType, null: true def resolve(**args) @@ -26,16 +30,16 @@ module Resolvers state: args[:state] || 'all', start_date: args[:start_date], end_date: args[:end_date] - }.merge(parent_id_parameter) + }.merge(parent_id_parameter(args)) end def parent @parent ||= object.respond_to?(:sync) ? object.sync : object end - def parent_id_parameter + def parent_id_parameter(args) if parent.is_a?(Group) - { group_ids: parent.id } + group_parameters(args) elsif parent.is_a?(Project) { project_ids: parent.id } end @@ -46,5 +50,26 @@ module Resolvers def authorize! Ability.allowed?(context[:current_user], :read_milestone, parent) || raise_resource_not_available_error! end + + def group_parameters(args) + return { group_ids: parent.id } unless include_descendants?(args) + + { + group_ids: parent.self_and_descendants.public_or_visible_to_user(current_user).select(:id), + project_ids: group_projects.with_issues_or_mrs_available_for_user(current_user) + } + end + + def include_descendants?(args) + args[:include_descendants].present? && Feature.enabled?(:group_milestone_descendants, parent) + end + + def group_projects + GroupProjectsFinder.new( + group: parent, + current_user: current_user, + options: { include_subgroups: true } + ).execute + end end end diff --git a/app/graphql/resolvers/namespace_projects_resolver.rb b/app/graphql/resolvers/namespace_projects_resolver.rb index f5b60f91be6..e841132eea7 100644 --- a/app/graphql/resolvers/namespace_projects_resolver.rb +++ b/app/graphql/resolvers/namespace_projects_resolver.rb @@ -29,3 +29,5 @@ module Resolvers end end end + +Resolvers::NamespaceProjectsResolver.prepend_if_ee('::EE::Resolvers::NamespaceProjectsResolver') diff --git a/app/graphql/resolvers/projects_resolver.rb b/app/graphql/resolvers/projects_resolver.rb new file mode 100644 index 00000000000..068546cd39f --- /dev/null +++ b/app/graphql/resolvers/projects_resolver.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Resolvers + class ProjectsResolver < BaseResolver + type Types::ProjectType, null: true + + argument :membership, GraphQL::BOOLEAN_TYPE, + required: false, + description: 'Limit projects that the current user is a member of' + + argument :search, GraphQL::STRING_TYPE, + required: false, + description: 'Search criteria' + + def resolve(**args) + ProjectsFinder + .new(current_user: current_user, params: project_finder_params(args)) + .execute + end + + private + + def project_finder_params(params) + { + without_deleted: true, + non_public: params[:membership], + search: params[:search] + }.compact + end + end +end diff --git a/app/graphql/resolvers/release_resolver.rb b/app/graphql/resolvers/release_resolver.rb new file mode 100644 index 00000000000..9bae8b8cd13 --- /dev/null +++ b/app/graphql/resolvers/release_resolver.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Resolvers + class ReleaseResolver < BaseResolver + type Types::ReleaseType, null: true + + argument :tag_name, GraphQL::STRING_TYPE, + required: true, + description: 'The name of the tag associated to the release' + + alias_method :project, :object + + def self.single + self + end + + def resolve(tag_name:) + ReleasesFinder.new( + project, + current_user, + { tag: tag_name } + ).execute.first + end + end +end diff --git a/app/graphql/resolvers/releases_resolver.rb b/app/graphql/resolvers/releases_resolver.rb new file mode 100644 index 00000000000..b2afbb92684 --- /dev/null +++ b/app/graphql/resolvers/releases_resolver.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Resolvers + class ReleasesResolver < BaseResolver + type Types::ReleaseType.connection_type, null: true + + alias_method :project, :object + + # This resolver has a custom singular resolver + def self.single + Resolvers::ReleaseResolver + end + + def resolve(**args) + ReleasesFinder.new( + project, + current_user + ).execute + end + end +end |