diff options
Diffstat (limited to 'app/graphql/resolvers')
62 files changed, 610 insertions, 120 deletions
diff --git a/app/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver.rb b/app/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver.rb index aea3afa8ec5..9bac9f222ab 100644 --- a/app/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver.rb +++ b/app/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver.rb @@ -13,10 +13,20 @@ module Resolvers required: true, description: 'The type of measurement/statistics to retrieve' - def resolve(identifier:) + argument :recorded_after, Types::TimeType, + required: false, + description: 'Measurement recorded after this date' + + argument :recorded_before, Types::TimeType, + required: false, + description: 'Measurement recorded before this date' + + def resolve(identifier:, recorded_before: nil, recorded_after: nil) authorize! ::Analytics::InstanceStatistics::Measurement + .recorded_after(recorded_after) + .recorded_before(recorded_before) .with_identifier(identifier) .order_by_latest end diff --git a/app/graphql/resolvers/alert_management/alert_resolver.rb b/app/graphql/resolvers/alert_management/alert_resolver.rb index dc9b1dbb5f4..c3219d9cdc3 100644 --- a/app/graphql/resolvers/alert_management/alert_resolver.rb +++ b/app/graphql/resolvers/alert_management/alert_resolver.rb @@ -19,7 +19,7 @@ module Resolvers required: false argument :search, GraphQL::STRING_TYPE, - description: 'Search criteria for filtering alerts. This will search on title, description, service, monitoring_tool.', + description: 'Search query for title, description, service, or monitoring_tool.', required: false argument :assignee_username, GraphQL::STRING_TYPE, diff --git a/app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb b/app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb index 96ea4610aff..8fc0f9fd1ff 100644 --- a/app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb +++ b/app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb @@ -6,7 +6,7 @@ module Resolvers type Types::AlertManagement::AlertStatusCountsType, null: true argument :search, GraphQL::STRING_TYPE, - description: 'Search criteria for filtering alerts. This will search on title, description, service, monitoring_tool.', + description: 'Search query for title, description, service, or monitoring_tool.', required: false argument :assignee_username, GraphQL::STRING_TYPE, diff --git a/app/graphql/resolvers/alert_management/integrations_resolver.rb b/app/graphql/resolvers/alert_management/integrations_resolver.rb new file mode 100644 index 00000000000..4d1fe367277 --- /dev/null +++ b/app/graphql/resolvers/alert_management/integrations_resolver.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Resolvers + module AlertManagement + class IntegrationsResolver < BaseResolver + alias_method :project, :synchronized_object + + type Types::AlertManagement::IntegrationType.connection_type, null: true + + def resolve(**args) + http_integrations + prometheus_integrations + end + + private + + def prometheus_integrations + return [] unless Ability.allowed?(current_user, :admin_project, project) + + Array(project.prometheus_service) + end + + def http_integrations + return [] unless Ability.allowed?(current_user, :admin_operations, project) + + ::AlertManagement::HttpIntegrationsFinder.new(project, {}).execute + end + end + end +end diff --git a/app/graphql/resolvers/assigned_merge_requests_resolver.rb b/app/graphql/resolvers/assigned_merge_requests_resolver.rb index 172a8e298ad..30415ef5d2d 100644 --- a/app/graphql/resolvers/assigned_merge_requests_resolver.rb +++ b/app/graphql/resolvers/assigned_merge_requests_resolver.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true module Resolvers - class AssignedMergeRequestsResolver < UserMergeRequestsResolver + class AssignedMergeRequestsResolver < UserMergeRequestsResolverBase + type ::Types::MergeRequestType.connection_type, null: true accept_author def user_role diff --git a/app/graphql/resolvers/authored_merge_requests_resolver.rb b/app/graphql/resolvers/authored_merge_requests_resolver.rb index bc796f8685a..1426ca83c06 100644 --- a/app/graphql/resolvers/authored_merge_requests_resolver.rb +++ b/app/graphql/resolvers/authored_merge_requests_resolver.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true module Resolvers - class AuthoredMergeRequestsResolver < UserMergeRequestsResolver + class AuthoredMergeRequestsResolver < UserMergeRequestsResolverBase + type ::Types::MergeRequestType.connection_type, null: true accept_assignee def user_role diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb index 2b8854fb4d0..87a63231b22 100644 --- a/app/graphql/resolvers/base_resolver.rb +++ b/app/graphql/resolvers/base_resolver.rb @@ -8,32 +8,81 @@ module Resolvers argument_class ::Types::BaseArgument - def self.single - @single ||= Class.new(self) do - def ready?(**args) - ready, early_return = super - [ready, select_result(early_return)] - end + def self.singular_type + return unless type - def resolve(**args) - select_result(super) - end + unwrapped = type.unwrap + + %i[node_type relay_node_type of_type itself].reduce(nil) do |t, m| + t || unwrapped.try(m) + end + end - def single? - true + def self.when_single(&block) + as_single << block + + # Have we been called after defining the single version of this resolver? + if @single.present? + @single.instance_exec(&block) + end + end + + def self.as_single + @as_single ||= [] + end + + def self.single_definition_blocks + ancestors.flat_map { |klass| klass.try(:as_single) || [] } + end + + def self.single + @single ||= begin + parent = self + klass = Class.new(self) do + type parent.singular_type, null: true + + def ready?(**args) + ready, early_return = super + [ready, select_result(early_return)] + end + + def resolve(**args) + select_result(super) + end + + def single? + true + end + + def select_result(results) + results&.first + end + + define_singleton_method :to_s do + "#{parent}.single" + end end - def select_result(results) - results&.first + single_definition_blocks.each do |definition| + klass.instance_exec(&definition) end + + klass end end def self.last + parent = self @last ||= Class.new(self.single) do + type parent.singular_type, null: true + def select_result(results) results&.last end + + define_singleton_method :to_s do + "#{parent}.last" + end end end @@ -68,14 +117,13 @@ module Resolvers end end + # TODO: remove! This should never be necessary + # Remove as part of https://gitlab.com/gitlab-org/gitlab/-/issues/13984, + # since once we use that authorization approach, the object is guaranteed to + # be synchronized before any field. def synchronized_object strong_memoize(:synchronized_object) do - case object - when BatchLoader::GraphQL - object.sync - else - object - end + ::Gitlab::Graphql::Lazy.force(object) end end diff --git a/app/graphql/resolvers/board_lists_resolver.rb b/app/graphql/resolvers/board_lists_resolver.rb index 3384b37e2ce..ef12dfa19ff 100644 --- a/app/graphql/resolvers/board_lists_resolver.rb +++ b/app/graphql/resolvers/board_lists_resolver.rb @@ -7,7 +7,7 @@ module Resolvers type Types::BoardListType, null: true - argument :id, GraphQL::ID_TYPE, + argument :id, Types::GlobalIDType[List], required: false, description: 'Find a list by its global ID' diff --git a/app/graphql/resolvers/boards_resolver.rb b/app/graphql/resolvers/boards_resolver.rb index 82efd92d33f..42b6ce03118 100644 --- a/app/graphql/resolvers/boards_resolver.rb +++ b/app/graphql/resolvers/boards_resolver.rb @@ -4,7 +4,7 @@ module Resolvers class BoardsResolver < BaseResolver type Types::BoardType, null: true - argument :id, GraphQL::ID_TYPE, + argument :id, ::Types::GlobalIDType[::Board], required: false, description: 'Find a board by its ID' @@ -23,10 +23,13 @@ module Resolvers private - def extract_board_id(gid) - return unless gid.present? + def extract_board_id(id) + return unless id.present? - GitlabSchema.parse_gid(gid, expected_type: ::Board).model_id + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = Types::GlobalIDType[Board].coerce_isolated_input(id) + id.model_id end end end diff --git a/app/graphql/resolvers/ci/jobs_resolver.rb b/app/graphql/resolvers/ci/jobs_resolver.rb new file mode 100644 index 00000000000..8a9ae42b375 --- /dev/null +++ b/app/graphql/resolvers/ci/jobs_resolver.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Resolvers + module Ci + class JobsResolver < BaseResolver + alias_method :pipeline, :object + + argument :security_report_types, [Types::Security::ReportTypeEnum], + required: false, + description: 'Filter jobs by the type of security report they produce' + + def resolve(security_report_types: []) + if security_report_types.present? + ::Security::SecurityJobsFinder.new( + pipeline: pipeline, + job_types: security_report_types + ).execute + else + pipeline.statuses + end + end + end + end +end diff --git a/app/graphql/resolvers/ci/runner_setup_resolver.rb b/app/graphql/resolvers/ci/runner_setup_resolver.rb new file mode 100644 index 00000000000..241cd57f74b --- /dev/null +++ b/app/graphql/resolvers/ci/runner_setup_resolver.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module Resolvers + module Ci + class RunnerSetupResolver < BaseResolver + type Types::Ci::RunnerSetupType, null: true + + argument :platform, GraphQL::STRING_TYPE, + required: true, + description: 'Platform to generate the instructions for' + + argument :architecture, GraphQL::STRING_TYPE, + required: true, + description: 'Architecture to generate the instructions for' + + argument :project_id, ::Types::GlobalIDType[::Project], + required: false, + description: 'Project to register the runner for' + + argument :group_id, ::Types::GlobalIDType[::Group], + required: false, + description: 'Group to register the runner for' + + def resolve(platform:, architecture:, **args) + instructions = Gitlab::Ci::RunnerInstructions.new( + { current_user: current_user, os: platform, arch: architecture }.merge(target_param(args)) + ) + + { + install_instructions: instructions.install_script || other_install_instructions(platform), + register_instructions: instructions.register_command + } + ensure + raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'User is not authorized to register a runner for the specified resource!' if instructions.errors.include?('Gitlab::Access::AccessDeniedError') + end + + private + + def other_install_instructions(platform) + Gitlab::Ci::RunnerInstructions::OTHER_ENVIRONMENTS[platform.to_sym][:installation_instructions_url] + end + + def target_param(args) + project_param(args[:project_id]) || group_param(args[:group_id]) || {} + end + + def project_param(project_id) + return unless project_id + + { project: find_object(project_id) } + end + + def group_param(group_id) + return unless group_id + + { group: find_object(group_id) } + end + + def find_object(gid) + GlobalID::Locator.locate(gid) + end + end + end +end diff --git a/app/graphql/resolvers/commit_pipelines_resolver.rb b/app/graphql/resolvers/commit_pipelines_resolver.rb index 92a83523593..40af392200c 100644 --- a/app/graphql/resolvers/commit_pipelines_resolver.rb +++ b/app/graphql/resolvers/commit_pipelines_resolver.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true +# rubocop: disable Graphql/ResolverType module Resolvers class CommitPipelinesResolver < BaseResolver + # The GraphQL type here gets defined in this include include ::ResolvesPipelines alias_method :commit, :object @@ -11,3 +13,4 @@ module Resolvers end end end +# rubocop: enable Graphql/ResolverType diff --git a/app/graphql/resolvers/concerns/caching_array_resolver.rb b/app/graphql/resolvers/concerns/caching_array_resolver.rb new file mode 100644 index 00000000000..4f2c8b98928 --- /dev/null +++ b/app/graphql/resolvers/concerns/caching_array_resolver.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +# Concern that will eliminate N+1 queries for size-constrained +# collections of items. +# +# **note**: The resolver will never load more items than +# `@field.max_page_size` if defined, falling back to +# `context.schema.default_max_page_size`. +# +# provided that: +# +# - the query can be uniquely determined by the object and the arguments +# - the model class includes FromUnion +# - the model class defines a scalar primary key +# +# This comes at the cost of returning arrays, not relations, so we don't get +# any keyset pagination goodness. Consequently, this is only suitable for small-ish +# result sets, as the full result set will be loaded into memory. +# +# To enforce this, the resolver limits the size of result sets to +# `@field.max_page_size || context.schema.default_max_page_size`. +# +# **important**: If the cardinality of your collection is likely to be greater than 100, +# then you will want to pass `max_page_size:` as part of the field definition +# or (ideally) as part of the resolver `field_options`. +# +# How to implement: +# -------------------- +# +# Each including class operates on two generic parameters, A and R: +# - A is any Object that can be used as a Hash key. Instances of A +# are returned by `query_input` and then passed to `query_for`. +# - R is any subclass of ApplicationRecord that includes FromUnion. +# R must have a single scalar primary_key +# +# Classes must implement: +# - #model_class -> Class[R]. (Must respond to :primary_key, and :from_union) +# - #query_input(**kwargs) -> A (Must be hashable) +# - #query_for(A) -> ActiveRecord::Relation[R] +# +# Note the relationship between query_input and query_for, one of which +# consumes the input of the other +# (i.e. `resolve(**args).sync == query_for(query_input(**args)).to_a`). +# +# Classes may implement: +# - #item_found(A, R) (return value is ignored) +# - max_union_size Integer (the maximum number of queries to run in any one union) +module CachingArrayResolver + MAX_UNION_SIZE = 50 + + def resolve(**args) + key = query_input(**args) + + BatchLoader::GraphQL.for(key).batch(**batch) do |keys, loader| + if keys.size == 1 + # We can avoid the union entirely. + k = keys.first + limit(query_for(k)).each { |item| found(loader, k, item) } + else + queries = keys.map { |key| query_for(key) } + + queries.in_groups_of(max_union_size, false).each do |group| + by_id = model_class + .from_union(tag(group), remove_duplicates: false) + .group_by { |r| r[primary_key] } + + by_id.values.each do |item_group| + item = item_group.first + item_group.map(&:union_member_idx).each do |i| + found(loader, keys[i], item) + end + end + end + end + end + end + + # Override this to intercept the items once they are found + def item_found(query_input, item) + end + + def max_union_size + MAX_UNION_SIZE + end + + private + + def primary_key + @primary_key ||= (model_class.primary_key || raise("No primary key for #{model_class}")) + end + + def batch + { key: self.class, default_value: [] } + end + + def found(loader, key, value) + loader.call(key) do |vs| + item_found(key, value) + vs << value + end + end + + # Tag each row returned from each query with a the index of which query in + # the union it comes from. This lets us map the results back to the cache key. + def tag(queries) + queries.each_with_index.map do |q, i| + limit(q.select(all_fields, member_idx(i))) + end + end + + def limit(query) + query.limit(query_limit) # rubocop: disable CodeReuse/ActiveRecord + end + + def all_fields + model_class.arel_table[Arel.star] + end + + # rubocop: disable Graphql/Descriptions (false positive!) + def query_limit + field&.max_page_size.presence || context.schema.default_max_page_size + end + # rubocop: enable Graphql/Descriptions + + def member_idx(idx) + ::Arel::Nodes::SqlLiteral.new(idx.to_s).as('union_member_idx') + end +end diff --git a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb index fe6fa0bb262..4715b867ecb 100644 --- a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb +++ b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb @@ -29,7 +29,7 @@ module IssueResolverArguments description: 'Usernames of users assigned to the issue' argument :assignee_id, GraphQL::STRING_TYPE, required: false, - description: 'ID of a user assigned to the issues, "none" and "any" values supported' + description: 'ID of a user assigned to the issues, "none" and "any" values are supported' argument :created_before, Types::TimeType, required: false, description: 'Issues created before this date' diff --git a/app/graphql/resolvers/concerns/looks_ahead.rb b/app/graphql/resolvers/concerns/looks_ahead.rb index 61f23920ebb..d468047b539 100644 --- a/app/graphql/resolvers/concerns/looks_ahead.rb +++ b/app/graphql/resolvers/concerns/looks_ahead.rb @@ -4,6 +4,7 @@ module LooksAhead extend ActiveSupport::Concern included do + extras [:lookahead] attr_accessor :lookahead end diff --git a/app/graphql/resolvers/concerns/resolves_pipelines.rb b/app/graphql/resolvers/concerns/resolves_pipelines.rb index 46d9e174deb..f061f5f1606 100644 --- a/app/graphql/resolvers/concerns/resolves_pipelines.rb +++ b/app/graphql/resolvers/concerns/resolves_pipelines.rb @@ -4,7 +4,7 @@ module ResolvesPipelines extend ActiveSupport::Concern included do - type [Types::Ci::PipelineType], null: false + type Types::Ci::PipelineType.connection_type, null: false argument :status, Types::Ci::PipelineStatusEnum, required: false, diff --git a/app/graphql/resolvers/concerns/resolves_project.rb b/app/graphql/resolvers/concerns/resolves_project.rb index 3c5ce3dab01..b2ee7d7e850 100644 --- a/app/graphql/resolvers/concerns/resolves_project.rb +++ b/app/graphql/resolvers/concerns/resolves_project.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true module ResolvesProject + # Accepts EITHER one of + # - full_path: String (see Project#full_path) + # - project_id: GlobalID. Arguments should be typed as: `::Types::GlobalIDType[Project]` def resolve_project(full_path: nil, project_id: nil) unless full_path.present? ^ project_id.present? raise ::Gitlab::Graphql::Errors::ArgumentError, 'Incompatible arguments: projectId, projectPath.' diff --git a/app/graphql/resolvers/concerns/resolves_snippets.rb b/app/graphql/resolvers/concerns/resolves_snippets.rb index 483372bbf63..790ff4f774f 100644 --- a/app/graphql/resolvers/concerns/resolves_snippets.rb +++ b/app/graphql/resolvers/concerns/resolves_snippets.rb @@ -4,9 +4,9 @@ module ResolvesSnippets extend ActiveSupport::Concern included do - type Types::SnippetType, null: false + type Types::SnippetType.connection_type, null: false - argument :ids, [GraphQL::ID_TYPE], + argument :ids, [::Types::GlobalIDType[::Snippet]], required: false, description: 'Array of global snippet ids, e.g., "gid://gitlab/ProjectSnippet/1"' @@ -32,16 +32,15 @@ module ResolvesSnippets }.merge(options_by_type(args[:type])) end - def resolve_ids(ids) - Array.wrap(ids).map { |id| resolve_gid(id, :id) } - end - - def resolve_gid(gid, argument) - return unless gid.present? + def resolve_ids(ids, type = ::Types::GlobalIDType[::Snippet]) + Array.wrap(ids).map do |id| + next unless id.present? - GlobalID.parse(gid)&.model_id.tap do |id| - raise Gitlab::Graphql::Errors::ArgumentError, "Invalid global id format for param #{argument}" if id.nil? - end + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = type.coerce_isolated_input(id) + id.model_id + end.compact end def options_by_type(type) diff --git a/app/graphql/resolvers/container_repositories_resolver.rb b/app/graphql/resolvers/container_repositories_resolver.rb new file mode 100644 index 00000000000..b4b2893a3b8 --- /dev/null +++ b/app/graphql/resolvers/container_repositories_resolver.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Resolvers + class ContainerRepositoriesResolver < BaseResolver + include ::Mutations::PackageEventable + + type Types::ContainerRepositoryType, null: true + + argument :name, GraphQL::STRING_TYPE, + required: false, + description: 'Filter the container repositories by their name' + + def resolve(name: nil) + ContainerRepositoriesFinder.new(user: current_user, subject: object, params: { name: name }) + .execute + .tap { track_event(:list_repositories, :container) } + 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 index fd9b349f974..1b69efebe4e 100644 --- a/app/graphql/resolvers/design_management/design_at_version_resolver.rb +++ b/app/graphql/resolvers/design_management/design_at_version_resolver.rb @@ -9,7 +9,7 @@ module Resolvers authorize :read_design - argument :id, GraphQL::ID_TYPE, + argument :id, ::Types::GlobalIDType[::DesignManagement::DesignAtVersion], required: true, description: 'The Global ID of the design at this version' @@ -18,7 +18,10 @@ module Resolvers end def find_object(id:) - dav = GitlabSchema.object_from_id(id, expected_type: ::DesignManagement::DesignAtVersion) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = ::Types::GlobalIDType[::DesignManagement::DesignAtVersion].coerce_isolated_input(id) + dav = GitlabSchema.find_by_gid(id) return unless consistent?(dav) dav @@ -35,7 +38,7 @@ module Resolvers # 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) + issue.nil? || (dav.present? && dav.design&.issue_id == issue.id) end def issue diff --git a/app/graphql/resolvers/design_management/design_resolver.rb b/app/graphql/resolvers/design_management/design_resolver.rb index 05bdbbbe407..e0a68bae397 100644 --- a/app/graphql/resolvers/design_management/design_resolver.rb +++ b/app/graphql/resolvers/design_management/design_resolver.rb @@ -3,7 +3,9 @@ module Resolvers module DesignManagement class DesignResolver < BaseResolver - argument :id, GraphQL::ID_TYPE, + type ::Types::DesignManagement::DesignType, null: true + + argument :id, ::Types::GlobalIDType[::DesignManagement::Design], required: false, description: 'Find a design by its ID' @@ -50,7 +52,11 @@ module Resolvers end def parse_gid(gid) - GitlabSchema.parse_gid(gid, expected_type: ::DesignManagement::Design).model_id + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + gid = ::Types::GlobalIDType[::DesignManagement::Design].coerce_isolated_input(gid) + + gid.model_id end end end diff --git a/app/graphql/resolvers/design_management/designs_resolver.rb b/app/graphql/resolvers/design_management/designs_resolver.rb index 955ea6304e0..c588142ea6b 100644 --- a/app/graphql/resolvers/design_management/designs_resolver.rb +++ b/app/graphql/resolvers/design_management/designs_resolver.rb @@ -3,16 +3,18 @@ module Resolvers module DesignManagement class DesignsResolver < BaseResolver - argument :ids, - [GraphQL::ID_TYPE], + DesignID = ::Types::GlobalIDType[::DesignManagement::Design] + VersionID = ::Types::GlobalIDType[::DesignManagement::Version] + + type ::Types::DesignManagement::DesignType.connection_type, null: true + + argument :ids, [DesignID], required: false, description: 'Filters designs by their ID' - argument :filenames, - [GraphQL::STRING_TYPE], + argument :filenames, [GraphQL::STRING_TYPE], required: false, description: 'Filters designs by their filename' - argument :at_version, - GraphQL::ID_TYPE, + argument :at_version, VersionID, 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' @@ -36,11 +38,20 @@ module Resolvers def version(at_version) return unless at_version - GitlabSchema.object_from_id(at_version, expected_type: ::DesignManagement::Version)&.sync + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + at_version = VersionID.coerce_isolated_input(at_version) + # TODO: when we get promises use this to make resolve lazy + Gitlab::Graphql::Lazy.force(GitlabSchema.find_by_gid(at_version)) end - def design_ids(ids) - ids&.map { |id| GlobalID.parse(id, expected_type: ::DesignManagement::Design).model_id } + def design_ids(gids) + return if gids.nil? + + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + gids = gids.map { |id| DesignID.coerce_isolated_input(id) } + gids.map(&:model_id) end def issue 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 index 03f7908780c..70021057f71 100644 --- a/app/graphql/resolvers/design_management/version/design_at_version_resolver.rb +++ b/app/graphql/resolvers/design_management/version/design_at_version_resolver.rb @@ -5,17 +5,20 @@ module Resolvers module Version # Resolver for a DesignAtVersion object given an implicit version context class DesignAtVersionResolver < BaseResolver + DesignAtVersionID = ::Types::GlobalIDType[::DesignManagement::DesignAtVersion] + DesignID = ::Types::GlobalIDType[::DesignManagement::Design] + include Gitlab::Graphql::Authorize::AuthorizeResource type Types::DesignManagement::DesignAtVersionType, null: true authorize :read_design - argument :id, GraphQL::ID_TYPE, + argument :id, DesignAtVersionID, required: false, as: :design_at_version_id, description: 'The ID of the DesignAtVersion' - argument :design_id, GraphQL::ID_TYPE, + argument :design_id, DesignID, required: false, description: 'The ID of a specific design' argument :filename, GraphQL::STRING_TYPE, @@ -29,6 +32,11 @@ module Resolvers def resolve(design_id: nil, filename: nil, design_at_version_id: nil) validate_arguments(design_id, filename, design_at_version_id) + # TODO: remove this when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + design_id &&= DesignID.coerce_isolated_input(design_id) + design_at_version_id &&= DesignAtVersionID.coerce_isolated_input(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 @@ -49,7 +57,7 @@ module Resolvers end def specific_design_at_version(id) - dav = GitlabSchema.object_from_id(id, expected_type: ::DesignManagement::DesignAtVersion) + dav = GitlabSchema.find_by_gid(id) return unless consistent?(dav) dav @@ -65,8 +73,8 @@ module Resolvers dav.design.visible_in?(version) end - def find(id, filename) - ids = [parse_design_id(id).model_id] if id + def find(gid, filename) + ids = [gid.model_id] if gid filenames = [filename] if filename ::DesignManagement::DesignsFinder @@ -74,10 +82,6 @@ module Resolvers .execute end - def parse_design_id(id) - GitlabSchema.parse_gid(id, expected_type: ::DesignManagement::Design) - end - def issue version.issue 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 index 5ccb2f3e311..a129d8620d4 100644 --- a/app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb +++ b/app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb @@ -11,8 +11,9 @@ module Resolvers authorize :read_design - argument :ids, - [GraphQL::ID_TYPE], + DesignID = ::Types::GlobalIDType[::DesignManagement::Design] + + argument :ids, [DesignID], required: false, description: 'Filters designs by their ID' argument :filenames, @@ -31,16 +32,19 @@ module Resolvers private def find(ids, filenames) - ids = ids&.map { |id| parse_design_id(id).model_id } - ::DesignManagement::DesignsFinder.new(issue, current_user, - ids: ids, + ids: design_ids(ids), filenames: filenames, visible_at_version: version) end - def parse_design_id(id) - GitlabSchema.parse_gid(id, expected_type: ::DesignManagement::Design) + def design_ids(gids) + return if gids.nil? + + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + gids = gids.map { |id| DesignID.coerce_isolated_input(id) } + gids.map(&:model_id) end def issue diff --git a/app/graphql/resolvers/design_management/version_in_collection_resolver.rb b/app/graphql/resolvers/design_management/version_in_collection_resolver.rb index 9e729172881..ecd7ab3ee45 100644 --- a/app/graphql/resolvers/design_management/version_in_collection_resolver.rb +++ b/app/graphql/resolvers/design_management/version_in_collection_resolver.rb @@ -11,20 +11,25 @@ module Resolvers alias_method :collection, :object + VersionID = ::Types::GlobalIDType[::DesignManagement::Version] + argument :sha, GraphQL::STRING_TYPE, required: false, description: "The SHA256 of a specific version" - argument :id, GraphQL::ID_TYPE, + argument :id, VersionID, + as: :version_id, required: false, description: 'The Global ID of the version' - def resolve(id: nil, sha: nil) - check_args(id, sha) + def resolve(version_id: nil, sha: nil) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + version_id &&= VersionID.coerce_isolated_input(version_id) - gid = GitlabSchema.parse_gid(id, expected_type: ::DesignManagement::Version) if id + check_args(version_id, sha) ::DesignManagement::VersionsFinder - .new(collection, current_user, sha: sha, version_id: gid&.model_id) + .new(collection, current_user, sha: sha, version_id: version_id&.model_id) .execute .first end diff --git a/app/graphql/resolvers/design_management/version_resolver.rb b/app/graphql/resolvers/design_management/version_resolver.rb index b0e0843e6c8..1bc9c1a7cd6 100644 --- a/app/graphql/resolvers/design_management/version_resolver.rb +++ b/app/graphql/resolvers/design_management/version_resolver.rb @@ -9,7 +9,7 @@ module Resolvers authorize :read_design - argument :id, GraphQL::ID_TYPE, + argument :id, ::Types::GlobalIDType[::DesignManagement::Version], required: true, description: 'The Global ID of the version' @@ -18,7 +18,11 @@ module Resolvers end def find_object(id:) - GitlabSchema.object_from_id(id, expected_type: ::DesignManagement::Version) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = ::Types::GlobalIDType[::DesignManagement::Version].coerce_isolated_input(id) + + GitlabSchema.find_by_gid(id) end end end diff --git a/app/graphql/resolvers/design_management/versions_resolver.rb b/app/graphql/resolvers/design_management/versions_resolver.rb index a62258dad5c..23858c8e991 100644 --- a/app/graphql/resolvers/design_management/versions_resolver.rb +++ b/app/graphql/resolvers/design_management/versions_resolver.rb @@ -7,12 +7,14 @@ module Resolvers alias_method :design_or_collection, :object + VersionID = ::Types::GlobalIDType[::DesignManagement::Version] + 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, + argument :earlier_or_equal_to_id, VersionID, as: :id, required: false, description: 'The Global ID of the most recent acceptable version' @@ -23,6 +25,9 @@ module Resolvers end def resolve(parent: nil, id: nil, sha: nil) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id &&= VersionID.coerce_isolated_input(id) version = cutoff(parent, id, sha) raise ::Gitlab::Graphql::Errors::ResourceNotAvailable, 'cutoff not found' unless version.present? @@ -47,8 +52,7 @@ module Resolvers end end - def specific_version(id, sha) - gid = GitlabSchema.parse_gid(id, expected_type: ::DesignManagement::Version) if id + def specific_version(gid, sha) find(sha: sha, version_id: gid&.model_id).first end @@ -58,8 +62,8 @@ module Resolvers .execute end - def by_id(id) - GitlabSchema.object_from_id(id, expected_type: ::DesignManagement::Version).sync + def by_id(gid) + ::Gitlab::Graphql::Lazy.force(GitlabSchema.find_by_gid(gid)) end # Find an `at_version` argument passed to a parent node. @@ -69,7 +73,11 @@ module Resolvers # 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) + # TODO: remove coercion when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + version_id = ::Gitlab::Graphql::FindArgumentInParent.find(parent, :at_version, limit_depth: 4) + version_id &&= VersionID.coerce_isolated_input(version_id) + version_id end end end diff --git a/app/graphql/resolvers/echo_resolver.rb b/app/graphql/resolvers/echo_resolver.rb index fe0b1893a23..6b85b700712 100644 --- a/app/graphql/resolvers/echo_resolver.rb +++ b/app/graphql/resolvers/echo_resolver.rb @@ -2,15 +2,16 @@ module Resolvers class EchoResolver < BaseResolver + type ::GraphQL::STRING_TYPE, null: false description 'Testing endpoint to validate the API with' argument :text, GraphQL::STRING_TYPE, required: true, description: 'Text to echo back' - def resolve(**args) - username = context[:current_user]&.username + def resolve(text:) + username = current_user&.username - "#{username.inspect} says: #{args[:text]}" + "#{username.inspect} says: #{text}" end end end diff --git a/app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb b/app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb index 5027403e95c..09e76dba645 100644 --- a/app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb +++ b/app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb @@ -3,19 +3,22 @@ module Resolvers module ErrorTracking class SentryDetailedErrorResolver < BaseResolver - argument :id, GraphQL::ID_TYPE, + type Types::ErrorTracking::SentryDetailedErrorType, null: true + + argument :id, ::Types::GlobalIDType[::Gitlab::ErrorTracking::DetailedError], required: true, description: 'ID of the Sentry issue' - def resolve(**args) - current_user = context[:current_user] - issue_id = GlobalID.parse(args[:id])&.model_id + def resolve(id:) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = ::Types::GlobalIDType[::Gitlab::ErrorTracking::DetailedError].coerce_isolated_input(id) # Get data from Sentry response = ::ErrorTracking::IssueDetailsService.new( project, current_user, - { issue_id: issue_id } + { issue_id: id.model_id } ).execute issue = response[:issue] issue.gitlab_project = project if issue diff --git a/app/graphql/resolvers/error_tracking/sentry_error_collection_resolver.rb b/app/graphql/resolvers/error_tracking/sentry_error_collection_resolver.rb index e4b4854c273..d47cc2bae56 100644 --- a/app/graphql/resolvers/error_tracking/sentry_error_collection_resolver.rb +++ b/app/graphql/resolvers/error_tracking/sentry_error_collection_resolver.rb @@ -3,6 +3,8 @@ module Resolvers module ErrorTracking class SentryErrorCollectionResolver < BaseResolver + type Types::ErrorTracking::SentryErrorCollectionType, null: true + def resolve(**args) project = object diff --git a/app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb b/app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb index c365baaf475..669b487db10 100644 --- a/app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb +++ b/app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb @@ -3,18 +3,20 @@ module Resolvers module ErrorTracking class SentryErrorStackTraceResolver < BaseResolver - argument :id, GraphQL::ID_TYPE, + argument :id, ::Types::GlobalIDType[::Gitlab::ErrorTracking::DetailedError], required: true, description: 'ID of the Sentry issue' - def resolve(**args) - issue_id = GlobalID.parse(args[:id])&.model_id + def resolve(id:) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = ::Types::GlobalIDType[::Gitlab::ErrorTracking::DetailedError].coerce_isolated_input(id) # Get data from Sentry response = ::ErrorTracking::IssueLatestEventService.new( project, current_user, - { issue_id: issue_id } + { issue_id: id.model_id } ).execute event = response[:latest_event] diff --git a/app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb b/app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb index 79f99709505..c5cf924ce7f 100644 --- a/app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb +++ b/app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb @@ -3,6 +3,8 @@ module Resolvers module ErrorTracking class SentryErrorsResolver < BaseResolver + type Types::ErrorTracking::SentryErrorType.connection_type, null: true + def resolve(**args) args[:cursor] = args.delete(:after) project = object.project diff --git a/app/graphql/resolvers/group_issues_resolver.rb b/app/graphql/resolvers/group_issues_resolver.rb index 1fa6c78e730..1db0ab08e31 100644 --- a/app/graphql/resolvers/group_issues_resolver.rb +++ b/app/graphql/resolvers/group_issues_resolver.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:disable Graphql/ResolverType (inherited from IssuesResolver) module Resolvers class GroupIssuesResolver < IssuesResolver diff --git a/app/graphql/resolvers/group_members_resolver.rb b/app/graphql/resolvers/group_members_resolver.rb index f34c873a8a9..d3aa376c29c 100644 --- a/app/graphql/resolvers/group_members_resolver.rb +++ b/app/graphql/resolvers/group_members_resolver.rb @@ -2,6 +2,8 @@ module Resolvers class GroupMembersResolver < MembersResolver + type Types::GroupMemberType.connection_type, null: true + authorize :read_group_member private diff --git a/app/graphql/resolvers/group_merge_requests_resolver.rb b/app/graphql/resolvers/group_merge_requests_resolver.rb index 5ee72e3f781..2bad974daf7 100644 --- a/app/graphql/resolvers/group_merge_requests_resolver.rb +++ b/app/graphql/resolvers/group_merge_requests_resolver.rb @@ -6,6 +6,8 @@ module Resolvers alias_method :group, :synchronized_object + type Types::MergeRequestType.connection_type, null: true + include_subgroups 'merge requests' accept_assignee accept_author diff --git a/app/graphql/resolvers/group_milestones_resolver.rb b/app/graphql/resolvers/group_milestones_resolver.rb index 8d34cea4fa1..83b82e2720b 100644 --- a/app/graphql/resolvers/group_milestones_resolver.rb +++ b/app/graphql/resolvers/group_milestones_resolver.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:disable Graphql/ResolverType (inherited from MilestonesResolver) module Resolvers class GroupMilestonesResolver < MilestonesResolver @@ -6,6 +7,8 @@ module Resolvers required: false, description: 'Also return milestones in all subgroups and subprojects' + type Types::MilestoneType.connection_type, null: true + private def parent_id_parameters(args) diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb index 396ae02ae13..dd35219454f 100644 --- a/app/graphql/resolvers/issues_resolver.rb +++ b/app/graphql/resolvers/issues_resolver.rb @@ -12,7 +12,7 @@ module Resolvers required: false, default_value: 'created_desc' - type Types::IssueType, null: true + type Types::IssueType.connection_type, null: true NON_STABLE_CURSOR_SORTS = %i[priority_asc priority_desc label_priority_asc label_priority_desc diff --git a/app/graphql/resolvers/members_resolver.rb b/app/graphql/resolvers/members_resolver.rb index 88a1ab71c45..523642e912f 100644 --- a/app/graphql/resolvers/members_resolver.rb +++ b/app/graphql/resolvers/members_resolver.rb @@ -5,6 +5,8 @@ module Resolvers include Gitlab::Graphql::Authorize::AuthorizeResource include LooksAhead + type Types::MemberInterface.connection_type, null: true + argument :search, GraphQL::STRING_TYPE, required: false, description: 'Search query' diff --git a/app/graphql/resolvers/merge_request_pipelines_resolver.rb b/app/graphql/resolvers/merge_request_pipelines_resolver.rb index b95e46d9cff..6590dfdc78c 100644 --- a/app/graphql/resolvers/merge_request_pipelines_resolver.rb +++ b/app/graphql/resolvers/merge_request_pipelines_resolver.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true +# rubocop: disable Graphql/ResolverType module Resolvers class MergeRequestPipelinesResolver < BaseResolver + # The GraphQL type here gets defined in this include include ::ResolvesPipelines alias_method :merge_request, :object @@ -18,3 +20,4 @@ module Resolvers end end end +# rubocop: enable Graphql/ResolverType diff --git a/app/graphql/resolvers/merge_request_resolver.rb b/app/graphql/resolvers/merge_request_resolver.rb index a47a128ea32..4cad65fa697 100644 --- a/app/graphql/resolvers/merge_request_resolver.rb +++ b/app/graphql/resolvers/merge_request_resolver.rb @@ -6,6 +6,8 @@ module Resolvers alias_method :project, :synchronized_object + type ::Types::MergeRequestType, null: true + argument :iid, GraphQL::STRING_TYPE, required: true, as: :iids, diff --git a/app/graphql/resolvers/metadata_resolver.rb b/app/graphql/resolvers/metadata_resolver.rb index 3a79e6434fb..26bfa81038c 100644 --- a/app/graphql/resolvers/metadata_resolver.rb +++ b/app/graphql/resolvers/metadata_resolver.rb @@ -5,7 +5,7 @@ module Resolvers type Types::MetadataType, null: false def resolve(**args) - { version: Gitlab::VERSION, revision: Gitlab.revision } + ::InstanceMetadata.new end end end diff --git a/app/graphql/resolvers/milestones_resolver.rb b/app/graphql/resolvers/milestones_resolver.rb index 84712b674db..564e388d571 100644 --- a/app/graphql/resolvers/milestones_resolver.rb +++ b/app/graphql/resolvers/milestones_resolver.rb @@ -25,7 +25,7 @@ module Resolvers required: false, description: 'A date that the milestone contains' - type Types::MilestoneType, null: true + type Types::MilestoneType.connection_type, null: true def resolve(**args) validate_timeframe_params!(args) diff --git a/app/graphql/resolvers/namespace_projects_resolver.rb b/app/graphql/resolvers/namespace_projects_resolver.rb index c221cb9aed6..9f57c8f3405 100644 --- a/app/graphql/resolvers/namespace_projects_resolver.rb +++ b/app/graphql/resolvers/namespace_projects_resolver.rb @@ -23,7 +23,6 @@ module Resolvers # The namespace could have been loaded in batch by `BatchLoader`. # At this point we need the `id` or the `full_path` of the namespace # to query for projects, so make sure it's loaded and not `nil` before continuing. - namespace = object.respond_to?(:sync) ? object.sync : object return Project.none if namespace.nil? query = include_subgroups ? namespace.all_projects.with_route : namespace.projects.with_route @@ -41,6 +40,14 @@ module Resolvers complexity = super complexity + 10 end + + private + + def namespace + strong_memoize(:namespace) do + object.respond_to?(:sync) ? object.sync : object + end + end end end diff --git a/app/graphql/resolvers/project_members_resolver.rb b/app/graphql/resolvers/project_members_resolver.rb index 1ca4e81f397..e64e8b845a5 100644 --- a/app/graphql/resolvers/project_members_resolver.rb +++ b/app/graphql/resolvers/project_members_resolver.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true +# rubocop:disable Graphql/ResolverType (inherited from MembersResolver) module Resolvers class ProjectMembersResolver < MembersResolver - type Types::MemberInterface, null: true - authorize :read_project_member private diff --git a/app/graphql/resolvers/project_merge_requests_resolver.rb b/app/graphql/resolvers/project_merge_requests_resolver.rb index ba13cb6e52c..bf082c0b182 100644 --- a/app/graphql/resolvers/project_merge_requests_resolver.rb +++ b/app/graphql/resolvers/project_merge_requests_resolver.rb @@ -2,6 +2,7 @@ module Resolvers class ProjectMergeRequestsResolver < MergeRequestsResolver + type ::Types::MergeRequestType.connection_type, null: true accept_assignee accept_author end diff --git a/app/graphql/resolvers/project_milestones_resolver.rb b/app/graphql/resolvers/project_milestones_resolver.rb index 976fc300b87..c88c9ce7219 100644 --- a/app/graphql/resolvers/project_milestones_resolver.rb +++ b/app/graphql/resolvers/project_milestones_resolver.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:disable Graphql/ResolverType (inherited from MilestonesResolver) module Resolvers class ProjectMilestonesResolver < MilestonesResolver @@ -6,6 +7,8 @@ module Resolvers required: false, description: "Also return milestones in the project's parent group and its ancestors" + type Types::MilestoneType.connection_type, null: true + private def parent_id_parameters(args) diff --git a/app/graphql/resolvers/project_pipeline_resolver.rb b/app/graphql/resolvers/project_pipeline_resolver.rb index 181c1e77109..4cf47dbdc60 100644 --- a/app/graphql/resolvers/project_pipeline_resolver.rb +++ b/app/graphql/resolvers/project_pipeline_resolver.rb @@ -2,6 +2,8 @@ module Resolvers class ProjectPipelineResolver < BaseResolver + type ::Types::Ci::PipelineType, null: true + alias_method :project, :object argument :iid, GraphQL::ID_TYPE, diff --git a/app/graphql/resolvers/project_pipelines_resolver.rb b/app/graphql/resolvers/project_pipelines_resolver.rb index 86094c46c2a..0171473a77f 100644 --- a/app/graphql/resolvers/project_pipelines_resolver.rb +++ b/app/graphql/resolvers/project_pipelines_resolver.rb @@ -1,13 +1,28 @@ # frozen_string_literal: true +# The GraphQL type here gets defined in +# https://gitlab.com/gitlab-org/gitlab/blob/master/app/graphql/resolvers/concerns/resolves_pipelines.rb#L7 +# rubocop: disable Graphql/ResolverType module Resolvers class ProjectPipelinesResolver < BaseResolver + include LooksAhead include ResolvesPipelines alias_method :project, :object - def resolve(**args) - resolve_pipelines(project, args) + def resolve_with_lookahead(**args) + apply_lookahead(resolve_pipelines(project, args)) + end + + private + + def preloads + { + jobs: [:statuses], + upstream: [:triggered_by_pipeline], + downstream: [:triggered_pipelines] + } end end end +# rubocop: enable Graphql/ResolverType diff --git a/app/graphql/resolvers/projects/jira_imports_resolver.rb b/app/graphql/resolvers/projects/jira_imports_resolver.rb index aa9b7139f38..efd45c2c465 100644 --- a/app/graphql/resolvers/projects/jira_imports_resolver.rb +++ b/app/graphql/resolvers/projects/jira_imports_resolver.rb @@ -3,6 +3,8 @@ module Resolvers module Projects class JiraImportsResolver < BaseResolver + type Types::JiraImportType.connection_type, null: true + include Gitlab::Graphql::Authorize::AuthorizeResource alias_method :project, :object diff --git a/app/graphql/resolvers/projects/jira_projects_resolver.rb b/app/graphql/resolvers/projects/jira_projects_resolver.rb index d017f973e17..31f42d305b0 100644 --- a/app/graphql/resolvers/projects/jira_projects_resolver.rb +++ b/app/graphql/resolvers/projects/jira_projects_resolver.rb @@ -5,6 +5,8 @@ module Resolvers class JiraProjectsResolver < BaseResolver include Gitlab::Graphql::Authorize::AuthorizeResource + type Types::Projects::Services::JiraProjectType.connection_type, null: true + argument :name, GraphQL::STRING_TYPE, required: false, diff --git a/app/graphql/resolvers/projects/services_resolver.rb b/app/graphql/resolvers/projects/services_resolver.rb index 40c64c24513..17d81e21c28 100644 --- a/app/graphql/resolvers/projects/services_resolver.rb +++ b/app/graphql/resolvers/projects/services_resolver.rb @@ -5,6 +5,8 @@ module Resolvers class ServicesResolver < BaseResolver include Gitlab::Graphql::Authorize::AuthorizeResource + type Types::Projects::ServiceType.connection_type, null: true + argument :active, GraphQL::BOOLEAN_TYPE, required: false, diff --git a/app/graphql/resolvers/projects/snippets_resolver.rb b/app/graphql/resolvers/projects/snippets_resolver.rb index 22895a24054..448918be2f5 100644 --- a/app/graphql/resolvers/projects/snippets_resolver.rb +++ b/app/graphql/resolvers/projects/snippets_resolver.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:disable Graphql/ResolverType (inherited from ResolvesSnippets) module Resolvers module Projects diff --git a/app/graphql/resolvers/releases_resolver.rb b/app/graphql/resolvers/releases_resolver.rb index 85892c2abeb..8e8127cf279 100644 --- a/app/graphql/resolvers/releases_resolver.rb +++ b/app/graphql/resolvers/releases_resolver.rb @@ -4,6 +4,10 @@ module Resolvers class ReleasesResolver < BaseResolver type Types::ReleaseType.connection_type, null: true + argument :sort, Types::ReleaseSortEnum, + required: false, default_value: :released_at_desc, + description: 'Sort releases by this criteria' + alias_method :project, :object # This resolver has a custom singular resolver @@ -11,12 +15,20 @@ module Resolvers Resolvers::ReleaseResolver end - def resolve(**args) + SORT_TO_PARAMS_MAP = { + released_at_desc: { order_by: 'released_at', sort: 'desc' }, + released_at_asc: { order_by: 'released_at', sort: 'asc' }, + created_desc: { order_by: 'created_at', sort: 'desc' }, + created_asc: { order_by: 'created_at', sort: 'asc' } + }.freeze + + def resolve(sort:) return unless Feature.enabled?(:graphql_release_data, project, default_enabled: true) ReleasesFinder.new( project, - current_user + current_user, + SORT_TO_PARAMS_MAP[sort] ).execute end end diff --git a/app/graphql/resolvers/snippets/blobs_resolver.rb b/app/graphql/resolvers/snippets/blobs_resolver.rb index dc28358cab6..3a0dcb50faf 100644 --- a/app/graphql/resolvers/snippets/blobs_resolver.rb +++ b/app/graphql/resolvers/snippets/blobs_resolver.rb @@ -5,6 +5,8 @@ module Resolvers class BlobsResolver < BaseResolver include Gitlab::Graphql::Authorize::AuthorizeResource + type Types::Snippets::BlobType.connection_type, null: true + alias_method :snippet, :object argument :paths, [GraphQL::STRING_TYPE], diff --git a/app/graphql/resolvers/snippets_resolver.rb b/app/graphql/resolvers/snippets_resolver.rb index 530a288a25b..77099565df0 100644 --- a/app/graphql/resolvers/snippets_resolver.rb +++ b/app/graphql/resolvers/snippets_resolver.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:disable Graphql/ResolverType (inherited from ResolvesSnippets) module Resolvers class SnippetsResolver < BaseResolver @@ -8,11 +9,11 @@ module Resolvers alias_method :user, :object - argument :author_id, GraphQL::ID_TYPE, + argument :author_id, ::Types::GlobalIDType[::User], required: false, description: 'The ID of an author' - argument :project_id, GraphQL::ID_TYPE, + argument :project_id, ::Types::GlobalIDType[::Project], required: false, description: 'The ID of a project' @@ -36,9 +37,11 @@ module Resolvers private def snippet_finder_params(args) + # TODO: remove the type arguments when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 super - .merge(author: resolve_gid(args[:author_id], :author), - project: resolve_gid(args[:project_id], :project), + .merge(author: resolve_ids(args[:author_id], ::Types::GlobalIDType[::User]), + project: resolve_ids(args[:project_id], ::Types::GlobalIDType[::Project]), explore: args[:explore]) end end diff --git a/app/graphql/resolvers/todo_resolver.rb b/app/graphql/resolvers/todo_resolver.rb index bd5f8f274cd..9a8f7a71154 100644 --- a/app/graphql/resolvers/todo_resolver.rb +++ b/app/graphql/resolvers/todo_resolver.rb @@ -2,7 +2,7 @@ module Resolvers class TodoResolver < BaseResolver - type Types::TodoType, null: true + type Types::TodoType.connection_type, null: true alias_method :target, :object diff --git a/app/graphql/resolvers/tree_resolver.rb b/app/graphql/resolvers/tree_resolver.rb index 5aad1c71b40..075a1929c47 100644 --- a/app/graphql/resolvers/tree_resolver.rb +++ b/app/graphql/resolvers/tree_resolver.rb @@ -2,6 +2,8 @@ module Resolvers class TreeResolver < BaseResolver + type Types::Tree::TreeType, null: true + argument :path, GraphQL::STRING_TYPE, required: false, default_value: '', diff --git a/app/graphql/resolvers/user_merge_requests_resolver.rb b/app/graphql/resolvers/user_merge_requests_resolver_base.rb index b0d6e159f73..47967fe69f9 100644 --- a/app/graphql/resolvers/user_merge_requests_resolver.rb +++ b/app/graphql/resolvers/user_merge_requests_resolver_base.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true module Resolvers - class UserMergeRequestsResolver < MergeRequestsResolver + class UserMergeRequestsResolverBase < MergeRequestsResolver include ResolvesProject argument :project_path, GraphQL::STRING_TYPE, required: false, description: 'The full-path of the project the authored merge requests should be in. Incompatible with projectId.' - argument :project_id, GraphQL::ID_TYPE, + argument :project_id, ::Types::GlobalIDType[::Project], required: false, description: 'The global ID of the project the authored merge requests should be in. Incompatible with projectPath.' @@ -50,8 +50,10 @@ module Resolvers end def load_project(project_path, project_id) - @project = resolve_project(full_path: project_path, project_id: project_id) - @project = @project.sync if @project.respond_to?(:sync) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + project_id &&= ::Types::GlobalIDType[::Project].coerce_isolated_input(project_id) + @project = ::Gitlab::Graphql::Lazy.force(resolve_project(full_path: project_path, project_id: project_id)) end def no_results_possible?(args) diff --git a/app/graphql/resolvers/user_resolver.rb b/app/graphql/resolvers/user_resolver.rb index a34cecba491..06c1f0cb42d 100644 --- a/app/graphql/resolvers/user_resolver.rb +++ b/app/graphql/resolvers/user_resolver.rb @@ -6,7 +6,7 @@ module Resolvers type Types::UserType, null: true - argument :id, GraphQL::ID_TYPE, + argument :id, Types::GlobalIDType[User], required: false, description: 'ID of the User' diff --git a/app/graphql/resolvers/users/group_count_resolver.rb b/app/graphql/resolvers/users/group_count_resolver.rb new file mode 100644 index 00000000000..5033c26554a --- /dev/null +++ b/app/graphql/resolvers/users/group_count_resolver.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Resolvers + module Users + class GroupCountResolver < BaseResolver + alias_method :user, :object + + def resolve(**args) + return unless can_read_group_count? + + BatchLoader::GraphQL.for(user.id).batch do |user_ids, loader| + results = UserGroupsCounter.new(user_ids).execute + + results.each do |user_id, count| + loader.call(user_id, count) + end + end + end + + def can_read_group_count? + current_user&.can?(:read_group_count, user) + end + end + end +end diff --git a/app/graphql/resolvers/users/snippets_resolver.rb b/app/graphql/resolvers/users/snippets_resolver.rb index d757640b5ff..c2d42437ffd 100644 --- a/app/graphql/resolvers/users/snippets_resolver.rb +++ b/app/graphql/resolvers/users/snippets_resolver.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rubocop:disable Graphql/ResolverType (inherited from ResolvesSnippets) module Resolvers module Users diff --git a/app/graphql/resolvers/users_resolver.rb b/app/graphql/resolvers/users_resolver.rb index 110a283b42e..f5838642141 100644 --- a/app/graphql/resolvers/users_resolver.rb +++ b/app/graphql/resolvers/users_resolver.rb @@ -4,6 +4,7 @@ module Resolvers class UsersResolver < BaseResolver include Gitlab::Graphql::Authorize::AuthorizeResource + type Types::UserType.connection_type, null: true description 'Find Users' argument :ids, [GraphQL::ID_TYPE], @@ -18,10 +19,14 @@ module Resolvers required: false, default_value: 'created_desc' - def resolve(ids: nil, usernames: nil, sort: nil) + argument :search, GraphQL::STRING_TYPE, + required: false, + description: "Query to search users by name, username, or primary email." + + def resolve(ids: nil, usernames: nil, sort: nil, search: nil) authorize! - ::UsersFinder.new(context[:current_user], finder_params(ids, usernames, sort)).execute + ::UsersFinder.new(context[:current_user], finder_params(ids, usernames, sort, search)).execute end def ready?(**args) @@ -42,11 +47,12 @@ module Resolvers private - def finder_params(ids, usernames, sort) + def finder_params(ids, usernames, sort, search) params = {} params[:sort] = sort if sort params[:username] = usernames if usernames params[:id] = parse_gids(ids) if ids + params[:search] = search if search params end |