diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-21 02:50:22 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-21 02:50:22 +0300 |
commit | 9dc93a4519d9d5d7be48ff274127136236a3adb3 (patch) | |
tree | 70467ae3692a0e35e5ea56bcb803eb512a10bedb /app/graphql/resolvers | |
parent | 4b0f34b6d759d6299322b3a54453e930c6121ff0 (diff) |
Add latest changes from gitlab-org/gitlab@13-11-stable-eev13.11.0-rc43
Diffstat (limited to 'app/graphql/resolvers')
40 files changed, 560 insertions, 143 deletions
diff --git a/app/graphql/resolvers/alert_management/http_integrations_resolver.rb b/app/graphql/resolvers/alert_management/http_integrations_resolver.rb index 94a72bca7c7..abc54614a59 100644 --- a/app/graphql/resolvers/alert_management/http_integrations_resolver.rb +++ b/app/graphql/resolvers/alert_management/http_integrations_resolver.rb @@ -3,19 +3,39 @@ module Resolvers module AlertManagement class HttpIntegrationsResolver < BaseResolver - alias_method :project, :synchronized_object + include ::Gitlab::Graphql::Laziness + + alias_method :project, :object + + argument :id, Types::GlobalIDType[::AlertManagement::HttpIntegration], + required: false, + description: 'ID of the integration.' type Types::AlertManagement::HttpIntegrationType.connection_type, null: true - def resolve(**args) - http_integrations + def resolve(id: nil) + return [] unless Ability.allowed?(current_user, :admin_operations, project) + + if id + integrations_by(gid: id) + else + http_integrations + end end private - def http_integrations - return [] unless Ability.allowed?(current_user, :admin_operations, project) + def integrations_by(gid:) + id = Types::GlobalIDType[::AlertManagement::HttpIntegration].coerce_isolated_input(gid) + object = GitlabSchema.find_by_gid(id) + + defer { object }.then do |integration| + ret = integration if project == integration&.project + Array.wrap(ret) + end + end + def http_integrations ::AlertManagement::HttpIntegrationsFinder.new(project, {}).execute end end diff --git a/app/graphql/resolvers/alert_management/integrations_resolver.rb b/app/graphql/resolvers/alert_management/integrations_resolver.rb index 4d1fe367277..cb7e73c2d1a 100644 --- a/app/graphql/resolvers/alert_management/integrations_resolver.rb +++ b/app/graphql/resolvers/alert_management/integrations_resolver.rb @@ -3,27 +3,60 @@ module Resolvers module AlertManagement class IntegrationsResolver < BaseResolver - alias_method :project, :synchronized_object + include ::Gitlab::Graphql::Laziness + + alias_method :project, :object + + argument :id, ::Types::GlobalIDType, + required: false, + description: 'ID of the integration.' type Types::AlertManagement::IntegrationType.connection_type, null: true - def resolve(**args) - http_integrations + prometheus_integrations + def resolve(id: nil) + if id + integrations_by(gid: id) + else + http_integrations + prometheus_integrations + end end private + def integrations_by(gid:) + object = GitlabSchema.object_from_id(gid, expected_type: expected_integration_types) + defer { object }.then do |integration| + ret = integration if project == integration&.project + Array.wrap(ret) + end + end + def prometheus_integrations - return [] unless Ability.allowed?(current_user, :admin_project, project) + return [] unless prometheus_integrations_allowed? Array(project.prometheus_service) end def http_integrations - return [] unless Ability.allowed?(current_user, :admin_operations, project) + return [] unless http_integrations_allowed? ::AlertManagement::HttpIntegrationsFinder.new(project, {}).execute end + + def prometheus_integrations_allowed? + Ability.allowed?(current_user, :admin_project, project) + end + + def http_integrations_allowed? + Ability.allowed?(current_user, :admin_operations, project) + end + + def expected_integration_types + [].tap do |types| + types << ::AlertManagement::HttpIntegration if http_integrations_allowed? + types << ::PrometheusService if prometheus_integrations_allowed? + end + end end end end diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb index 67bba079512..48563633d11 100644 --- a/app/graphql/resolvers/base_resolver.rb +++ b/app/graphql/resolvers/base_resolver.rb @@ -39,9 +39,7 @@ module Resolvers as_single << block # Have we been called after defining the single version of this resolver? - if @single.present? - @single.instance_exec(&block) - end + @single.instance_exec(&block) if @single.present? end def self.as_single @@ -90,7 +88,7 @@ module Resolvers def self.last parent = self - @last ||= Class.new(self.single) do + @last ||= Class.new(single) do type parent.singular_type, null: true def select_result(results) @@ -138,16 +136,6 @@ 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 - ::Gitlab::Graphql::Lazy.force(object) - end - end - def single? false end @@ -160,5 +148,13 @@ module Resolvers def select_result(results) results end + + def self.authorization + @authorization ||= ::Gitlab::Graphql::Authorize::ObjectAuthorization.new(try(:required_permissions)) + end + + def self.authorized?(object, context) + authorization.ok?(object, context[:current_user]) + end end end diff --git a/app/graphql/resolvers/blobs_resolver.rb b/app/graphql/resolvers/blobs_resolver.rb new file mode 100644 index 00000000000..d006769bd4b --- /dev/null +++ b/app/graphql/resolvers/blobs_resolver.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Resolvers + class BlobsResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + type Types::Tree::BlobType.connection_type, null: true + authorize :download_code + calls_gitaly! + + alias_method :repository, :object + + argument :paths, [GraphQL::STRING_TYPE], + required: true, + description: 'Array of desired blob paths.' + argument :ref, GraphQL::STRING_TYPE, + required: false, + default_value: nil, + description: 'The commit ref to get the blobs from. Default value is HEAD.' + + # We fetch blobs from Gitaly efficiently but it still scales O(N) with the + # number of paths being fetched, so apply a scaling limit to that. + def self.resolver_complexity(args, child_complexity:) + super + (args[:paths] || []).size + end + + def resolve(paths:, ref:) + authorize!(repository.container) + + return [] if repository.empty? + + ref ||= repository.root_ref + + repository.blobs_at(paths.map { |path| [ref, path] }) + end + end +end diff --git a/app/graphql/resolvers/board_lists_resolver.rb b/app/graphql/resolvers/board_lists_resolver.rb index e66f7b97b40..0b699006626 100644 --- a/app/graphql/resolvers/board_lists_resolver.rb +++ b/app/graphql/resolvers/board_lists_resolver.rb @@ -3,13 +3,12 @@ module Resolvers class BoardListsResolver < BaseResolver include BoardIssueFilterable - prepend ManualAuthorization include Gitlab::Graphql::Authorize::AuthorizeResource + include LooksAhead type Types::BoardListType, null: true - extras [:lookahead] - authorize :read_issue_board_list + authorizes_object! argument :id, Types::GlobalIDType[List], required: false, @@ -21,15 +20,11 @@ module Resolvers alias_method :board, :object - def resolve(lookahead: nil, id: nil, issue_filters: {}) - authorize!(board) - + def resolve_with_lookahead(id: nil, issue_filters: {}) lists = board_lists(id) context.scoped_set!(:issue_filters, issue_filters(issue_filters)) - if load_preferences?(lookahead) - List.preload_preferences_for_user(lists, current_user) - end + List.preload_preferences_for_user(lists, current_user) if load_preferences? offset_pagination(lists) end @@ -46,9 +41,8 @@ module Resolvers service.execute(board, create_default_lists: false) end - def load_preferences?(lookahead) - lookahead&.selection(:edges)&.selection(:node)&.selects?(:collapsed) || - lookahead&.selection(:nodes)&.selects?(:collapsed) + def load_preferences? + node_selection&.selects?(:collapsed) end def extract_list_id(gid) diff --git a/app/graphql/resolvers/board_resolver.rb b/app/graphql/resolvers/board_resolver.rb index 637d690e4cd..85362ab1422 100644 --- a/app/graphql/resolvers/board_resolver.rb +++ b/app/graphql/resolvers/board_resolver.rb @@ -2,7 +2,7 @@ module Resolvers class BoardResolver < BaseResolver.single - alias_method :parent, :synchronized_object + alias_method :parent, :object type Types::BoardType, null: true diff --git a/app/graphql/resolvers/ci/config_resolver.rb b/app/graphql/resolvers/ci/config_resolver.rb index f8670649e48..252c9d3acf0 100644 --- a/app/graphql/resolvers/ci/config_resolver.rb +++ b/app/graphql/resolvers/ci/config_resolver.rb @@ -7,6 +7,10 @@ module Resolvers include ResolvesProject type Types::Ci::Config::ConfigType, null: true + description <<~MD + Linted and processed contents of a CI config. + Should not be requested more than once per request. + MD authorize :read_pipeline @@ -55,7 +59,7 @@ module Resolvers name: job[:name], stage: job[:stage], group_name: CommitStatus.new(name: job[:name]).group_name, - needs: job.dig(:needs) || [], + needs: job[:needs] || [], allow_failure: job[:allow_failure], before_script: job[:before_script], script: job[:script], diff --git a/app/graphql/resolvers/ci/jobs_resolver.rb b/app/graphql/resolvers/ci/jobs_resolver.rb index dd565094017..5ae9e721cc8 100644 --- a/app/graphql/resolvers/ci/jobs_resolver.rb +++ b/app/graphql/resolvers/ci/jobs_resolver.rb @@ -11,7 +11,18 @@ module Resolvers required: false, description: 'Filter jobs by the type of security report they produce.' - def resolve(security_report_types: []) + argument :statuses, [::Types::Ci::JobStatusEnum], + required: false, + description: 'Filter jobs by status.' + + def resolve(statuses: nil, security_report_types: []) + jobs = init_collection(security_report_types) + jobs = jobs.with_status(statuses) if statuses.present? + + jobs + end + + def init_collection(security_report_types) if security_report_types.present? ::Security::SecurityJobsFinder.new( pipeline: pipeline, diff --git a/app/graphql/resolvers/ci/pipeline_stages_resolver.rb b/app/graphql/resolvers/ci/pipeline_stages_resolver.rb index 98170e0cd2e..a458e873935 100644 --- a/app/graphql/resolvers/ci/pipeline_stages_resolver.rb +++ b/app/graphql/resolvers/ci/pipeline_stages_resolver.rb @@ -16,7 +16,7 @@ module Resolvers def preloads { - statuses: [:needs] + jobs: { latest_statuses: [:needs] } } end end diff --git a/app/graphql/resolvers/ci/runner_platforms_resolver.rb b/app/graphql/resolvers/ci/runner_platforms_resolver.rb index 9677c5139b4..f120e94b67b 100644 --- a/app/graphql/resolvers/ci/runner_platforms_resolver.rb +++ b/app/graphql/resolvers/ci/runner_platforms_resolver.rb @@ -3,7 +3,8 @@ module Resolvers module Ci class RunnerPlatformsResolver < BaseResolver - type Types::Ci::RunnerPlatformType, null: false + type Types::Ci::RunnerPlatformType.connection_type, null: true + description 'Supported runner platforms.' def resolve(**args) runner_instructions.map do |platform, data| diff --git a/app/graphql/resolvers/ci/runner_setup_resolver.rb b/app/graphql/resolvers/ci/runner_setup_resolver.rb index ac2a56b89a7..9166999b400 100644 --- a/app/graphql/resolvers/ci/runner_setup_resolver.rb +++ b/app/graphql/resolvers/ci/runner_setup_resolver.rb @@ -3,30 +3,37 @@ module Resolvers module Ci class RunnerSetupResolver < BaseResolver + ACCESS_DENIED = 'User is not authorized to register a runner for the specified resource!' + type Types::Ci::RunnerSetupType, null: true + description 'Runner setup instructions.' - argument :platform, GraphQL::STRING_TYPE, - required: true, - description: 'Platform to generate the instructions for.' + argument :platform, + type: 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 :architecture, + type: 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 :project_id, + type: ::Types::GlobalIDType[::Project], + required: false, + deprecated: { reason: 'No longer used', milestone: '13.11' }, + description: 'Project to register the runner for.' - argument :group_id, ::Types::GlobalIDType[::Group], - required: false, - description: 'Group to register the runner for.' + argument :group_id, + type: ::Types::GlobalIDType[::Group], + required: false, + deprecated: { reason: 'No longer used', milestone: '13.11' }, + 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, - **target_param(args) + arch: architecture ) { @@ -34,11 +41,15 @@ module Resolvers 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') + raise Gitlab::Graphql::Errors::ResourceNotAvailable, ACCESS_DENIED if access_denied?(instructions) end private + def access_denied?(instructions) + instructions.errors.include?('Gitlab::Access::AccessDeniedError') + end + def other_install_instructions(platform) Gitlab::Ci::RunnerInstructions::OTHER_ENVIRONMENTS[platform.to_sym][:installation_instructions_url] end diff --git a/app/graphql/resolvers/ci/test_report_summary_resolver.rb b/app/graphql/resolvers/ci/test_report_summary_resolver.rb new file mode 100644 index 00000000000..22db70f032a --- /dev/null +++ b/app/graphql/resolvers/ci/test_report_summary_resolver.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Resolvers + module Ci + class TestReportSummaryResolver < BaseResolver + type ::Types::Ci::TestReportSummaryType, null: true + + alias_method :pipeline, :object + + def resolve(**args) + TestReportSummarySerializer + .new(project: pipeline.project, current_user: @current_user) + .represent(pipeline.test_report_summary) + end + end + end +end diff --git a/app/graphql/resolvers/ci/test_suite_resolver.rb b/app/graphql/resolvers/ci/test_suite_resolver.rb new file mode 100644 index 00000000000..90cc30b1281 --- /dev/null +++ b/app/graphql/resolvers/ci/test_suite_resolver.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Resolvers + module Ci + class TestSuiteResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + type ::Types::Ci::TestSuiteType, null: true + authorize :read_build + authorizes_object! + + alias_method :pipeline, :object + + argument :build_ids, [GraphQL::ID_TYPE], + required: true, + description: 'IDs of the builds used to run the test suite.' + + def resolve(build_ids:) + builds = pipeline.latest_builds.id_in(build_ids).presence + return unless builds + + TestSuiteSerializer + .new(project: pipeline.project, current_user: @current_user) + .represent(load_test_suite_data(builds), details: true) + end + + private + + def load_test_suite_data(builds) + suite = builds.sum do |build| + build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new) + end + + Gitlab::Ci::Reports::TestFailureHistory.new(suite.failed.values, pipeline.project).load! + + suite + end + end + end +end diff --git a/app/graphql/resolvers/concerns/board_issue_filterable.rb b/app/graphql/resolvers/concerns/board_issue_filterable.rb index 1541738f46c..3484a1cc4ba 100644 --- a/app/graphql/resolvers/concerns/board_issue_filterable.rb +++ b/app/graphql/resolvers/concerns/board_issue_filterable.rb @@ -7,10 +7,10 @@ module BoardIssueFilterable def issue_filters(args) filters = args.to_h + set_filter_values(filters) if filters[:not] - filters[:not] = filters[:not].to_h set_filter_values(filters[:not]) end @@ -18,6 +18,17 @@ module BoardIssueFilterable end def set_filter_values(filters) + filter_by_assignee(filters) + end + + def filter_by_assignee(filters) + if filters[:assignee_username] && filters[:assignee_wildcard_id] + raise ::Gitlab::Graphql::Errors::ArgumentError, 'Incompatible arguments: assigneeUsername, assigneeWildcardId.' + end + + if filters[:assignee_wildcard_id] + filters[:assignee_id] = filters.delete(:assignee_wildcard_id) + end end end diff --git a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb index 84b0dafe213..0ff3997f3bc 100644 --- a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb +++ b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb @@ -12,10 +12,10 @@ module IssueResolverArguments argument :iids, [GraphQL::STRING_TYPE], required: false, description: 'List of IIDs of issues. For example, [1, 2].' - argument :label_name, GraphQL::STRING_TYPE.to_list_type, + argument :label_name, [GraphQL::STRING_TYPE, null: true], required: false, description: 'Labels applied to this issue.' - argument :milestone_title, GraphQL::STRING_TYPE.to_list_type, + argument :milestone_title, [GraphQL::STRING_TYPE, null: true], required: false, description: 'Milestone applied to this issue.' argument :author_username, GraphQL::STRING_TYPE, @@ -23,7 +23,8 @@ module IssueResolverArguments description: 'Username of the author of the issue.' argument :assignee_username, GraphQL::STRING_TYPE, required: false, - description: 'Username of a user assigned to the issue.' + description: 'Username of a user assigned to the issue.', + deprecated: { reason: 'Use `assigneeUsernames`', milestone: '13.11' } argument :assignee_usernames, [GraphQL::STRING_TYPE], required: false, description: 'Usernames of users assigned to the issue.' @@ -55,6 +56,10 @@ module IssueResolverArguments as: :issue_types, description: 'Filter issues by the given issue types.', required: false + argument :not, Types::Issues::NegatedIssueFilterInputType, + description: 'List of negated params.', + prepare: ->(negated_args, ctx) { negated_args.to_h }, + required: false end def resolve_with_lookahead(**args) @@ -69,11 +74,22 @@ module IssueResolverArguments args[:iids] ||= [args.delete(:iid)].compact if args[:iid] args[:attempt_project_search_optimizations] = true if args[:search].present? + prepare_assignee_username_params(args) + finder = IssuesFinder.new(current_user, args) continue_issue_resolve(parent, finder, **args) end + def ready?(**args) + if args.slice(*mutually_exclusive_assignee_username_args).compact.size > 1 + arg_str = mutually_exclusive_assignee_username_args.map { |x| x.to_s.camelize(:lower) }.join(', ') + raise Gitlab::Graphql::Errors::ArgumentError, "only one of [#{arg_str}] arguments is allowed at the same time." + end + + super + end + class_methods do def resolver_complexity(args, child_complexity:) complexity = super @@ -82,4 +98,15 @@ module IssueResolverArguments complexity end end + + private + + def prepare_assignee_username_params(args) + args[:assignee_username] = args.delete(:assignee_usernames) if args[:assignee_usernames].present? + args[:not][:assignee_username] = args[:not].delete(:assignee_usernames) if args.dig(:not, :assignee_usernames).present? + end + + def mutually_exclusive_assignee_username_args + [:assignee_usernames, :assignee_username] + end end diff --git a/app/graphql/resolvers/concerns/looks_ahead.rb b/app/graphql/resolvers/concerns/looks_ahead.rb index 77a85edfba6..644b2a11460 100644 --- a/app/graphql/resolvers/concerns/looks_ahead.rb +++ b/app/graphql/resolvers/concerns/looks_ahead.rb @@ -15,12 +15,7 @@ module LooksAhead end def apply_lookahead(query) - selection = node_selection - - includes = preloads.each.flat_map do |name, requirements| - selection&.selects?(name) ? requirements : [] - end - all_preloads = (unconditional_includes + includes).uniq + all_preloads = (unconditional_includes + filtered_preloads).uniq return query if all_preloads.empty? @@ -37,6 +32,14 @@ module LooksAhead {} end + def filtered_preloads + selection = node_selection + + preloads.each.flat_map do |name, requirements| + selection&.selects?(name) ? requirements : [] + end + end + def node_selection return unless lookahead diff --git a/app/graphql/resolvers/concerns/manual_authorization.rb b/app/graphql/resolvers/concerns/manual_authorization.rb deleted file mode 100644 index 182110b9594..00000000000 --- a/app/graphql/resolvers/concerns/manual_authorization.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -# TODO: remove this entirely when framework authorization is released -# See: https://gitlab.com/gitlab-org/gitlab/-/issues/290216 -module ManualAuthorization - def resolve(**args) - super - rescue ::Gitlab::Graphql::Errors::ResourceNotAvailable - nil - end -end diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb index 31444b0c592..75f1ee478a8 100644 --- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb +++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb @@ -50,7 +50,8 @@ module ResolvesMergeRequests approved_by: [:approved_by_users], milestone: [:milestone], security_auto_fix: [:author], - head_pipeline: [:merge_request_diff, { head_pipeline: [:merge_request] }] + head_pipeline: [:merge_request_diff, { head_pipeline: [:merge_request] }], + timelogs: [:timelogs] } end end diff --git a/app/graphql/resolvers/concerns/resolves_snippets.rb b/app/graphql/resolvers/concerns/resolves_snippets.rb index 445f3567b1d..8de85c074ec 100644 --- a/app/graphql/resolvers/concerns/resolves_snippets.rb +++ b/app/graphql/resolvers/concerns/resolves_snippets.rb @@ -4,7 +4,7 @@ module ResolvesSnippets extend ActiveSupport::Concern included do - type Types::SnippetType.connection_type, null: false + type Types::SnippetType.connection_type, null: true argument :ids, [::Types::GlobalIDType[::Snippet]], required: false, diff --git a/app/graphql/resolvers/echo_resolver.rb b/app/graphql/resolvers/echo_resolver.rb index 0c7dad622cf..a09b0a1fd87 100644 --- a/app/graphql/resolvers/echo_resolver.rb +++ b/app/graphql/resolvers/echo_resolver.rb @@ -5,8 +5,10 @@ module Resolvers 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.' + argument :text, + type: GraphQL::STRING_TYPE, + required: true, + description: 'Text to echo back.' def resolve(text:) username = current_user&.username diff --git a/app/graphql/resolvers/environments_resolver.rb b/app/graphql/resolvers/environments_resolver.rb index ed3395d05aa..df04e70e250 100644 --- a/app/graphql/resolvers/environments_resolver.rb +++ b/app/graphql/resolvers/environments_resolver.rb @@ -21,7 +21,7 @@ module Resolvers def resolve(**args) return unless project.present? - EnvironmentsFinder.new(project, context[:current_user], args).find + EnvironmentsFinder.new(project, context[:current_user], args).execute rescue EnvironmentsFinder::InvalidStatesError => exception raise Gitlab::Graphql::Errors::ArgumentError, exception.message end diff --git a/app/graphql/resolvers/group_members_resolver.rb b/app/graphql/resolvers/group_members_resolver.rb index 36e1977b756..d3662b08cdf 100644 --- a/app/graphql/resolvers/group_members_resolver.rb +++ b/app/graphql/resolvers/group_members_resolver.rb @@ -13,12 +13,6 @@ module Resolvers private - def preloads - { - user: [:user, :source] - } - end - def finder_class GroupMembersFinder end diff --git a/app/graphql/resolvers/group_merge_requests_resolver.rb b/app/graphql/resolvers/group_merge_requests_resolver.rb index 2bad974daf7..34a4c67bc56 100644 --- a/app/graphql/resolvers/group_merge_requests_resolver.rb +++ b/app/graphql/resolvers/group_merge_requests_resolver.rb @@ -4,7 +4,7 @@ module Resolvers class GroupMergeRequestsResolver < MergeRequestsResolver include GroupIssuableResolver - alias_method :group, :synchronized_object + alias_method :group, :object type Types::MergeRequestType.connection_type, null: true diff --git a/app/graphql/resolvers/group_milestones_resolver.rb b/app/graphql/resolvers/group_milestones_resolver.rb index 179283fd7b7..31280b36278 100644 --- a/app/graphql/resolvers/group_milestones_resolver.rb +++ b/app/graphql/resolvers/group_milestones_resolver.rb @@ -1,22 +1,40 @@ # frozen_string_literal: true -# rubocop:disable Graphql/ResolverType (inherited from MilestonesResolver) module Resolvers class GroupMilestonesResolver < MilestonesResolver argument :include_descendants, GraphQL::BOOLEAN_TYPE, required: false, - description: 'Also return milestones in all subgroups and subprojects.' + description: 'Include milestones from all subgroups and subprojects.' + argument :include_ancestors, GraphQL::BOOLEAN_TYPE, + required: false, + description: 'Include milestones from all parent groups.' type Types::MilestoneType.connection_type, null: true private def parent_id_parameters(args) - return { group_ids: parent.id } unless args[:include_descendants].present? + include_ancestors = args[:include_ancestors].present? + include_descendants = args[:include_descendants].present? + return { group_ids: parent.id } unless include_ancestors || include_descendants + + group_ids = if include_ancestors && include_descendants + parent.self_and_hierarchy + elsif include_ancestors + parent.self_and_ancestors + else + parent.self_and_descendants + end + + project_ids = if include_descendants + group_projects.with_issues_or_mrs_available_for_user(current_user) + else + nil + end { - 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) + group_ids: group_ids.public_or_visible_to_user(current_user).select(:id), + project_ids: project_ids } end diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb index ac3bdda0f12..7a67f115abf 100644 --- a/app/graphql/resolvers/issues_resolver.rb +++ b/app/graphql/resolvers/issues_resolver.rb @@ -44,7 +44,8 @@ module Resolvers { alert_management_alert: [:alert_management_alert], labels: [:labels], - assignees: [:assignees] + assignees: [:assignees], + timelogs: [:timelogs] } end diff --git a/app/graphql/resolvers/members_resolver.rb b/app/graphql/resolvers/members_resolver.rb index 76c3ae936ee..2b731d54cdd 100644 --- a/app/graphql/resolvers/members_resolver.rb +++ b/app/graphql/resolvers/members_resolver.rb @@ -21,6 +21,12 @@ module Resolvers private + def preloads + { + user: [:user, :source] + } + end + def finder_class # override in subclass end diff --git a/app/graphql/resolvers/merge_request_resolver.rb b/app/graphql/resolvers/merge_request_resolver.rb index 8fd33c6626e..c431d079beb 100644 --- a/app/graphql/resolvers/merge_request_resolver.rb +++ b/app/graphql/resolvers/merge_request_resolver.rb @@ -4,14 +4,14 @@ module Resolvers class MergeRequestResolver < BaseResolver.single include ResolvesMergeRequests - alias_method :project, :synchronized_object + alias_method :project, :object type ::Types::MergeRequestType, null: true argument :iid, GraphQL::STRING_TYPE, - required: true, - as: :iids, - description: 'IID of the merge request, for example `1`.' + required: true, + as: :iids, + description: 'IID of the merge request, for example `1`.' def no_results_possible?(args) project.nil? diff --git a/app/graphql/resolvers/merge_requests_resolver.rb b/app/graphql/resolvers/merge_requests_resolver.rb index ecbdaaa3f55..a9eea4ae4b8 100644 --- a/app/graphql/resolvers/merge_requests_resolver.rb +++ b/app/graphql/resolvers/merge_requests_resolver.rb @@ -3,42 +3,49 @@ module Resolvers class MergeRequestsResolver < BaseResolver include ResolvesMergeRequests + extend ::Gitlab::Graphql::NegatableArguments type ::Types::MergeRequestType.connection_type, null: true - alias_method :project, :synchronized_object + alias_method :project, :object def self.accept_assignee argument :assignee_username, GraphQL::STRING_TYPE, - required: false, - description: 'Username of the assignee.' + required: false, + description: 'Username of the assignee.' end def self.accept_author argument :author_username, GraphQL::STRING_TYPE, - required: false, - description: 'Username of the author.' + required: false, + description: 'Username of the author.' end def self.accept_reviewer argument :reviewer_username, GraphQL::STRING_TYPE, - required: false, - description: 'Username of the reviewer.' + required: false, + description: 'Username of the reviewer.' end argument :iids, [GraphQL::STRING_TYPE], - required: false, - description: 'Array of IIDs of merge requests, for example `[1, 2]`.' + required: false, + description: 'Array of IIDs of merge requests, for example `[1, 2]`.' argument :source_branches, [GraphQL::STRING_TYPE], required: false, as: :source_branch, - description: 'Array of source branch names. All resolved merge requests will have one of these branches as their source.' + description: <<~DESC + Array of source branch names. + All resolved merge requests will have one of these branches as their source. + DESC argument :target_branches, [GraphQL::STRING_TYPE], required: false, as: :target_branch, - description: 'Array of target branch names. All resolved merge requests will have one of these branches as their target.' + description: <<~DESC + Array of target branch names. + All resolved merge requests will have one of these branches as their target. + DESC argument :state, ::Types::MergeRequestStateEnum, required: false, @@ -62,6 +69,16 @@ module Resolvers required: false, default_value: :created_desc + negated do + argument :labels, [GraphQL::STRING_TYPE], + required: false, + as: :label_name, + description: 'Array of label names. All resolved merge requests will not have these labels.' + argument :milestone_title, GraphQL::STRING_TYPE, + required: false, + description: 'Title of the milestone.' + end + def self.single ::Resolvers::MergeRequestResolver end diff --git a/app/graphql/resolvers/metrics/dashboard_resolver.rb b/app/graphql/resolvers/metrics/dashboard_resolver.rb index a82a4a95254..0669fececd5 100644 --- a/app/graphql/resolvers/metrics/dashboard_resolver.rb +++ b/app/graphql/resolvers/metrics/dashboard_resolver.rb @@ -8,15 +8,16 @@ module Resolvers argument :path, GraphQL::STRING_TYPE, required: true, - description: "Path to a file which defines metrics dashboard " \ - "eg: 'config/prometheus/common_metrics.yml'." + description: <<~MD + Path to a file which defines a metrics dashboard eg: `"config/prometheus/common_metrics.yml"`. + MD alias_method :environment, :object - def resolve(**args) + def resolve(path:) return unless environment - ::PerformanceMonitoring::PrometheusDashboard.find_for(**args, **service_params) + ::PerformanceMonitoring::PrometheusDashboard.find_for(path: path, **service_params) end private diff --git a/app/graphql/resolvers/milestones_resolver.rb b/app/graphql/resolvers/milestones_resolver.rb index 9a715e4d08b..c94e3d9e1d8 100644 --- a/app/graphql/resolvers/milestones_resolver.rb +++ b/app/graphql/resolvers/milestones_resolver.rb @@ -7,7 +7,7 @@ module Resolvers argument :ids, [GraphQL::ID_TYPE], required: false, - description: 'Array of global milestone IDs, e.g., "gid://gitlab/Milestone/1".' + description: 'Array of global milestone IDs, e.g., `"gid://gitlab/Milestone/1"`.' argument :state, Types::MilestoneStateEnum, required: false, @@ -56,7 +56,7 @@ module Resolvers end def parent - synchronized_object + object end def parent_id_parameters(args) diff --git a/app/graphql/resolvers/package_details_resolver.rb b/app/graphql/resolvers/package_details_resolver.rb index e688e34599a..89d79747732 100644 --- a/app/graphql/resolvers/package_details_resolver.rb +++ b/app/graphql/resolvers/package_details_resolver.rb @@ -2,12 +2,20 @@ module Resolvers class PackageDetailsResolver < BaseResolver - type ::Types::Packages::PackageType, null: true + type ::Types::Packages::PackageDetailsType, null: true argument :id, ::Types::GlobalIDType[::Packages::Package], required: true, description: 'The global ID of the package.' + def ready?(**args) + context[self.class] ||= { executions: 0 } + context[self.class][:executions] += 1 + raise GraphQL::ExecutionError, "Package details can be requested only for one package at a time" if context[self.class][:executions] > 1 + + super + end + def resolve(id:) # TODO: remove this line when the compatibility layer is removed # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 diff --git a/app/graphql/resolvers/project_jobs_resolver.rb b/app/graphql/resolvers/project_jobs_resolver.rb new file mode 100644 index 00000000000..75068014242 --- /dev/null +++ b/app/graphql/resolvers/project_jobs_resolver.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Resolvers + class ProjectJobsResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + include LooksAhead + + type ::Types::Ci::JobType.connection_type, null: true + authorize :read_build + authorizes_object! + + argument :statuses, [::Types::Ci::JobStatusEnum], + required: false, + description: 'Filter jobs by status.' + + alias_method :project, :object + + def ready?(**args) + context[self.class] ||= { executions: 0 } + context[self.class][:executions] += 1 + raise GraphQL::ExecutionError, "Jobs can only be requested for one project at a time" if context[self.class][:executions] > 1 + + super + end + + def resolve_with_lookahead(statuses: nil) + jobs = ::Ci::JobsFinder.new(current_user: current_user, project: project, params: { scope: statuses }).execute + + apply_lookahead(jobs) + end + + private + + def preloads + { + artifacts: [:job_artifacts], + pipeline: [:user] + } + end + end +end diff --git a/app/graphql/resolvers/project_pipeline_resolver.rb b/app/graphql/resolvers/project_pipeline_resolver.rb index 8fca6b829c0..aa8808b15ac 100644 --- a/app/graphql/resolvers/project_pipeline_resolver.rb +++ b/app/graphql/resolvers/project_pipeline_resolver.rb @@ -31,7 +31,7 @@ module Resolvers end else BatchLoader::GraphQL.for(sha).batch(key: project) do |shas, loader, args| - finder = ::Ci::PipelinesFinder.new(project, current_user, shas: shas) + finder = ::Ci::PipelinesFinder.new(project, current_user, sha: shas) finder.execute.each { |pipeline| loader.call(pipeline.sha.to_s, pipeline) } end diff --git a/app/graphql/resolvers/projects/services_resolver.rb b/app/graphql/resolvers/projects/services_resolver.rb index f618bf2df77..ec31a7dbe6d 100644 --- a/app/graphql/resolvers/projects/services_resolver.rb +++ b/app/graphql/resolvers/projects/services_resolver.rb @@ -3,11 +3,11 @@ module Resolvers module Projects class ServicesResolver < BaseResolver - prepend ManualAuthorization include Gitlab::Graphql::Authorize::AuthorizeResource type Types::Projects::ServiceType.connection_type, null: true authorize :admin_project + authorizes_object! argument :active, GraphQL::BOOLEAN_TYPE, @@ -20,15 +20,7 @@ module Resolvers alias_method :project, :object - def resolve(**args) - authorize!(project) - - services(args[:active], args[:type]) - end - - private - - def services(active, type) + def resolve(active: nil, type: nil) servs = project.services servs = servs.by_active_flag(active) unless active.nil? servs = servs.by_type(type) unless type.blank? diff --git a/app/graphql/resolvers/repository_branch_names_resolver.rb b/app/graphql/resolvers/repository_branch_names_resolver.rb new file mode 100644 index 00000000000..45cfe229b2f --- /dev/null +++ b/app/graphql/resolvers/repository_branch_names_resolver.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Resolvers + class RepositoryBranchNamesResolver < BaseResolver + type ::GraphQL::STRING_TYPE, null: false + + calls_gitaly! + + argument :search_pattern, GraphQL::STRING_TYPE, + required: true, + description: 'The pattern to search for branch names by.' + + def resolve(search_pattern:) + Repositories::BranchNamesFinder.new(object, search: search_pattern).execute + end + end +end diff --git a/app/graphql/resolvers/snippets/blobs_resolver.rb b/app/graphql/resolvers/snippets/blobs_resolver.rb index 569b82149d3..4328d38d485 100644 --- a/app/graphql/resolvers/snippets/blobs_resolver.rb +++ b/app/graphql/resolvers/snippets/blobs_resolver.rb @@ -3,12 +3,12 @@ module Resolvers module Snippets class BlobsResolver < BaseResolver - prepend ManualAuthorization include Gitlab::Graphql::Authorize::AuthorizeResource type Types::Snippets::BlobType.connection_type, null: true authorize :read_snippet calls_gitaly! + authorizes_object! alias_method :snippet, :object @@ -17,7 +17,6 @@ module Resolvers description: 'Paths of the blobs.' def resolve(paths: []) - authorize!(snippet) return [snippet.blob] if snippet.empty_repo? if paths.empty? diff --git a/app/graphql/resolvers/timelog_resolver.rb b/app/graphql/resolvers/timelog_resolver.rb new file mode 100644 index 00000000000..aebd04259ee --- /dev/null +++ b/app/graphql/resolvers/timelog_resolver.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +module Resolvers + class TimelogResolver < BaseResolver + include LooksAhead + + type ::Types::TimelogType.connection_type, null: false + + argument :start_date, Types::TimeType, + required: false, + description: 'List time logs within a date range where the logged date is equal to or after startDate.' + + argument :end_date, Types::TimeType, + required: false, + description: 'List time logs within a date range where the logged date is equal to or before endDate.' + + argument :start_time, Types::TimeType, + required: false, + description: 'List time-logs within a time range where the logged time is equal to or after startTime.' + + argument :end_time, Types::TimeType, + required: false, + description: 'List time-logs within a time range where the logged time is equal to or before endTime.' + + def resolve_with_lookahead(**args) + return Timelog.none unless timelogs_available_for_user? + + validate_params_presence!(args) + transformed_args = transform_args(args) + validate_time_difference!(transformed_args) + + find_timelogs(transformed_args) + end + + private + + def preloads + { + note: [:note] + } + end + + def find_timelogs(args) + apply_lookahead(group.timelogs(args[:start_time], args[:end_time])) + end + + def timelogs_available_for_user? + group&.user_can_access_group_timelogs?(context[:current_user]) + end + + def validate_params_presence!(args) + message = case time_params_count(args) + when 0 + 'Start and End arguments must be present' + when 1 + 'Both Start and End arguments must be present' + when 2 + validate_duplicated_args(args) + when 3 || 4 + 'Only Time or Date arguments must be present' + end + + raise_argument_error(message) if message + end + + def validate_time_difference!(args) + message = if args[:end_time] < args[:start_time] + 'Start argument must be before End argument' + elsif args[:end_time] - args[:start_time] > 60.days + 'The time range period cannot contain more than 60 days' + end + + raise_argument_error(message) if message + end + + def transform_args(args) + return args if args.keys == [:start_time, :end_time] + + time_args = args.except(:start_date, :end_date) + + if time_args.empty? + time_args[:start_time] = args[:start_date].beginning_of_day + time_args[:end_time] = args[:end_date].end_of_day + elsif time_args.key?(:start_time) + time_args[:end_time] = args[:end_date].end_of_day + elsif time_args.key?(:end_time) + time_args[:start_time] = args[:start_date].beginning_of_day + end + + time_args + end + + def time_params_count(args) + [:start_time, :end_time, :start_date, :end_date].count { |param| args.key?(param) } + end + + def validate_duplicated_args(args) + if args.key?(:start_time) && args.key?(:start_date) || + args.key?(:end_time) && args.key?(:end_date) + 'Both Start and End arguments must be present' + end + end + + def raise_argument_error(message) + raise Gitlab::Graphql::Errors::ArgumentError, message + end + + def group + @group ||= object.respond_to?(:sync) ? object.sync : object + end + end +end diff --git a/app/graphql/resolvers/user_merge_requests_resolver_base.rb b/app/graphql/resolvers/user_merge_requests_resolver_base.rb index 47967fe69f9..221a43f691d 100644 --- a/app/graphql/resolvers/user_merge_requests_resolver_base.rb +++ b/app/graphql/resolvers/user_merge_requests_resolver_base.rb @@ -4,16 +4,24 @@ module Resolvers 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_path, + type: GraphQL::STRING_TYPE, + required: false, + description: <<~DESC + The full-path of the project the authored merge requests should be in. + Incompatible with projectId. + DESC - 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.' + argument :project_id, + type: ::Types::GlobalIDType[::Project], + required: false, + description: <<~DESC + The global ID of the project the authored merge requests should be in. + Incompatible with projectPath. + DESC attr_reader :project - alias_method :user, :synchronized_object + alias_method :user, :object def ready?(project_id: nil, project_path: nil, **args) return early_return unless can_read_profile? @@ -22,8 +30,7 @@ module Resolvers load_project(project_path, project_id) return early_return unless can_read_project? elsif args[:iids].present? - raise ::Gitlab::Graphql::Errors::ArgumentError, - 'iids requires projectPath or projectId' + raise ::Gitlab::Graphql::Errors::ArgumentError, 'iids requires projectPath or projectId' end super(**args) diff --git a/app/graphql/resolvers/user_starred_projects_resolver.rb b/app/graphql/resolvers/user_starred_projects_resolver.rb index db420b3d116..a8abe759f27 100644 --- a/app/graphql/resolvers/user_starred_projects_resolver.rb +++ b/app/graphql/resolvers/user_starred_projects_resolver.rb @@ -2,11 +2,11 @@ module Resolvers class UserStarredProjectsResolver < BaseResolver - type Types::ProjectType, null: true + type Types::ProjectType.connection_type, null: true argument :search, GraphQL::STRING_TYPE, - required: false, - description: 'Search query.' + required: false, + description: 'Search query.' alias_method :user, :object diff --git a/app/graphql/resolvers/users/snippets_resolver.rb b/app/graphql/resolvers/users/snippets_resolver.rb index e8048b9deb9..ee1727aadbe 100644 --- a/app/graphql/resolvers/users/snippets_resolver.rb +++ b/app/graphql/resolvers/users/snippets_resolver.rb @@ -5,6 +5,7 @@ module Resolvers module Users class SnippetsResolver < BaseResolver include ResolvesSnippets + include Gitlab::Allowable alias_method :user, :object @@ -14,6 +15,12 @@ module Resolvers private + def resolve_snippets(_args) + return Snippet.none unless Ability.allowed?(current_user, :read_user_profile, user) + + super + end + def snippet_finder_params(args) super.merge(author: user) end |