diff options
Diffstat (limited to 'app/graphql')
71 files changed, 644 insertions, 163 deletions
diff --git a/app/graphql/mutations/alert_management/prometheus_integration/create.rb b/app/graphql/mutations/alert_management/prometheus_integration/create.rb index c6dc85dc07c..4d0a5a5cb13 100644 --- a/app/graphql/mutations/alert_management/prometheus_integration/create.rb +++ b/app/graphql/mutations/alert_management/prometheus_integration/create.rb @@ -23,7 +23,7 @@ module Mutations def resolve(args) project = authorized_find!(args[:project_path]) - return integration_exists if project.prometheus_service + return integration_exists if project.prometheus_integration result = ::Projects::Operations::UpdateService.new( project, @@ -32,7 +32,7 @@ module Mutations **token_attributes ).execute - response(project.prometheus_service, result) + response(project.prometheus_integration, result) end private diff --git a/app/graphql/mutations/alert_management/prometheus_integration/prometheus_integration_base.rb b/app/graphql/mutations/alert_management/prometheus_integration/prometheus_integration_base.rb index cb243f49b33..d8678ea4d61 100644 --- a/app/graphql/mutations/alert_management/prometheus_integration/prometheus_integration_base.rb +++ b/app/graphql/mutations/alert_management/prometheus_integration/prometheus_integration_base.rb @@ -14,7 +14,7 @@ module Mutations private def find_object(id:) - GitlabSchema.object_from_id(id, expected_class: ::PrometheusService) + GitlabSchema.object_from_id(id, expected_class: ::Integrations::Prometheus) end def response(integration, result) diff --git a/app/graphql/mutations/alert_management/prometheus_integration/reset_token.rb b/app/graphql/mutations/alert_management/prometheus_integration/reset_token.rb index 428be091436..33a12405583 100644 --- a/app/graphql/mutations/alert_management/prometheus_integration/reset_token.rb +++ b/app/graphql/mutations/alert_management/prometheus_integration/reset_token.rb @@ -6,7 +6,7 @@ module Mutations class ResetToken < PrometheusIntegrationBase graphql_name 'PrometheusIntegrationResetToken' - argument :id, Types::GlobalIDType[::PrometheusService], + argument :id, Types::GlobalIDType[::Integrations::Prometheus], required: true, description: "The ID of the integration to mutate." diff --git a/app/graphql/mutations/alert_management/prometheus_integration/update.rb b/app/graphql/mutations/alert_management/prometheus_integration/update.rb index 7594766176f..ddab1af908c 100644 --- a/app/graphql/mutations/alert_management/prometheus_integration/update.rb +++ b/app/graphql/mutations/alert_management/prometheus_integration/update.rb @@ -6,7 +6,7 @@ module Mutations class Update < PrometheusIntegrationBase graphql_name 'PrometheusIntegrationUpdate' - argument :id, Types::GlobalIDType[::PrometheusService], + argument :id, Types::GlobalIDType[::Integrations::Prometheus], required: true, description: "The ID of the integration to mutate." diff --git a/app/graphql/mutations/boards/issues/issue_move_list.rb b/app/graphql/mutations/boards/issues/issue_move_list.rb index 4c9752c6343..b73657ea0c8 100644 --- a/app/graphql/mutations/boards/issues/issue_move_list.rb +++ b/app/graphql/mutations/boards/issues/issue_move_list.rb @@ -53,8 +53,6 @@ module Mutations end def resolve(board:, project_path:, iid:, **args) - Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/247861') - issue = authorized_find!(project_path: project_path, iid: iid) move_params = { id: issue.id, board_id: board.id }.merge(move_arguments(args)) diff --git a/app/graphql/mutations/ci/job_token_scope/add_project.rb b/app/graphql/mutations/ci/job_token_scope/add_project.rb new file mode 100644 index 00000000000..30f98a537b5 --- /dev/null +++ b/app/graphql/mutations/ci/job_token_scope/add_project.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Mutations + module Ci + module JobTokenScope + class AddProject < BaseMutation + include FindsProject + + graphql_name 'CiJobTokenScopeAddProject' + + authorize :admin_project + + argument :project_path, GraphQL::ID_TYPE, + required: true, + description: 'The project that the CI job token scope belongs to.' + + argument :target_project_path, GraphQL::ID_TYPE, + required: true, + description: 'The project to be added to the CI job token scope.' + + field :ci_job_token_scope, + Types::Ci::JobTokenScopeType, + null: true, + description: "The CI job token's scope of access." + + def resolve(project_path:, target_project_path:) + project = authorized_find!(project_path) + target_project = Project.find_by_full_path(target_project_path) + + result = ::Ci::JobTokenScope::AddProjectService + .new(project, current_user) + .execute(target_project) + + if result.success? + { + ci_job_token_scope: ::Ci::JobToken::Scope.new(project), + errors: [] + } + else + { + ci_job_token_scope: nil, + errors: [result.message] + } + end + end + end + end + end +end diff --git a/app/graphql/mutations/ci/job_token_scope/remove_project.rb b/app/graphql/mutations/ci/job_token_scope/remove_project.rb new file mode 100644 index 00000000000..71c9083bef8 --- /dev/null +++ b/app/graphql/mutations/ci/job_token_scope/remove_project.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Mutations + module Ci + module JobTokenScope + class RemoveProject < BaseMutation + include FindsProject + + graphql_name 'CiJobTokenScopeRemoveProject' + + authorize :admin_project + + argument :project_path, GraphQL::ID_TYPE, + required: true, + description: 'The project that the CI job token scope belongs to.' + + argument :target_project_path, GraphQL::ID_TYPE, + required: true, + description: 'The project to be removed from the CI job token scope.' + + field :ci_job_token_scope, + Types::Ci::JobTokenScopeType, + null: true, + description: "The CI job token's scope of access." + + def resolve(project_path:, target_project_path:) + project = authorized_find!(project_path) + target_project = Project.find_by_full_path(target_project_path) + + result = ::Ci::JobTokenScope::RemoveProjectService + .new(project, current_user) + .execute(target_project) + + if result.success? + { + ci_job_token_scope: ::Ci::JobToken::Scope.new(project), + errors: [] + } + else + { + ci_job_token_scope: nil, + errors: [result.message] + } + end + end + end + end + end +end diff --git a/app/graphql/mutations/ci/runner/update.rb b/app/graphql/mutations/ci/runner/update.rb index 5b61b2ffc0d..4cdfa1fb1bd 100644 --- a/app/graphql/mutations/ci/runner/update.rb +++ b/app/graphql/mutations/ci/runner/update.rb @@ -66,3 +66,5 @@ module Mutations end end end + +Mutations::Ci::Runner::Update.prepend_mod_with('Mutations::Ci::Runner::Update') diff --git a/app/graphql/mutations/concerns/mutations/package_eventable.rb b/app/graphql/mutations/concerns/mutations/package_eventable.rb index 86fd7b9a88a..134e3659125 100644 --- a/app/graphql/mutations/concerns/mutations/package_eventable.rb +++ b/app/graphql/mutations/concerns/mutations/package_eventable.rb @@ -8,7 +8,7 @@ module Mutations def track_event(event, scope) ::Packages::CreateEventService.new(nil, current_user, event_name: event, scope: scope).execute - ::Gitlab::Tracking.event(event.to_s, scope.to_s) + ::Gitlab::Tracking.event(event.to_s, scope.to_s, user: current_user) end end end diff --git a/app/graphql/mutations/concerns/mutations/spam_protection.rb b/app/graphql/mutations/concerns/mutations/spam_protection.rb index d765da23a4b..341067710b2 100644 --- a/app/graphql/mutations/concerns/mutations/spam_protection.rb +++ b/app/graphql/mutations/concerns/mutations/spam_protection.rb @@ -16,25 +16,6 @@ module Mutations private - # additional_spam_params -> hash - # - # Used from a spammable mutation's #resolve method to generate - # the required additional spam/CAPTCHA params which must be merged into the params - # passed to the constructor of a service, where they can then be used in the service - # to perform spam checking via SpamActionService. - # - # Also accesses the #context of the mutation's Resolver superclass to obtain the request. - # - # Example: - # - # existing_args.merge!(additional_spam_params) - def additional_spam_params - { - api: true, - request: context[:request] - } - end - def spam_action_response(object) fields = spam_action_response_fields(object) diff --git a/app/graphql/mutations/issues/create.rb b/app/graphql/mutations/issues/create.rb index 3a57e2434a5..7c4a851f8aa 100644 --- a/app/graphql/mutations/issues/create.rb +++ b/app/graphql/mutations/issues/create.rb @@ -73,7 +73,8 @@ module Mutations project = authorized_find!(project_path) params = build_create_issue_params(attributes.merge(author_id: current_user.id)) - issue = ::Issues::CreateService.new(project: project, current_user: current_user, params: params).execute + spam_params = ::Spam::SpamParams.new_from_request(request: context[:request]) + issue = ::Issues::CreateService.new(project: project, current_user: current_user, params: params, spam_params: spam_params).execute if issue.spam? issue.errors.add(:base, 'Spam detected.') diff --git a/app/graphql/mutations/issues/set_confidential.rb b/app/graphql/mutations/issues/set_confidential.rb index 8e88b31d9ed..cfee2420ee0 100644 --- a/app/graphql/mutations/issues/set_confidential.rb +++ b/app/graphql/mutations/issues/set_confidential.rb @@ -3,6 +3,8 @@ module Mutations module Issues class SetConfidential < Base + include Mutations::SpamProtection + graphql_name 'IssueSetConfidential' argument :confidential, @@ -13,9 +15,13 @@ module Mutations def resolve(project_path:, iid:, confidential:) issue = authorized_find!(project_path: project_path, iid: iid) project = issue.project + # Changing confidentiality affects spam checking rules, therefore we need to provide + # spam_params so a check can be performed. + spam_params = ::Spam::SpamParams.new_from_request(request: context[:request]) - ::Issues::UpdateService.new(project: project, current_user: current_user, params: { confidential: confidential }) + ::Issues::UpdateService.new(project: project, current_user: current_user, params: { confidential: confidential }, spam_params: spam_params) .execute(issue) + check_spam_action_response!(issue) { issue: issue, diff --git a/app/graphql/mutations/issues/update.rb b/app/graphql/mutations/issues/update.rb index eb16b7b38d0..1ceed868a6c 100644 --- a/app/graphql/mutations/issues/update.rb +++ b/app/graphql/mutations/issues/update.rb @@ -31,7 +31,8 @@ module Mutations issue = authorized_find!(project_path: project_path, iid: iid) project = issue.project - ::Issues::UpdateService.new(project: project, current_user: current_user, params: args).execute(issue) + spam_params = ::Spam::SpamParams.new_from_request(request: context[:request]) + ::Issues::UpdateService.new(project: project, current_user: current_user, params: args, spam_params: spam_params).execute(issue) { issue: issue, diff --git a/app/graphql/mutations/packages/destroy.rb b/app/graphql/mutations/packages/destroy.rb new file mode 100644 index 00000000000..979a54da6bd --- /dev/null +++ b/app/graphql/mutations/packages/destroy.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Mutations + module Packages + class Destroy < ::Mutations::BaseMutation + graphql_name 'DestroyPackage' + + authorize :destroy_package + + argument :id, + ::Types::GlobalIDType[::Packages::Package], + required: true, + description: 'ID of the Package.' + + def resolve(id:) + package = authorized_find!(id: id) + + result = ::Packages::DestroyPackageService.new(container: package, current_user: current_user).execute + + errors = result.error? ? Array.wrap(result[:message]) : [] + + { + errors: errors + } + end + + private + + def find_object(id:) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = ::Types::GlobalIDType[::Packages::Package].coerce_isolated_input(id) + GitlabSchema.find_by_gid(id) + end + end + end +end diff --git a/app/graphql/mutations/release_asset_links/create.rb b/app/graphql/mutations/release_asset_links/create.rb index 02704efb47c..ff9d98d2c0f 100644 --- a/app/graphql/mutations/release_asset_links/create.rb +++ b/app/graphql/mutations/release_asset_links/create.rb @@ -33,6 +33,10 @@ module Mutations return { link: nil, errors: [message] } end + unless Ability.allowed?(current_user, :update_release, release) + raise_resource_not_available_error! + end + new_link = release.links.create(link_attrs) unless new_link.persisted? diff --git a/app/graphql/mutations/security/ci_configuration/base_security_analyzer.rb b/app/graphql/mutations/security/ci_configuration/base_security_analyzer.rb new file mode 100644 index 00000000000..090a9a4e0ef --- /dev/null +++ b/app/graphql/mutations/security/ci_configuration/base_security_analyzer.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Mutations + module Security + module CiConfiguration + class BaseSecurityAnalyzer < BaseMutation + include FindsProject + + argument :project_path, GraphQL::ID_TYPE, + required: true, + description: 'Full path of the project.' + + field :success_path, GraphQL::STRING_TYPE, null: true, + description: 'Redirect path to use when the response is successful.' + + field :branch, GraphQL::STRING_TYPE, null: true, + description: 'Branch that has the new/modified `.gitlab-ci.yml` file.' + + authorize :push_code + + def resolve(project_path:, **args) + project = authorized_find!(project_path) + + result = configure_analyzer(project, **args) + prepare_response(result) + end + + private + + def configure_analyzer(project, **args) + raise NotImplementedError + end + + def prepare_response(result) + { + branch: result.payload[:branch], + success_path: result.payload[:success_path], + errors: result.errors + } + end + end + end + end +end diff --git a/app/graphql/mutations/security/ci_configuration/configure_sast.rb b/app/graphql/mutations/security/ci_configuration/configure_sast.rb index 237aff1f052..7ce0bf83a4b 100644 --- a/app/graphql/mutations/security/ci_configuration/configure_sast.rb +++ b/app/graphql/mutations/security/ci_configuration/configure_sast.rb @@ -3,9 +3,7 @@ module Mutations module Security module CiConfiguration - class ConfigureSast < BaseMutation - include FindsProject - + class ConfigureSast < BaseSecurityAnalyzer graphql_name 'ConfigureSast' description <<~DESC Configure SAST for a project by enabling SAST in a new or modified @@ -13,37 +11,12 @@ module Mutations create a Merge Request are a part of the response. DESC - argument :project_path, GraphQL::ID_TYPE, - required: true, - description: 'Full path of the project.' - argument :configuration, ::Types::CiConfiguration::Sast::InputType, required: true, description: 'SAST CI configuration for the project.' - field :success_path, GraphQL::STRING_TYPE, null: true, - description: 'Redirect path to use when the response is successful.' - - field :branch, GraphQL::STRING_TYPE, null: true, - description: 'Branch that has the new/modified `.gitlab-ci.yml` file.' - - authorize :push_code - - def resolve(project_path:, configuration:) - project = authorized_find!(project_path) - - result = ::Security::CiConfiguration::SastCreateService.new(project, current_user, configuration).execute - prepare_response(result) - end - - private - - def prepare_response(result) - { - branch: result.payload[:branch], - success_path: result.payload[:success_path], - errors: result.errors - } + def configure_analyzer(project, **args) + ::Security::CiConfiguration::SastCreateService.new(project, current_user, args[:configuration]).execute end end end diff --git a/app/graphql/mutations/security/ci_configuration/configure_secret_detection.rb b/app/graphql/mutations/security/ci_configuration/configure_secret_detection.rb index 32ad670edaa..54322babb26 100644 --- a/app/graphql/mutations/security/ci_configuration/configure_secret_detection.rb +++ b/app/graphql/mutations/security/ci_configuration/configure_secret_detection.rb @@ -3,9 +3,7 @@ module Mutations module Security module CiConfiguration - class ConfigureSecretDetection < BaseMutation - include FindsProject - + class ConfigureSecretDetection < BaseSecurityAnalyzer graphql_name 'ConfigureSecretDetection' description <<~DESC Configure Secret Detection for a project by enabling Secret Detection @@ -14,33 +12,8 @@ module Mutations response. DESC - argument :project_path, GraphQL::ID_TYPE, - required: true, - description: 'Full path of the project.' - - field :success_path, GraphQL::STRING_TYPE, null: true, - description: 'Redirect path to use when the response is successful.' - - field :branch, GraphQL::STRING_TYPE, null: true, - description: 'Branch that has the new/modified `.gitlab-ci.yml` file.' - - authorize :push_code - - def resolve(project_path:) - project = authorized_find!(project_path) - - result = ::Security::CiConfiguration::SecretDetectionCreateService.new(project, current_user).execute - prepare_response(result) - end - - private - - def prepare_response(result) - { - branch: result.payload[:branch], - success_path: result.payload[:success_path], - errors: result.errors - } + def configure_analyzer(project, **_args) + ::Security::CiConfiguration::SecretDetectionCreateService.new(project, current_user).execute end end end diff --git a/app/graphql/mutations/snippets/create.rb b/app/graphql/mutations/snippets/create.rb index d1ad0697acd..765163e73a1 100644 --- a/app/graphql/mutations/snippets/create.rb +++ b/app/graphql/mutations/snippets/create.rb @@ -49,7 +49,9 @@ module Mutations process_args_for_params!(args) - service_response = ::Snippets::CreateService.new(project: project, current_user: current_user, params: args).execute + spam_params = ::Spam::SpamParams.new_from_request(request: context[:request]) + service = ::Snippets::CreateService.new(project: project, current_user: current_user, params: args, spam_params: spam_params) + service_response = service.execute # Only when the user is not an api user and the operation was successful if !api_user? && service_response.success? @@ -81,12 +83,6 @@ module Mutations # it's the expected key param args[:files] = args.delete(:uploaded_files) - if Feature.enabled?(:snippet_spam) - args.merge!(additional_spam_params) - else - args[:disable_spam_action_service] = true - end - # Return nil to make it explicit that this method is mutating the args parameter, and that # the return value is not relevant and is not to be used. nil diff --git a/app/graphql/mutations/snippets/update.rb b/app/graphql/mutations/snippets/update.rb index 2e1382e1cb1..792c631e5ca 100644 --- a/app/graphql/mutations/snippets/update.rb +++ b/app/graphql/mutations/snippets/update.rb @@ -34,7 +34,9 @@ module Mutations process_args_for_params!(args) - service_response = ::Snippets::UpdateService.new(project: snippet.project, current_user: current_user, params: args).execute(snippet) + spam_params = ::Spam::SpamParams.new_from_request(request: context[:request]) + service = ::Snippets::UpdateService.new(project: snippet.project, current_user: current_user, params: args, spam_params: spam_params) + service_response = service.execute(snippet) # TODO: DRY this up - From here down, this is all duplicated with Mutations::Snippets::Create#resolve, except for # `snippet.reset`, which is required in order to return the object in its non-dirty, unmodified, database state @@ -62,12 +64,6 @@ module Mutations def process_args_for_params!(args) convert_blob_actions_to_snippet_actions!(args) - if Feature.enabled?(:snippet_spam) - args.merge!(additional_spam_params) - else - args[:disable_spam_action_service] = true - end - # Return nil to make it explicit that this method is mutating the args parameter, and that # the return value is not relevant and is not to be used. nil diff --git a/app/graphql/queries/container_registry/get_container_repositories.query.graphql b/app/graphql/queries/container_registry/get_container_repositories.query.graphql index 4683ef9dfdb..df0b590acac 100644 --- a/app/graphql/queries/container_registry/get_container_repositories.query.graphql +++ b/app/graphql/queries/container_registry/get_container_repositories.query.graphql @@ -29,6 +29,7 @@ query getProjectContainerRepositories( canDelete createdAt expirationPolicyStartedAt + expirationPolicyCleanupStatus __typename } pageInfo { @@ -61,6 +62,7 @@ query getProjectContainerRepositories( canDelete createdAt expirationPolicyStartedAt + expirationPolicyCleanupStatus __typename } pageInfo { diff --git a/app/graphql/queries/epic/epic_children.query.graphql b/app/graphql/queries/epic/epic_children.query.graphql index 5ee27052f95..b0e55811b7d 100644 --- a/app/graphql/queries/epic/epic_children.query.graphql +++ b/app/graphql/queries/epic/epic_children.query.graphql @@ -42,6 +42,7 @@ fragment EpicNode on Epic { relationPath createdAt closedAt + confidential hasChildren hasIssues group { diff --git a/app/graphql/queries/pipelines/get_pipeline_details.query.graphql b/app/graphql/queries/pipelines/get_pipeline_details.query.graphql index 873ecc81466..4e4caa1e27c 100644 --- a/app/graphql/queries/pipelines/get_pipeline_details.query.graphql +++ b/app/graphql/queries/pipelines/get_pipeline_details.query.graphql @@ -29,6 +29,9 @@ query getPipelineDetails($projectPath: ID!, $iid: ID!) { iid complete usesNeeds + userPermissions { + updatePipeline + } downstream { __typename nodes { diff --git a/app/graphql/queries/releases/all_releases.query.graphql b/app/graphql/queries/releases/all_releases.query.graphql new file mode 100644 index 00000000000..ab8cbcb8aa3 --- /dev/null +++ b/app/graphql/queries/releases/all_releases.query.graphql @@ -0,0 +1,105 @@ +# This query is identical to +# `app/assets/javascripts/releases/graphql/queries/all_releases.query.graphql`. +# These two queries should be kept in sync. +query allReleases( + $fullPath: ID! + $first: Int + $last: Int + $before: String + $after: String + $sort: ReleaseSort +) { + project(fullPath: $fullPath) { + __typename + releases(first: $first, last: $last, before: $before, after: $after, sort: $sort) { + __typename + nodes { + __typename + name + tagName + tagPath + descriptionHtml + releasedAt + createdAt + upcomingRelease + assets { + __typename + count + sources { + __typename + nodes { + __typename + format + url + } + } + links { + __typename + nodes { + __typename + id + name + url + directAssetUrl + linkType + external + } + } + } + evidences { + __typename + nodes { + __typename + filepath + collectedAt + sha + } + } + links { + __typename + editUrl + selfUrl + openedIssuesUrl + closedIssuesUrl + openedMergeRequestsUrl + mergedMergeRequestsUrl + closedMergeRequestsUrl + } + commit { + __typename + sha + webUrl + title + } + author { + __typename + webUrl + avatarUrl + username + } + milestones { + __typename + nodes { + __typename + id + title + description + webPath + stats { + __typename + totalIssuesCount + closedIssuesCount + } + } + } + } + pageInfo { + __typename + startCursor + hasPreviousPage + hasNextPage + endCursor + } + } + } +} diff --git a/app/graphql/resolvers/alert_management/alert_resolver.rb b/app/graphql/resolvers/alert_management/alert_resolver.rb index 008641ed88a..62744e719da 100644 --- a/app/graphql/resolvers/alert_management/alert_resolver.rb +++ b/app/graphql/resolvers/alert_management/alert_resolver.rb @@ -12,7 +12,7 @@ module Resolvers argument :statuses, [Types::AlertManagement::StatusEnum], as: :status, required: false, - description: 'Alerts with the specified statues. For example, [TRIGGERED].' + description: 'Alerts with the specified statues. For example, `[TRIGGERED]`.' argument :sort, Types::AlertManagement::AlertSortEnum, description: 'Sort alerts by this criteria.', diff --git a/app/graphql/resolvers/alert_management/integrations_resolver.rb b/app/graphql/resolvers/alert_management/integrations_resolver.rb index cb7e73c2d1a..a97650e95d9 100644 --- a/app/graphql/resolvers/alert_management/integrations_resolver.rb +++ b/app/graphql/resolvers/alert_management/integrations_resolver.rb @@ -34,7 +34,7 @@ module Resolvers def prometheus_integrations return [] unless prometheus_integrations_allowed? - Array(project.prometheus_service) + Array(project.prometheus_integration) end def http_integrations @@ -54,7 +54,7 @@ module Resolvers def expected_integration_types [].tap do |types| types << ::AlertManagement::HttpIntegration if http_integrations_allowed? - types << ::PrometheusService if prometheus_integrations_allowed? + types << ::Integrations::Prometheus if prometheus_integrations_allowed? end end end diff --git a/app/graphql/resolvers/board_list_issues_resolver.rb b/app/graphql/resolvers/board_list_issues_resolver.rb index dac93b91469..25fb35ec74b 100644 --- a/app/graphql/resolvers/board_list_issues_resolver.rb +++ b/app/graphql/resolvers/board_list_issues_resolver.rb @@ -16,7 +16,7 @@ module Resolvers filter_params = item_filters(args[:filters]).merge(board_id: list.board.id, id: list.id) service = ::Boards::Issues::ListService.new(list.board.resource_parent, context[:current_user], filter_params) - offset_pagination(service.execute) + service.execute end # https://gitlab.com/gitlab-org/gitlab/-/issues/235681 diff --git a/app/graphql/resolvers/ci/config_resolver.rb b/app/graphql/resolvers/ci/config_resolver.rb index 252c9d3acf0..f2e33251b9c 100644 --- a/app/graphql/resolvers/ci/config_resolver.rb +++ b/app/graphql/resolvers/ci/config_resolver.rb @@ -18,6 +18,10 @@ module Resolvers required: true, description: 'The project of the CI config.' + argument :sha, GraphQL::STRING_TYPE, + required: false, + description: "Sha for the pipeline." + argument :content, GraphQL::STRING_TYPE, required: true, description: "Contents of `.gitlab-ci.yml`." @@ -26,11 +30,11 @@ module Resolvers required: false, description: 'Run pipeline creation simulation, or only do static check.' - def resolve(project_path:, content:, dry_run: false) + def resolve(project_path:, content:, sha: nil, dry_run: false) project = authorized_find!(project_path: project_path) result = ::Gitlab::Ci::Lint - .new(project: project, current_user: context[:current_user]) + .new(project: project, current_user: context[:current_user], sha: sha) .validate(content, dry_run: dry_run) response(result).merge(merged_yaml: result.merged_yaml) diff --git a/app/graphql/resolvers/ci/job_token_scope_resolver.rb b/app/graphql/resolvers/ci/job_token_scope_resolver.rb new file mode 100644 index 00000000000..ca76a7b94fc --- /dev/null +++ b/app/graphql/resolvers/ci/job_token_scope_resolver.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Resolvers + module Ci + class JobTokenScopeResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + authorize :admin_project + description 'Container for resources that can be accessed by a CI job token from the current project. Null if job token scope setting is disabled.' + type ::Types::Ci::JobTokenScopeType, null: true + + def resolve + authorize!(object) + + return unless object.ci_job_token_scope_enabled? + + ::Ci::JobToken::Scope.new(object) + end + end + end +end diff --git a/app/graphql/resolvers/ci/runners_resolver.rb b/app/graphql/resolvers/ci/runners_resolver.rb index 3ad1e2780dd..5074a248e18 100644 --- a/app/graphql/resolvers/ci/runners_resolver.rb +++ b/app/graphql/resolvers/ci/runners_resolver.rb @@ -3,6 +3,8 @@ module Resolvers module Ci class RunnersResolver < BaseResolver + include LooksAhead + type Types::Ci::RunnerType.connection_type, null: true argument :status, ::Types::Ci::RunnerStatusEnum, @@ -25,10 +27,11 @@ module Resolvers required: false, description: 'Sort order of results.' - def resolve(**args) - ::Ci::RunnersFinder - .new(current_user: current_user, params: runners_finder_params(args)) - .execute + def resolve_with_lookahead(**args) + apply_lookahead( + ::Ci::RunnersFinder + .new(current_user: current_user, params: runners_finder_params(args)) + .execute) end private @@ -39,7 +42,10 @@ module Resolvers type_type: params[:type], tag_name: params[:tag_list], search: params[:search], - sort: params[:sort]&.to_s + sort: params[:sort]&.to_s, + preload: { + tag_name: node_selection&.selects?(:tag_list) + } }.compact end end diff --git a/app/graphql/resolvers/ci/template_resolver.rb b/app/graphql/resolvers/ci/template_resolver.rb index dd910116544..7f5a1a486d7 100644 --- a/app/graphql/resolvers/ci/template_resolver.rb +++ b/app/graphql/resolvers/ci/template_resolver.rb @@ -6,7 +6,7 @@ module Resolvers type Types::Ci::TemplateType, null: true argument :name, GraphQL::STRING_TYPE, required: true, - description: 'Name of the CI/CD template to search for.' + description: 'Name of the CI/CD template to search for. Template must be formatted as `Name.gitlab-ci.yml`.' alias_method :project, :object diff --git a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb index aa08d62c6a5..c24f4dedc0e 100644 --- a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb +++ b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb @@ -11,7 +11,7 @@ module IssueResolverArguments description: 'IID of the issue. For example, "1".' argument :iids, [GraphQL::STRING_TYPE], required: false, - description: 'List of IIDs of issues. For example, ["1", "2"].' + description: 'List of IIDs of issues. For example, `["1", "2"]`.' argument :label_name, [GraphQL::STRING_TYPE, null: true], required: false, description: 'Labels applied to this issue.' diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb index 93e679b2d0c..2017eb7decd 100644 --- a/app/graphql/resolvers/issues_resolver.rb +++ b/app/graphql/resolvers/issues_resolver.rb @@ -15,6 +15,7 @@ module Resolvers type Types::IssueType.connection_type, null: true NON_STABLE_CURSOR_SORTS = %i[priority_asc priority_desc + popularity_asc popularity_desc label_priority_asc label_priority_desc milestone_due_asc milestone_due_desc].freeze diff --git a/app/graphql/resolvers/milestones_resolver.rb b/app/graphql/resolvers/milestones_resolver.rb index 1241b41501d..4fa4c939a23 100644 --- a/app/graphql/resolvers/milestones_resolver.rb +++ b/app/graphql/resolvers/milestones_resolver.rb @@ -25,14 +25,27 @@ module Resolvers required: false, description: 'A date that the milestone contains.' + argument :sort, Types::MilestoneSortEnum, + description: 'Sort milestones by this criteria.', + required: false, + default_value: :due_date_asc + type Types::MilestoneType.connection_type, null: true + NON_STABLE_CURSOR_SORTS = %i[expired_last_due_date_asc expired_last_due_date_desc].freeze + def resolve(**args) validate_timeframe_params!(args) authorize! - MilestonesFinder.new(milestones_finder_params(args)).execute + milestones = MilestonesFinder.new(milestones_finder_params(args)).execute + + if non_stable_cursor_sort?(args[:sort]) + offset_pagination(milestones) + else + milestones + end end private @@ -43,6 +56,7 @@ module Resolvers state: args[:state] || 'all', title: args[:title], search_title: args[:search_title], + sort: args[:sort], containing_date: args[:containing_date] }.merge!(transform_timeframe_parameters(args)).merge!(parent_id_parameters(args)) end @@ -64,5 +78,9 @@ module Resolvers def parse_gids(gids) gids&.map { |gid| GitlabSchema.parse_gid(gid, expected_type: Milestone).model_id } end + + def non_stable_cursor_sort?(sort) + NON_STABLE_CURSOR_SORTS.include?(sort) + end end end diff --git a/app/graphql/resolvers/projects/jira_projects_resolver.rb b/app/graphql/resolvers/projects/jira_projects_resolver.rb index de85e8c42e6..864acb6d759 100644 --- a/app/graphql/resolvers/projects/jira_projects_resolver.rb +++ b/app/graphql/resolvers/projects/jira_projects_resolver.rb @@ -34,16 +34,16 @@ module Resolvers private - alias_method :jira_service, :object + alias_method :jira_integration, :object def project - jira_service&.project + jira_integration&.project end def jira_projects(name:) args = { query: name }.compact - Jira::Requests::Projects::ListService.new(project.jira_service, args).execute + Jira::Requests::Projects::ListService.new(project.jira_integration, args).execute end end end diff --git a/app/graphql/types/alert_management/alert_sort_enum.rb b/app/graphql/types/alert_management/alert_sort_enum.rb index 11a5cb95722..b4d09734e30 100644 --- a/app/graphql/types/alert_management/alert_sort_enum.rb +++ b/app/graphql/types/alert_management/alert_sort_enum.rb @@ -18,8 +18,8 @@ module Types value 'EVENT_COUNT_DESC', 'Events count by descending order.', value: :event_count_desc value 'SEVERITY_ASC', 'Severity from less critical to more critical.', value: :severity_asc value 'SEVERITY_DESC', 'Severity from more critical to less critical.', value: :severity_desc - value 'STATUS_ASC', 'Status by order: Ignored > Resolved > Acknowledged > Triggered.', value: :status_asc - value 'STATUS_DESC', 'Status by order: Triggered > Acknowledged > Resolved > Ignored.', value: :status_desc + value 'STATUS_ASC', 'Status by order: `Ignored > Resolved > Acknowledged > Triggered`.', value: :status_asc + value 'STATUS_DESC', 'Status by order: `Triggered > Acknowledged > Resolved > Ignored`.', value: :status_desc end end end diff --git a/app/graphql/types/alert_management/alert_type.rb b/app/graphql/types/alert_management/alert_type.rb index 5a2a5c68c8d..0ff0775ca86 100644 --- a/app/graphql/types/alert_management/alert_type.rb +++ b/app/graphql/types/alert_management/alert_type.rb @@ -8,7 +8,7 @@ module Types present_using ::AlertManagement::AlertPresenter - implements(Types::Notes::NoteableType) + implements(Types::Notes::NoteableInterface) authorize :read_alert_management_alert diff --git a/app/graphql/types/alert_management/integration_type.rb b/app/graphql/types/alert_management/integration_type.rb index d26d7348765..6cbc17cdbfb 100644 --- a/app/graphql/types/alert_management/integration_type.rb +++ b/app/graphql/types/alert_management/integration_type.rb @@ -43,7 +43,7 @@ module Types definition_methods do def resolve_type(object, context) - if object.is_a?(::PrometheusService) + if object.is_a?(::Integrations::Prometheus) Types::AlertManagement::PrometheusIntegrationType else Types::AlertManagement::HttpIntegrationType diff --git a/app/graphql/types/alert_management/prometheus_integration_type.rb b/app/graphql/types/alert_management/prometheus_integration_type.rb index 79f265f2f1e..27e4832d8f6 100644 --- a/app/graphql/types/alert_management/prometheus_integration_type.rb +++ b/app/graphql/types/alert_management/prometheus_integration_type.rb @@ -12,10 +12,10 @@ module Types authorize :admin_project - alias_method :prometheus_service, :object + alias_method :prometheus_integration, :object def name - prometheus_service.title + prometheus_integration.title end def type @@ -23,15 +23,15 @@ module Types end def token - prometheus_service.project&.alerting_setting&.token + prometheus_integration.project&.alerting_setting&.token end def url - prometheus_service.project && notify_project_prometheus_alerts_url(prometheus_service.project, format: :json) + prometheus_integration.project && notify_project_prometheus_alerts_url(prometheus_integration.project, format: :json) end def active - prometheus_service.manual_configuration? + prometheus_integration.manual_configuration? end end end diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb index 47caf83eb1c..75fdb41ceb6 100644 --- a/app/graphql/types/base_field.rb +++ b/app/graphql/types/base_field.rb @@ -65,7 +65,7 @@ module Types end def visible?(context) - return false if feature_flag.present? && !Feature.enabled?(feature_flag) + return false if feature_flag.present? && !Feature.enabled?(feature_flag, default_enabled: :yaml) super end @@ -95,7 +95,15 @@ module Types end def feature_documentation_message(key, description) - "#{description} Available only when feature flag `#{key}` is enabled." + message_parts = ["#{description} Available only when feature flag `#{key}` is enabled."] + + message_parts << if Feature::Definition.has_definition?(key) && Feature::Definition.default_enabled?(key) + "This flag is enabled by default." + else + "This flag is disabled by default, because the feature is experimental and is subject to change without notice." + end + + message_parts.join(' ') end def check_feature_flag(args) diff --git a/app/graphql/types/boards/board_issue_input_base_type.rb b/app/graphql/types/boards/board_issue_input_base_type.rb index 633221e61d1..30f4efcd403 100644 --- a/app/graphql/types/boards/board_issue_input_base_type.rb +++ b/app/graphql/types/boards/board_issue_input_base_type.rb @@ -6,7 +6,7 @@ module Types class BoardIssueInputBaseType < BoardIssuableInputBaseType argument :iids, [GraphQL::STRING_TYPE], required: false, - description: 'List of IIDs of issues. For example ["1", "2"].' + description: 'List of IIDs of issues. For example `["1", "2"]`.' argument :milestone_title, GraphQL::STRING_TYPE, required: false, diff --git a/app/graphql/types/ci/build_need_type.rb b/app/graphql/types/ci/build_need_type.rb index 3bd81f8fa8f..19ff758ad1d 100644 --- a/app/graphql/types/ci/build_need_type.rb +++ b/app/graphql/types/ci/build_need_type.rb @@ -7,6 +7,8 @@ module Types class BuildNeedType < BaseObject graphql_name 'CiBuildNeed' + field :id, GraphQL::ID_TYPE, null: false, + description: 'ID of the job we need to complete.' field :name, GraphQL::STRING_TYPE, null: true, description: 'Name of the job we need to complete.' end diff --git a/app/graphql/types/ci/detailed_status_type.rb b/app/graphql/types/ci/detailed_status_type.rb index 0b643a6b676..6310a62a103 100644 --- a/app/graphql/types/ci/detailed_status_type.rb +++ b/app/graphql/types/ci/detailed_status_type.rb @@ -6,6 +6,9 @@ module Types class DetailedStatusType < BaseObject graphql_name 'DetailedStatus' + field :id, GraphQL::STRING_TYPE, null: false, + description: 'ID for a detailed status.', + extras: [:parent] field :group, GraphQL::STRING_TYPE, null: true, description: 'Group of the status.' field :icon, GraphQL::STRING_TYPE, null: true, @@ -29,6 +32,10 @@ module Types calls_gitaly: true, description: 'Action information for the status. This includes method, button title, icon, path, and title.' + def id(parent:) + "#{object.id}-#{parent.object.object.id}" + end + def action if object.has_action? { diff --git a/app/graphql/types/ci/group_type.rb b/app/graphql/types/ci/group_type.rb index d6d4252e8d7..3da183cb842 100644 --- a/app/graphql/types/ci/group_type.rb +++ b/app/graphql/types/ci/group_type.rb @@ -6,12 +6,14 @@ module Types class GroupType < BaseObject graphql_name 'CiGroup' + field :id, GraphQL::STRING_TYPE, null: false, + description: 'ID for a group.' field :name, GraphQL::STRING_TYPE, null: true, - description: 'Name of the job group.' + description: 'Name of the job group.' field :size, GraphQL::INT_TYPE, null: true, - description: 'Size of the group.' + description: 'Size of the group.' field :jobs, Ci::JobType.connection_type, null: true, - description: 'Jobs in group.' + description: 'Jobs in group.' field :detailed_status, Types::Ci::DetailedStatusType, null: true, description: 'Detailed status of the group.' diff --git a/app/graphql/types/ci/job_token_scope_type.rb b/app/graphql/types/ci/job_token_scope_type.rb new file mode 100644 index 00000000000..9f48298e1d3 --- /dev/null +++ b/app/graphql/types/ci/job_token_scope_type.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Types + # rubocop: disable Graphql/AuthorizeTypes + # Authorization is in the resolver based on the parent project + module Ci + class JobTokenScopeType < BaseObject + graphql_name 'CiJobTokenScopeType' + + field :projects, Types::ProjectType.connection_type, null: false, + description: 'Allow list of projects that can be accessed by CI Job tokens created by this project.', + method: :all_projects + end + end + # rubocop: enable Graphql/AuthorizeTypes +end diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb index 5ed4d823aee..360ea3ba7a9 100644 --- a/app/graphql/types/ci/job_type.rb +++ b/app/graphql/types/ci/job_type.rb @@ -56,7 +56,7 @@ module Types field :short_sha, type: GraphQL::STRING_TYPE, null: false, description: 'Short SHA1 ID of the commit.' field :scheduling_type, GraphQL::STRING_TYPE, null: true, - description: 'Type of pipeline scheduling. Value is `dag` if the pipeline uses the `needs` keyword, and `stage` otherwise.' + description: 'Type of job scheduling. Value is `dag` if the job uses the `needs` keyword, and `stage` otherwise.' field :commit_path, GraphQL::STRING_TYPE, null: true, description: 'Path to the commit that triggered the job.' field :ref_name, GraphQL::STRING_TYPE, null: true, diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb index 2eeddaca6ba..f4a6c18f73e 100644 --- a/app/graphql/types/ci/pipeline_type.rb +++ b/app/graphql/types/ci/pipeline_type.rb @@ -150,6 +150,9 @@ module Types description: 'A specific test suite in a pipeline test report.', resolver: Resolvers::Ci::TestSuiteResolver + field :ref, GraphQL::STRING_TYPE, null: true, + description: 'Reference to the branch from which the pipeline was triggered.' + def detailed_status object.detailed_status(current_user) end diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb index 837d91ef765..9c5041b0860 100644 --- a/app/graphql/types/ci/runner_type.rb +++ b/app/graphql/types/ci/runner_type.rb @@ -6,6 +6,10 @@ module Types graphql_name 'CiRunner' authorize :read_runner + JOB_COUNT_LIMIT = 1000 + + alias_method :runner, :object + field :id, ::Types::GlobalIDType[::Ci::Runner], null: false, description: 'ID of the runner.' field :description, GraphQL::STRING_TYPE, null: true, @@ -21,22 +25,48 @@ module Types description: 'Indicates the runner is allowed to receive jobs.' field :status, ::Types::Ci::RunnerStatusEnum, null: false, description: 'Status of the runner.' - field :version, GraphQL::STRING_TYPE, null: false, + field :version, GraphQL::STRING_TYPE, null: true, description: 'Version of the runner.' field :short_sha, GraphQL::STRING_TYPE, null: true, description: %q(First eight characters of the runner's token used to authenticate new job requests. Used as the runner's unique ID.) - field :revision, GraphQL::STRING_TYPE, null: false, + field :revision, GraphQL::STRING_TYPE, null: true, description: 'Revision of the runner.' field :locked, GraphQL::BOOLEAN_TYPE, null: true, description: 'Indicates the runner is locked.' field :run_untagged, GraphQL::BOOLEAN_TYPE, null: false, description: 'Indicates the runner is able to run untagged jobs.' - field :ip_address, GraphQL::STRING_TYPE, null: false, + field :ip_address, GraphQL::STRING_TYPE, null: true, description: 'IP address of the runner.' field :runner_type, ::Types::Ci::RunnerTypeEnum, null: false, description: 'Type of the runner.' field :tag_list, [GraphQL::STRING_TYPE], null: true, description: 'Tags associated with the runner.' + field :project_count, GraphQL::INT_TYPE, null: true, + description: 'Number of projects that the runner is associated with.' + field :job_count, GraphQL::INT_TYPE, null: true, + description: "Number of jobs processed by the runner (limited to #{JOB_COUNT_LIMIT}, plus one to indicate that more items exist)." + + def job_count + # We limit to 1 above the JOB_COUNT_LIMIT to indicate that more items exist after JOB_COUNT_LIMIT + runner.builds.limit(JOB_COUNT_LIMIT + 1).count + end + + # rubocop: disable CodeReuse/ActiveRecord + def project_count + BatchLoader::GraphQL.for(runner.id).batch(key: :runner_project_count) do |ids, loader, args| + counts = ::Ci::Runner.project_type + .select(:id, 'COUNT(ci_runner_projects.id) as count') + .left_outer_joins(:runner_projects) + .where(id: ids) + .group(:id) + .index_by(&:id) + + ids.each do |id| + loader.call(id, counts[id]&.count) + end + end + end + # rubocop: enable CodeReuse/ActiveRecord end end end diff --git a/app/graphql/types/ci/stage_type.rb b/app/graphql/types/ci/stage_type.rb index 1be9e3192a9..ce3edb6c54f 100644 --- a/app/graphql/types/ci/stage_type.rb +++ b/app/graphql/types/ci/stage_type.rb @@ -6,22 +6,21 @@ module Types graphql_name 'CiStage' authorize :read_commit_status - field :name, - type: GraphQL::STRING_TYPE, - null: true, + field :id, GraphQL::ID_TYPE, null: false, + description: 'ID of the stage.' + field :name, type: GraphQL::STRING_TYPE, null: true, description: 'Name of the stage.' - field :groups, - type: Ci::GroupType.connection_type, - null: true, + field :groups, type: Ci::GroupType.connection_type, null: true, extras: [:lookahead], description: 'Group of jobs for the stage.' - field :detailed_status, Types::Ci::DetailedStatusType, - null: true, + field :detailed_status, Types::Ci::DetailedStatusType, null: true, description: 'Detailed status of the stage.' - field :jobs, Ci::JobType.connection_type, - null: true, + field :jobs, Ci::JobType.connection_type, null: true, description: 'Jobs for the stage.', method: 'latest_statuses' + field :status, GraphQL::STRING_TYPE, + null: true, + description: 'Status of the pipeline stage.' def detailed_status object.detailed_status(current_user) @@ -54,6 +53,7 @@ module Types # rubocop: disable CodeReuse/ActiveRecord def jobs_for_pipeline(pipeline, stage_ids, include_needs) results = pipeline.latest_statuses.where(stage_id: stage_ids) + results = results.preload(:project) results = results.preload(:needs) if include_needs results.group_by(&:stage_id) diff --git a/app/graphql/types/ci/status_action_type.rb b/app/graphql/types/ci/status_action_type.rb index 9f7299c0270..a06b09735b3 100644 --- a/app/graphql/types/ci/status_action_type.rb +++ b/app/graphql/types/ci/status_action_type.rb @@ -5,6 +5,9 @@ module Types class StatusActionType < BaseObject graphql_name 'StatusAction' + field :id, GraphQL::STRING_TYPE, null: false, + description: 'ID for a status action.', + extras: [:parent] field :button_title, GraphQL::STRING_TYPE, null: true, description: 'Title for the button, for example: Retry this job.' field :icon, GraphQL::STRING_TYPE, null: true, @@ -17,6 +20,10 @@ module Types field :title, GraphQL::STRING_TYPE, null: true, description: 'Title for the action, for example: Retry.' + def id(parent:) + "#{parent.parent.object.object.class.name}-#{parent.object.object.id}" + end + def action_method object[:method] end diff --git a/app/graphql/types/deployment_tier_enum.rb b/app/graphql/types/deployment_tier_enum.rb new file mode 100644 index 00000000000..7079b98c512 --- /dev/null +++ b/app/graphql/types/deployment_tier_enum.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Types + class DeploymentTierEnum < BaseEnum + graphql_name 'DeploymentTier' + description 'All environment deployment tiers.' + + value 'PRODUCTION', description: 'Production.', value: :production + value 'STAGING', description: 'Staging.', value: :staging + value 'TESTING', description: 'Testing.', value: :testing + value 'DEVELOPMENT', description: 'Development.', value: :development + value 'OTHER', description: 'Other.', value: :other + end +end diff --git a/app/graphql/types/design_management/design_type.rb b/app/graphql/types/design_management/design_type.rb index 44e87905f92..2f40bf5ebfd 100644 --- a/app/graphql/types/design_management/design_type.rb +++ b/app/graphql/types/design_management/design_type.rb @@ -10,7 +10,7 @@ module Types alias_method :design, :object - implements(Types::Notes::NoteableType) + implements(Types::Notes::NoteableInterface) implements(Types::DesignManagement::DesignFields) implements(Types::CurrentUserTodos) diff --git a/app/graphql/types/issuable_searchable_field_enum.rb b/app/graphql/types/issuable_searchable_field_enum.rb new file mode 100644 index 00000000000..88a49504f28 --- /dev/null +++ b/app/graphql/types/issuable_searchable_field_enum.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Types + class IssuableSearchableFieldEnum < BaseEnum + graphql_name 'IssuableSearchableField' + description 'Fields to perform the search in' + + Issuable::SEARCHABLE_FIELDS.each do |field| + value field.upcase, value: field, description: "Search in #{field} field." + end + end +end diff --git a/app/graphql/types/issue_sort_enum.rb b/app/graphql/types/issue_sort_enum.rb index e730a51b60e..a2390ff01fe 100644 --- a/app/graphql/types/issue_sort_enum.rb +++ b/app/graphql/types/issue_sort_enum.rb @@ -10,6 +10,8 @@ module Types value 'RELATIVE_POSITION_ASC', 'Relative position by ascending order.', value: :relative_position_asc value 'SEVERITY_ASC', 'Severity from less critical to more critical.', value: :severity_asc value 'SEVERITY_DESC', 'Severity from more critical to less critical.', value: :severity_desc + value 'POPULARITY_ASC', 'Number of upvotes (awarded "thumbs up" emoji) by ascending order.', value: :popularity_asc + value 'POPULARITY_DESC', 'Number of upvotes (awarded "thumbs up" emoji) by descending order.', value: :popularity_desc end end diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index 0ccd1e2cebd..6ff38273c03 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -6,7 +6,7 @@ module Types connection_type_class(Types::IssueConnectionType) - implements(Types::Notes::NoteableType) + implements(Types::Notes::NoteableInterface) implements(Types::CurrentUserTodos) authorize :read_issue @@ -127,6 +127,9 @@ module Types field :timelogs, Types::TimelogType.connection_type, null: false, description: 'Timelogs on the issue.' + field :project_id, GraphQL::INT_TYPE, null: false, method: :project_id, + description: 'ID of the issue project.' + def author Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find end diff --git a/app/graphql/types/issues/negated_issue_filter_input_type.rb b/app/graphql/types/issues/negated_issue_filter_input_type.rb index 8a2e75ed9ba..88faf7e7074 100644 --- a/app/graphql/types/issues/negated_issue_filter_input_type.rb +++ b/app/graphql/types/issues/negated_issue_filter_input_type.rb @@ -7,7 +7,7 @@ module Types argument :iids, [GraphQL::STRING_TYPE], required: false, - description: 'List of IIDs of issues to exclude. For example, [1, 2].' + description: 'List of IIDs of issues to exclude. For example, `[1, 2]`.' argument :label_name, [GraphQL::STRING_TYPE], required: false, description: 'Labels not applied to this issue.' diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index 338b70bb0c6..0e9df926cdd 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -6,7 +6,7 @@ module Types connection_type_class(Types::MergeRequestConnectionType) - implements(Types::Notes::NoteableType) + implements(Types::Notes::NoteableInterface) implements(Types::CurrentUserTodos) authorize :read_merge_request diff --git a/app/graphql/types/milestone_sort_enum.rb b/app/graphql/types/milestone_sort_enum.rb new file mode 100644 index 00000000000..9f7dedb4c4c --- /dev/null +++ b/app/graphql/types/milestone_sort_enum.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Types + class MilestoneSortEnum < SortEnum + graphql_name 'MilestoneSort' + description 'Values for sorting milestones' + + value 'DUE_DATE_ASC', 'Milestone due date by ascending order.', value: :due_date_asc + value 'DUE_DATE_DESC', 'Milestone due date by descending order.', value: :due_date_desc + value 'EXPIRED_LAST_DUE_DATE_ASC', 'Group milestones in this order: non-expired milestones with due dates, non-expired milestones without due dates and expired milestones then sort by due date in ascending order.', value: :expired_last_due_date_asc + value 'EXPIRED_LAST_DUE_DATE_DESC', 'Group milestones in this order: non-expired milestones with due dates, non-expired milestones without due dates and expired milestones then sort by due date in descending order.', value: :expired_last_due_date_desc + end +end diff --git a/app/graphql/types/milestone_type.rb b/app/graphql/types/milestone_type.rb index eafede26c9e..27bc77b4da1 100644 --- a/app/graphql/types/milestone_type.rb +++ b/app/graphql/types/milestone_type.rb @@ -26,6 +26,9 @@ module Types field :state, Types::MilestoneStateEnum, null: false, description: 'State of the milestone.' + field :expired, GraphQL::BOOLEAN_TYPE, null: false, + description: 'Expired state of the milestone (a milestone is expired when the due date is past the current date). Defaults to `false` when due date has not been set.' + field :web_path, GraphQL::STRING_TYPE, null: false, method: :milestone_path, description: 'Web path of the milestone.' diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 6d3327f9735..df693fafbb9 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -99,11 +99,14 @@ module Types mount_mutation Mutations::Ci::CiCdSettingsUpdate mount_mutation Mutations::Ci::Job::Play mount_mutation Mutations::Ci::Job::Retry + mount_mutation Mutations::Ci::JobTokenScope::AddProject + mount_mutation Mutations::Ci::JobTokenScope::RemoveProject mount_mutation Mutations::Ci::Runner::Update, feature_flag: :runner_graphql_query mount_mutation Mutations::Ci::Runner::Delete, feature_flag: :runner_graphql_query mount_mutation Mutations::Ci::RunnersRegistrationToken::Reset, feature_flag: :runner_graphql_query mount_mutation Mutations::Namespace::PackageSettings::Update mount_mutation Mutations::UserCallouts::Create + mount_mutation Mutations::Packages::Destroy mount_mutation Mutations::Echo end end diff --git a/app/graphql/types/noteable_type.rb b/app/graphql/types/noteable_type.rb new file mode 100644 index 00000000000..859de86d6b8 --- /dev/null +++ b/app/graphql/types/noteable_type.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Types + class NoteableType < BaseUnion + graphql_name 'NoteableType' + description 'Represents an object that supports notes.' + + possible_types Types::IssueType, Types::DesignManagement::DesignType, Types::MergeRequestType + + def self.resolve_type(object, context) + case object + when Issue + Types::IssueType + when ::DesignManagement::Design + Types::DesignManagement::DesignType + when MergeRequest + Types::MergeRequestType + else + raise 'Unsupported issuable type' + end + end + end +end diff --git a/app/graphql/types/notes/discussion_type.rb b/app/graphql/types/notes/discussion_type.rb index 17cb4debd63..56579c357a7 100644 --- a/app/graphql/types/notes/discussion_type.rb +++ b/app/graphql/types/notes/discussion_type.rb @@ -19,6 +19,8 @@ module Types description: "Timestamp of the discussion's creation." field :notes, Types::Notes::NoteType.connection_type, null: false, description: 'All notes in the discussion.' + field :noteable, Types::NoteableType, null: true, + description: 'Object which the discussion belongs to.' # DiscussionID.coerce_result is suitable here, but will always mark this # as being a 'Discussion'. Using `GlobalId.build` guarantees that we get @@ -26,6 +28,14 @@ module Types def reply_id ::Gitlab::GlobalId.build(object, id: object.reply_id) end + + def noteable + noteable = object.noteable + + return unless Ability.allowed?(context[:current_user], :"read_#{noteable.to_ability_name}", noteable) + + noteable + end end end end diff --git a/app/graphql/types/notes/noteable_type.rb b/app/graphql/types/notes/noteable_interface.rb index a82a76f9c87..bd22f12d6f0 100644 --- a/app/graphql/types/notes/noteable_type.rb +++ b/app/graphql/types/notes/noteable_interface.rb @@ -2,7 +2,7 @@ module Types module Notes - module NoteableType + module NoteableInterface include Types::BaseInterface field :notes, Types::Notes::NoteType.connection_type, null: false, description: "All notes on this noteable." @@ -28,4 +28,4 @@ module Types end end -Types::Notes::NoteableType.prepend_mod_with('Types::Notes::NoteableType') +Types::Notes::NoteableInterface.prepend_mod_with('Types::Notes::NoteableInterface') diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 55dc73d898d..968635f9e6e 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -59,8 +59,6 @@ module Types field :visibility, GraphQL::STRING_TYPE, null: true, description: 'Visibility of the project.' - field :container_registry_enabled, GraphQL::BOOLEAN_TYPE, null: true, - description: 'Indicates if the project stores Docker container images in a container registry.' field :shared_runners_enabled, GraphQL::BOOLEAN_TYPE, null: true, description: 'Indicates if shared runners are enabled for the project.' field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, @@ -77,9 +75,15 @@ module Types field :avatar_url, GraphQL::STRING_TYPE, null: true, calls_gitaly: true, description: 'URL to avatar image file of the project.' - %i[issues merge_requests wiki snippets].each do |feature| + { + issues: "Issues are", + merge_requests: "Merge Requests are", + wiki: 'Wikis are', + snippets: 'Snippets are', + container_registry: 'Container Registry is' + }.each do |feature, name_string| field "#{feature}_enabled", GraphQL::BOOLEAN_TYPE, null: true, - description: "Indicates if #{feature.to_s.titleize.pluralize} are enabled for the current user" + description: "Indicates if #{name_string} enabled for the current user" define_method "#{feature}_enabled" do object.feature_available?(feature, context[:current_user]) @@ -346,6 +350,10 @@ module Types description: 'Find a single CI/CD template by name.', resolver: Resolvers::Ci::TemplateResolver + field :ci_job_token_scope, Types::Ci::JobTokenScopeType, null: true, + description: 'The CI Job Tokens scope of access.', + resolver: Resolvers::Ci::JobTokenScopeResolver + def label(title:) BatchLoader::GraphQL.for(title).batch(key: project) do |titles, loader, args| LabelsFinder diff --git a/app/graphql/types/projects/service_type_enum.rb b/app/graphql/types/projects/service_type_enum.rb index 9948fa8bb69..027026dc16c 100644 --- a/app/graphql/types/projects/service_type_enum.rb +++ b/app/graphql/types/projects/service_type_enum.rb @@ -5,7 +5,7 @@ module Types class ServiceTypeEnum < BaseEnum graphql_name 'ServiceType' - ::Integration.available_services_types(include_dev: false).each do |type| + ::Integration.available_integration_types(include_dev: false).each do |type| value type.underscore.upcase, value: type, description: "#{type} type" end end diff --git a/app/graphql/types/query_complexity_type.rb b/app/graphql/types/query_complexity_type.rb new file mode 100644 index 00000000000..82809fac22f --- /dev/null +++ b/app/graphql/types/query_complexity_type.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Types + # rubocop: disable Graphql/AuthorizeTypes + class QueryComplexityType < ::Types::BaseObject + ANALYZER = GraphQL::Analysis::QueryComplexity.new { |_query, complexity| complexity } + + graphql_name 'QueryComplexity' + + alias_method :query, :object + + field :limit, GraphQL::INT_TYPE, + null: true, + method: :max_complexity, + see: { + 'GitLab documentation on this limit' => + 'https://docs.gitlab.com/ee/api/graphql/index.html#max-query-complexity' + }, + description: 'GraphQL query complexity limit.' + + field :score, GraphQL::INT_TYPE, + null: true, + description: 'GraphQL query complexity score.' + + def score + ::GraphQL::Analysis.analyze_query(query, [ANALYZER]).first + end + end + # rubocop: enable Graphql/AuthorizeTypes +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 8b7b9f0107b..d2c67aea95c 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -36,6 +36,10 @@ module Types resolver: Resolvers::MetadataResolver, description: 'Metadata about GitLab.' + field :query_complexity, Types::QueryComplexityType, + null: true, + description: 'Information about the complexity of the GraphQL query.' + field :snippets, Types::SnippetType.connection_type, null: true, @@ -170,6 +174,10 @@ module Types def application_settings Gitlab::CurrentSettings.current_application_settings end + + def query_complexity + context.query + end end end diff --git a/app/graphql/types/release_asset_link_type.rb b/app/graphql/types/release_asset_link_type.rb index c27e1cf19b3..829e7e246db 100644 --- a/app/graphql/types/release_asset_link_type.rb +++ b/app/graphql/types/release_asset_link_type.rb @@ -20,6 +20,8 @@ module Types field :direct_asset_url, GraphQL::STRING_TYPE, null: true, description: 'Direct asset URL of the link.' + field :direct_asset_path, GraphQL::STRING_TYPE, null: true, method: :filepath, + description: 'Relative path for the direct asset link.' def direct_asset_url return object.url unless object.filepath diff --git a/app/graphql/types/snippet_type.rb b/app/graphql/types/snippet_type.rb index 34357ead048..7606bdbc46d 100644 --- a/app/graphql/types/snippet_type.rb +++ b/app/graphql/types/snippet_type.rb @@ -5,7 +5,7 @@ module Types graphql_name 'Snippet' description 'Represents a snippet entry' - implements(Types::Notes::NoteableType) + implements(Types::Notes::NoteableInterface) present_using SnippetPresenter diff --git a/app/graphql/types/snippets/blob_type.rb b/app/graphql/types/snippets/blob_type.rb index fb9ee380705..1335838935e 100644 --- a/app/graphql/types/snippets/blob_type.rb +++ b/app/graphql/types/snippets/blob_type.rb @@ -16,6 +16,10 @@ module Types description: 'Blob plain highlighted data.', null: true + field :raw_plain_data, GraphQL::STRING_TYPE, + description: 'The raw content of the blob, if the blob is text data.', + null: true + field :raw_path, GraphQL::STRING_TYPE, description: 'Blob raw content endpoint path.', null: false diff --git a/app/graphql/types/user_callout_type.rb b/app/graphql/types/user_callout_type.rb index 12f4fdea878..0ff32d68400 100644 --- a/app/graphql/types/user_callout_type.rb +++ b/app/graphql/types/user_callout_type.rb @@ -4,7 +4,7 @@ module Types class UserCalloutType < BaseObject # rubocop:disable Graphql/AuthorizeTypes graphql_name 'UserCallout' - field :feature_name, UserCalloutFeatureNameEnum, null: false, + field :feature_name, UserCalloutFeatureNameEnum, null: true, description: 'Name of the feature that the callout is for.' field :dismissed_at, Types::TimeType, null: true, description: 'Date when the callout was dismissed.' |