summaryrefslogtreecommitdiff
path: root/app/graphql
diff options
context:
space:
mode:
Diffstat (limited to 'app/graphql')
-rw-r--r--app/graphql/gitlab_schema.rb9
-rw-r--r--app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb6
-rw-r--r--app/graphql/mutations/alert_management/base.rb8
-rw-r--r--app/graphql/mutations/alert_management/http_integration/create.rb41
-rw-r--r--app/graphql/mutations/alert_management/http_integration/destroy.rb24
-rw-r--r--app/graphql/mutations/alert_management/http_integration/http_integration_base.rb29
-rw-r--r--app/graphql/mutations/alert_management/http_integration/reset_token.rb25
-rw-r--r--app/graphql/mutations/alert_management/http_integration/update.rb33
-rw-r--r--app/graphql/mutations/alert_management/prometheus_integration/create.rb50
-rw-r--r--app/graphql/mutations/alert_management/prometheus_integration/prometheus_integration_base.rb42
-rw-r--r--app/graphql/mutations/alert_management/prometheus_integration/reset_token.rb27
-rw-r--r--app/graphql/mutations/alert_management/prometheus_integration/update.rb35
-rw-r--r--app/graphql/mutations/alert_management/update_alert_status.rb6
-rw-r--r--app/graphql/mutations/boards/create.rb37
-rw-r--r--app/graphql/mutations/boards/lists/update.rb2
-rw-r--r--app/graphql/mutations/commits/create.rb10
-rw-r--r--app/graphql/mutations/concerns/mutations/package_eventable.rb14
-rw-r--r--app/graphql/mutations/concerns/mutations/resolves_resource_parent.rb43
-rw-r--r--app/graphql/mutations/container_repositories/destroy.rb45
-rw-r--r--app/graphql/mutations/custom_emoji/create.rb50
-rw-r--r--app/graphql/mutations/labels/create.rb43
-rw-r--r--app/graphql/mutations/merge_requests/set_labels.rb15
-rw-r--r--app/graphql/mutations/metrics/dashboard/annotations/delete.rb3
-rw-r--r--app/graphql/mutations/notes/reposition_image_diff_note.rb65
-rw-r--r--app/graphql/mutations/notes/update/image_diff_note.rb5
-rw-r--r--app/graphql/mutations/releases/base.rb19
-rw-r--r--app/graphql/mutations/releases/create.rb68
-rw-r--r--app/graphql/mutations/snippets/destroy.rb3
-rw-r--r--app/graphql/mutations/snippets/mark_as_spam.rb3
-rw-r--r--app/graphql/mutations/snippets/update.rb3
-rw-r--r--app/graphql/mutations/terraform/state/base.rb22
-rw-r--r--app/graphql/mutations/terraform/state/delete.rb18
-rw-r--r--app/graphql/mutations/terraform/state/lock.rb29
-rw-r--r--app/graphql/mutations/terraform/state/unlock.rb18
-rw-r--r--app/graphql/mutations/todos/base.rb10
-rw-r--r--app/graphql/mutations/todos/create.rb39
-rw-r--r--app/graphql/mutations/todos/mark_all_done.rb4
-rw-r--r--app/graphql/mutations/todos/restore_many.rb8
-rw-r--r--app/graphql/queries/design_management/design_permissions.query.graphql13
-rw-r--r--app/graphql/queries/design_management/get_design_list.query.graphql40
-rw-r--r--app/graphql/queries/repository/files.query.graphql76
-rw-r--r--app/graphql/queries/repository/permissions.query.graphql11
-rw-r--r--app/graphql/queries/snippet/project_permissions.query.graphql9
-rw-r--r--app/graphql/queries/snippet/snippet.query.graphql65
-rw-r--r--app/graphql/queries/snippet/snippet_blob_content.query.graphql18
-rw-r--r--app/graphql/queries/snippet/user_permissions.query.graphql9
-rw-r--r--app/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver.rb12
-rw-r--r--app/graphql/resolvers/alert_management/alert_resolver.rb2
-rw-r--r--app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb2
-rw-r--r--app/graphql/resolvers/alert_management/integrations_resolver.rb29
-rw-r--r--app/graphql/resolvers/assigned_merge_requests_resolver.rb3
-rw-r--r--app/graphql/resolvers/authored_merge_requests_resolver.rb3
-rw-r--r--app/graphql/resolvers/base_resolver.rb86
-rw-r--r--app/graphql/resolvers/board_lists_resolver.rb2
-rw-r--r--app/graphql/resolvers/boards_resolver.rb11
-rw-r--r--app/graphql/resolvers/ci/jobs_resolver.rb24
-rw-r--r--app/graphql/resolvers/ci/runner_setup_resolver.rb64
-rw-r--r--app/graphql/resolvers/commit_pipelines_resolver.rb3
-rw-r--r--app/graphql/resolvers/concerns/caching_array_resolver.rb128
-rw-r--r--app/graphql/resolvers/concerns/issue_resolver_arguments.rb2
-rw-r--r--app/graphql/resolvers/concerns/looks_ahead.rb1
-rw-r--r--app/graphql/resolvers/concerns/resolves_pipelines.rb2
-rw-r--r--app/graphql/resolvers/concerns/resolves_project.rb3
-rw-r--r--app/graphql/resolvers/concerns/resolves_snippets.rb21
-rw-r--r--app/graphql/resolvers/container_repositories_resolver.rb19
-rw-r--r--app/graphql/resolvers/design_management/design_at_version_resolver.rb9
-rw-r--r--app/graphql/resolvers/design_management/design_resolver.rb10
-rw-r--r--app/graphql/resolvers/design_management/designs_resolver.rb29
-rw-r--r--app/graphql/resolvers/design_management/version/design_at_version_resolver.rb22
-rw-r--r--app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb18
-rw-r--r--app/graphql/resolvers/design_management/version_in_collection_resolver.rb15
-rw-r--r--app/graphql/resolvers/design_management/version_resolver.rb8
-rw-r--r--app/graphql/resolvers/design_management/versions_resolver.rb20
-rw-r--r--app/graphql/resolvers/echo_resolver.rb7
-rw-r--r--app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb13
-rw-r--r--app/graphql/resolvers/error_tracking/sentry_error_collection_resolver.rb2
-rw-r--r--app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb10
-rw-r--r--app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb2
-rw-r--r--app/graphql/resolvers/group_issues_resolver.rb1
-rw-r--r--app/graphql/resolvers/group_members_resolver.rb2
-rw-r--r--app/graphql/resolvers/group_merge_requests_resolver.rb2
-rw-r--r--app/graphql/resolvers/group_milestones_resolver.rb3
-rw-r--r--app/graphql/resolvers/issues_resolver.rb2
-rw-r--r--app/graphql/resolvers/members_resolver.rb2
-rw-r--r--app/graphql/resolvers/merge_request_pipelines_resolver.rb3
-rw-r--r--app/graphql/resolvers/merge_request_resolver.rb2
-rw-r--r--app/graphql/resolvers/metadata_resolver.rb2
-rw-r--r--app/graphql/resolvers/milestones_resolver.rb2
-rw-r--r--app/graphql/resolvers/namespace_projects_resolver.rb9
-rw-r--r--app/graphql/resolvers/project_members_resolver.rb3
-rw-r--r--app/graphql/resolvers/project_merge_requests_resolver.rb1
-rw-r--r--app/graphql/resolvers/project_milestones_resolver.rb3
-rw-r--r--app/graphql/resolvers/project_pipeline_resolver.rb2
-rw-r--r--app/graphql/resolvers/project_pipelines_resolver.rb19
-rw-r--r--app/graphql/resolvers/projects/jira_imports_resolver.rb2
-rw-r--r--app/graphql/resolvers/projects/jira_projects_resolver.rb2
-rw-r--r--app/graphql/resolvers/projects/services_resolver.rb2
-rw-r--r--app/graphql/resolvers/projects/snippets_resolver.rb1
-rw-r--r--app/graphql/resolvers/releases_resolver.rb16
-rw-r--r--app/graphql/resolvers/snippets/blobs_resolver.rb2
-rw-r--r--app/graphql/resolvers/snippets_resolver.rb11
-rw-r--r--app/graphql/resolvers/todo_resolver.rb2
-rw-r--r--app/graphql/resolvers/tree_resolver.rb2
-rw-r--r--app/graphql/resolvers/user_merge_requests_resolver_base.rb (renamed from app/graphql/resolvers/user_merge_requests_resolver.rb)10
-rw-r--r--app/graphql/resolvers/user_resolver.rb2
-rw-r--r--app/graphql/resolvers/users/group_count_resolver.rb25
-rw-r--r--app/graphql/resolvers/users/snippets_resolver.rb1
-rw-r--r--app/graphql/resolvers/users_resolver.rb12
-rw-r--r--app/graphql/types/alert_management/http_integration_type.rb22
-rw-r--r--app/graphql/types/alert_management/integration_type.rb58
-rw-r--r--app/graphql/types/alert_management/integration_type_enum.rb13
-rw-r--r--app/graphql/types/alert_management/prometheus_integration_type.rb38
-rw-r--r--app/graphql/types/availability_enum.rb12
-rw-r--r--app/graphql/types/base_object.rb14
-rw-r--r--app/graphql/types/board_type.rb2
-rw-r--r--app/graphql/types/ci/detailed_status_type.rb2
-rw-r--r--app/graphql/types/ci/job_type.rb3
-rw-r--r--app/graphql/types/ci/pipeline_type.rb40
-rw-r--r--app/graphql/types/ci/runner_setup_type.rb15
-rw-r--r--app/graphql/types/commit_type.rb9
-rw-r--r--app/graphql/types/container_repository_cleanup_status_enum.rb13
-rw-r--r--app/graphql/types/container_repository_details_type.rb21
-rw-r--r--app/graphql/types/container_repository_status_enum.rb12
-rw-r--r--app/graphql/types/container_repository_tag_type.rb25
-rw-r--r--app/graphql/types/container_repository_type.rb27
-rw-r--r--app/graphql/types/countable_connection_type.rb2
-rw-r--r--app/graphql/types/custom_emoji_type.rb27
-rw-r--r--app/graphql/types/environment_type.rb5
-rw-r--r--app/graphql/types/global_id_type.rb2
-rw-r--r--app/graphql/types/grafana_integration_type.rb8
-rw-r--r--app/graphql/types/group_invitation_type.rb17
-rw-r--r--app/graphql/types/group_type.rb13
-rw-r--r--app/graphql/types/invitation_interface.rb41
-rw-r--r--app/graphql/types/issue_connection_type.rb9
-rw-r--r--app/graphql/types/issue_type.rb53
-rw-r--r--app/graphql/types/merge_request_type.rb26
-rw-r--r--app/graphql/types/mutation_type.rb16
-rw-r--r--app/graphql/types/notes/update_diff_image_position_input_type.rb8
-rw-r--r--app/graphql/types/permission_types/custom_emoji.rb11
-rw-r--r--app/graphql/types/permission_types/note.rb2
-rw-r--r--app/graphql/types/project_invitation_type.rb21
-rw-r--r--app/graphql/types/project_statistics_type.rb16
-rw-r--r--app/graphql/types/project_type.rb15
-rw-r--r--app/graphql/types/projects/namespace_project_sort_enum.rb1
-rw-r--r--app/graphql/types/projects/service_type_enum.rb2
-rw-r--r--app/graphql/types/query_type.rb23
-rw-r--r--app/graphql/types/release_asset_link_input_type.rb25
-rw-r--r--app/graphql/types/release_asset_link_type.rb6
-rw-r--r--app/graphql/types/release_asset_link_type_enum.rb2
-rw-r--r--app/graphql/types/release_assets_input_type.rb13
-rw-r--r--app/graphql/types/release_links_type.rb14
-rw-r--r--app/graphql/types/release_sort_enum.rb18
-rw-r--r--app/graphql/types/root_storage_statistics_type.rb1
-rw-r--r--app/graphql/types/security/report_type_enum.rb15
-rw-r--r--app/graphql/types/snippet_type.rb2
-rw-r--r--app/graphql/types/terraform/state_type.rb7
-rw-r--r--app/graphql/types/terraform/state_version_type.rb35
-rw-r--r--app/graphql/types/user_status_type.rb2
-rw-r--r--app/graphql/types/user_type.rb8
159 files changed, 2401 insertions, 273 deletions
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index 2f5043f9ffa..d66a2333d11 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -30,6 +30,8 @@ class GitlabSchema < GraphQL::Schema
default_max_page_size 100
+ lazy_resolve ::Gitlab::Graphql::Lazy, :force
+
class << self
def multiplex(queries, **kwargs)
kwargs[:max_complexity] ||= max_query_complexity(kwargs[:context])
@@ -76,6 +78,13 @@ class GitlabSchema < GraphQL::Schema
find_by_gid(gid)
end
+ def resolve_type(type, object, ctx = :__undefined__)
+ tc = type.metadata[:type_class]
+ return if tc.respond_to?(:assignable?) && !tc.assignable?(object)
+
+ super
+ end
+
# Find an object by looking it up from its 'GlobalID'.
#
# * For `ApplicationRecord`s, this is equivalent to
diff --git a/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb b/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb
index a3a421f8938..17f9b5b5637 100644
--- a/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb
+++ b/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb
@@ -33,9 +33,9 @@ module Mutations
super
end
- def resolve(args)
+ def resolve(queue_name:, **args)
{
- result: Gitlab::SidekiqQueue.new(args[:queue_name]).drop_jobs!(args, timeout: 30),
+ result: Gitlab::SidekiqQueue.new(queue_name).drop_jobs!(args, timeout: 30),
errors: []
}
rescue Gitlab::SidekiqQueue::NoMetadataError
@@ -44,7 +44,7 @@ module Mutations
errors: ['No metadata provided']
}
rescue Gitlab::SidekiqQueue::InvalidQueueError
- raise Gitlab::Graphql::Errors::ResourceNotAvailable, "Queue #{args[:queue_name]} not found"
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable, "Queue #{queue_name} not found"
end
end
end
diff --git a/app/graphql/mutations/alert_management/base.rb b/app/graphql/mutations/alert_management/base.rb
index 0ccfcf34180..81d5ee95f06 100644
--- a/app/graphql/mutations/alert_management/base.rb
+++ b/app/graphql/mutations/alert_management/base.rb
@@ -4,7 +4,6 @@ module Mutations
module AlertManagement
class Base < BaseMutation
include Gitlab::Utils::UsageData
- include ResolvesProject
argument :project_path, GraphQL::ID_TYPE,
required: true,
@@ -33,13 +32,12 @@ module Mutations
private
- def find_object(project_path:, iid:)
- project = resolve_project(full_path: project_path)
+ def find_object(project_path:, **args)
+ project = Project.find_by_full_path(project_path)
return unless project
- resolver = Resolvers::AlertManagement::AlertResolver.single.new(object: project, context: context, field: nil)
- resolver.resolve(iid: iid)
+ ::AlertManagement::AlertsFinder.new(current_user, project, args).execute.first
end
end
end
diff --git a/app/graphql/mutations/alert_management/http_integration/create.rb b/app/graphql/mutations/alert_management/http_integration/create.rb
new file mode 100644
index 00000000000..ddb75e66bb4
--- /dev/null
+++ b/app/graphql/mutations/alert_management/http_integration/create.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AlertManagement
+ module HttpIntegration
+ class Create < HttpIntegrationBase
+ include ResolvesProject
+
+ graphql_name 'HttpIntegrationCreate'
+
+ argument :project_path, GraphQL::ID_TYPE,
+ required: true,
+ description: 'The project to create the integration in'
+
+ argument :name, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'The name of the integration'
+
+ argument :active, GraphQL::BOOLEAN_TYPE,
+ required: true,
+ description: 'Whether the integration is receiving alerts'
+
+ def resolve(args)
+ project = authorized_find!(full_path: args[:project_path])
+
+ response ::AlertManagement::HttpIntegrations::CreateService.new(
+ project,
+ current_user,
+ args.slice(:name, :active)
+ ).execute
+ end
+
+ private
+
+ def find_object(full_path:)
+ resolve_project(full_path: full_path)
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/alert_management/http_integration/destroy.rb b/app/graphql/mutations/alert_management/http_integration/destroy.rb
new file mode 100644
index 00000000000..0f478760aab
--- /dev/null
+++ b/app/graphql/mutations/alert_management/http_integration/destroy.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AlertManagement
+ module HttpIntegration
+ class Destroy < HttpIntegrationBase
+ graphql_name 'HttpIntegrationDestroy'
+
+ argument :id, Types::GlobalIDType[::AlertManagement::HttpIntegration],
+ required: true,
+ description: "The id of the integration to remove"
+
+ def resolve(id:)
+ integration = authorized_find!(id: id)
+
+ response ::AlertManagement::HttpIntegrations::DestroyService.new(
+ integration,
+ current_user
+ ).execute
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/alert_management/http_integration/http_integration_base.rb b/app/graphql/mutations/alert_management/http_integration/http_integration_base.rb
new file mode 100644
index 00000000000..d328eabf244
--- /dev/null
+++ b/app/graphql/mutations/alert_management/http_integration/http_integration_base.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AlertManagement
+ module HttpIntegration
+ class HttpIntegrationBase < BaseMutation
+ field :integration,
+ Types::AlertManagement::HttpIntegrationType,
+ null: true,
+ description: "The HTTP integration"
+
+ authorize :admin_operations
+
+ private
+
+ def find_object(id:)
+ GitlabSchema.object_from_id(id, expected_class: ::AlertManagement::HttpIntegration)
+ end
+
+ def response(result)
+ {
+ integration: result.payload[:integration],
+ errors: result.errors
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/alert_management/http_integration/reset_token.rb b/app/graphql/mutations/alert_management/http_integration/reset_token.rb
new file mode 100644
index 00000000000..eefab156825
--- /dev/null
+++ b/app/graphql/mutations/alert_management/http_integration/reset_token.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AlertManagement
+ module HttpIntegration
+ class ResetToken < HttpIntegrationBase
+ graphql_name 'HttpIntegrationResetToken'
+
+ argument :id, Types::GlobalIDType[::AlertManagement::HttpIntegration],
+ required: true,
+ description: "The id of the integration to mutate"
+
+ def resolve(id:)
+ integration = authorized_find!(id: id)
+
+ response ::AlertManagement::HttpIntegrations::UpdateService.new(
+ integration,
+ current_user,
+ regenerate_token: true
+ ).execute
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/alert_management/http_integration/update.rb b/app/graphql/mutations/alert_management/http_integration/update.rb
new file mode 100644
index 00000000000..309c45b04ac
--- /dev/null
+++ b/app/graphql/mutations/alert_management/http_integration/update.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AlertManagement
+ module HttpIntegration
+ class Update < HttpIntegrationBase
+ graphql_name 'HttpIntegrationUpdate'
+
+ argument :id, Types::GlobalIDType[::AlertManagement::HttpIntegration],
+ required: true,
+ description: "The id of the integration to mutate"
+
+ argument :name, GraphQL::STRING_TYPE,
+ required: false,
+ description: "The name of the integration"
+
+ argument :active, GraphQL::BOOLEAN_TYPE,
+ required: false,
+ description: "Whether the integration is receiving alerts"
+
+ def resolve(args)
+ integration = authorized_find!(id: args[:id])
+
+ response ::AlertManagement::HttpIntegrations::UpdateService.new(
+ integration,
+ current_user,
+ args.slice(:name, :active)
+ ).execute
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/alert_management/prometheus_integration/create.rb b/app/graphql/mutations/alert_management/prometheus_integration/create.rb
new file mode 100644
index 00000000000..935ec53795c
--- /dev/null
+++ b/app/graphql/mutations/alert_management/prometheus_integration/create.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AlertManagement
+ module PrometheusIntegration
+ class Create < PrometheusIntegrationBase
+ include ResolvesProject
+
+ graphql_name 'PrometheusIntegrationCreate'
+
+ argument :project_path, GraphQL::ID_TYPE,
+ required: true,
+ description: 'The project to create the integration in'
+
+ argument :active, GraphQL::BOOLEAN_TYPE,
+ required: true,
+ description: 'Whether the integration is receiving alerts'
+
+ argument :api_url, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Endpoint at which prometheus can be queried'
+
+ def resolve(args)
+ project = authorized_find!(full_path: args[:project_path])
+
+ return integration_exists if project.prometheus_service
+
+ result = ::Projects::Operations::UpdateService.new(
+ project,
+ current_user,
+ **integration_attributes(args),
+ **token_attributes
+ ).execute
+
+ response(project.prometheus_service, result)
+ end
+
+ private
+
+ def find_object(full_path:)
+ resolve_project(full_path: full_path)
+ end
+
+ def integration_exists
+ response(nil, message: _('Multiple Prometheus integrations are not supported'))
+ end
+ end
+ end
+ end
+end
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
new file mode 100644
index 00000000000..6b690ac239a
--- /dev/null
+++ b/app/graphql/mutations/alert_management/prometheus_integration/prometheus_integration_base.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AlertManagement
+ module PrometheusIntegration
+ class PrometheusIntegrationBase < BaseMutation
+ field :integration,
+ Types::AlertManagement::PrometheusIntegrationType,
+ null: true,
+ description: "The newly created integration"
+
+ authorize :admin_project
+
+ private
+
+ def find_object(id:)
+ GitlabSchema.object_from_id(id, expected_class: ::PrometheusService)
+ end
+
+ def response(integration, result)
+ {
+ integration: integration,
+ errors: Array(result[:message])
+ }
+ end
+
+ def integration_attributes(args)
+ {
+ prometheus_integration_attributes: {
+ manual_configuration: args[:active],
+ api_url: args[:api_url]
+ }.compact
+ }
+ end
+
+ def token_attributes
+ { alerting_setting_attributes: { regenerate_token: true } }
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/alert_management/prometheus_integration/reset_token.rb b/app/graphql/mutations/alert_management/prometheus_integration/reset_token.rb
new file mode 100644
index 00000000000..745ac51f6e3
--- /dev/null
+++ b/app/graphql/mutations/alert_management/prometheus_integration/reset_token.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AlertManagement
+ module PrometheusIntegration
+ class ResetToken < PrometheusIntegrationBase
+ graphql_name 'PrometheusIntegrationResetToken'
+
+ argument :id, Types::GlobalIDType[::PrometheusService],
+ required: true,
+ description: "The id of the integration to mutate"
+
+ def resolve(id:)
+ integration = authorized_find!(id: id)
+
+ result = ::Projects::Operations::UpdateService.new(
+ integration.project,
+ current_user,
+ token_attributes
+ ).execute
+
+ response integration, result
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/alert_management/prometheus_integration/update.rb b/app/graphql/mutations/alert_management/prometheus_integration/update.rb
new file mode 100644
index 00000000000..1f0dea119c5
--- /dev/null
+++ b/app/graphql/mutations/alert_management/prometheus_integration/update.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AlertManagement
+ module PrometheusIntegration
+ class Update < PrometheusIntegrationBase
+ graphql_name 'PrometheusIntegrationUpdate'
+
+ argument :id, Types::GlobalIDType[::PrometheusService],
+ required: true,
+ description: "The id of the integration to mutate"
+
+ argument :active, GraphQL::BOOLEAN_TYPE,
+ required: false,
+ description: "Whether the integration is receiving alerts"
+
+ argument :api_url, GraphQL::STRING_TYPE,
+ required: false,
+ description: "Endpoint at which prometheus can be queried"
+
+ def resolve(args)
+ integration = authorized_find!(id: args[:id])
+
+ result = ::Projects::Operations::UpdateService.new(
+ integration.project,
+ current_user,
+ integration_attributes(args)
+ ).execute
+
+ response integration.reset, result
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/alert_management/update_alert_status.rb b/app/graphql/mutations/alert_management/update_alert_status.rb
index 1e14bae048a..74185dca529 100644
--- a/app/graphql/mutations/alert_management/update_alert_status.rb
+++ b/app/graphql/mutations/alert_management/update_alert_status.rb
@@ -9,9 +9,9 @@ module Mutations
required: true,
description: 'The status to set the alert'
- def resolve(args)
- alert = authorized_find!(project_path: args[:project_path], iid: args[:iid])
- result = update_status(alert, args[:status])
+ def resolve(project_path:, iid:, status:)
+ alert = authorized_find!(project_path: project_path, iid: iid)
+ result = update_status(alert, status)
track_usage_event(:incident_management_alert_status_changed, current_user.id)
diff --git a/app/graphql/mutations/boards/create.rb b/app/graphql/mutations/boards/create.rb
index e381205242e..ebbd19930ec 100644
--- a/app/graphql/mutations/boards/create.rb
+++ b/app/graphql/mutations/boards/create.rb
@@ -3,8 +3,7 @@
module Mutations
module Boards
class Create < ::Mutations::BaseMutation
- include Mutations::ResolvesGroup
- include ResolvesProject
+ include Mutations::ResolvesResourceParent
graphql_name 'CreateBoard'
@@ -13,12 +12,6 @@ module Mutations
null: true,
description: 'The board after mutation.'
- argument :project_path, GraphQL::ID_TYPE,
- required: false,
- description: 'The project full path the board is associated with.'
- argument :group_path, GraphQL::ID_TYPE,
- required: false,
- description: 'The group full path the board is associated with.'
argument :name,
GraphQL::STRING_TYPE,
required: false,
@@ -28,7 +21,7 @@ module Mutations
required: false,
description: 'The ID of the user to be assigned to the board.'
argument :milestone_id,
- GraphQL::ID_TYPE,
+ Types::GlobalIDType[Milestone],
required: false,
description: 'The ID of the milestone to be assigned to the board.'
argument :weight,
@@ -36,17 +29,14 @@ module Mutations
required: false,
description: 'The weight of the board.'
argument :label_ids,
- [GraphQL::ID_TYPE],
+ [Types::GlobalIDType[Label]],
required: false,
description: 'The IDs of labels to be added to the board.'
authorize :admin_board
def resolve(args)
- group_path = args.delete(:group_path)
- project_path = args.delete(:project_path)
-
- board_parent = authorized_find!(group_path: group_path, project_path: project_path)
+ board_parent = authorized_resource_parent_find!(args)
response = ::Boards::CreateService.new(board_parent, current_user, args).execute
{
@@ -54,25 +44,6 @@ module Mutations
errors: response.errors
}
end
-
- def ready?(**args)
- if args.values_at(:project_path, :group_path).compact.blank?
- raise Gitlab::Graphql::Errors::ArgumentError,
- 'group_path or project_path arguments are required'
- end
-
- super
- end
-
- private
-
- def find_object(group_path: nil, project_path: nil)
- if group_path
- resolve_group(full_path: group_path)
- else
- resolve_project(full_path: project_path)
- end
- end
end
end
end
diff --git a/app/graphql/mutations/boards/lists/update.rb b/app/graphql/mutations/boards/lists/update.rb
index 7efed3058b3..14502b5174f 100644
--- a/app/graphql/mutations/boards/lists/update.rb
+++ b/app/graphql/mutations/boards/lists/update.rb
@@ -6,7 +6,7 @@ module Mutations
class Update < BaseMutation
graphql_name 'UpdateBoardList'
- argument :list_id, GraphQL::ID_TYPE,
+ argument :list_id, Types::GlobalIDType[List],
required: true,
loads: Types::BoardListType,
description: 'Global ID of the list.'
diff --git a/app/graphql/mutations/commits/create.rb b/app/graphql/mutations/commits/create.rb
index 9ed1bb819c8..2b9107350fd 100644
--- a/app/graphql/mutations/commits/create.rb
+++ b/app/graphql/mutations/commits/create.rb
@@ -13,7 +13,11 @@ module Mutations
argument :branch, GraphQL::STRING_TYPE,
required: true,
- description: 'Name of the branch'
+ description: 'Name of the branch to commit into, it can be a new branch'
+
+ argument :start_branch, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'If on a new branch, name of the original branch'
argument :message,
GraphQL::STRING_TYPE,
@@ -32,13 +36,13 @@ module Mutations
authorize :push_code
- def resolve(project_path:, branch:, message:, actions:)
+ def resolve(project_path:, branch:, message:, actions:, **args)
project = authorized_find!(full_path: project_path)
attributes = {
commit_message: message,
branch_name: branch,
- start_branch: branch,
+ start_branch: args[:start_branch] || branch,
actions: actions.map { |action| action.to_h }
}
diff --git a/app/graphql/mutations/concerns/mutations/package_eventable.rb b/app/graphql/mutations/concerns/mutations/package_eventable.rb
new file mode 100644
index 00000000000..86fd7b9a88a
--- /dev/null
+++ b/app/graphql/mutations/concerns/mutations/package_eventable.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Mutations
+ module PackageEventable
+ extend ActiveSupport::Concern
+
+ private
+
+ 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)
+ end
+ end
+end
diff --git a/app/graphql/mutations/concerns/mutations/resolves_resource_parent.rb b/app/graphql/mutations/concerns/mutations/resolves_resource_parent.rb
new file mode 100644
index 00000000000..04a9abf9529
--- /dev/null
+++ b/app/graphql/mutations/concerns/mutations/resolves_resource_parent.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Mutations
+ module ResolvesResourceParent
+ extend ActiveSupport::Concern
+ include Mutations::ResolvesGroup
+ include ResolvesProject
+
+ included do
+ argument :project_path, GraphQL::ID_TYPE,
+ required: false,
+ description: 'The project full path the resource is associated with'
+
+ argument :group_path, GraphQL::ID_TYPE,
+ required: false,
+ description: 'The group full path the resource is associated with'
+ end
+
+ def ready?(**args)
+ unless args[:project_path].present? ^ args[:group_path].present?
+ raise Gitlab::Graphql::Errors::ArgumentError,
+ 'Exactly one of group_path or project_path arguments is required'
+ end
+
+ super
+ end
+
+ private
+
+ def authorized_resource_parent_find!(args)
+ authorized_find!(project_path: args.delete(:project_path),
+ group_path: args.delete(:group_path))
+ end
+
+ def find_object(project_path: nil, group_path: nil)
+ if group_path.present?
+ resolve_group(full_path: group_path)
+ else
+ resolve_project(full_path: project_path)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/container_repositories/destroy.rb b/app/graphql/mutations/container_repositories/destroy.rb
new file mode 100644
index 00000000000..8312193147f
--- /dev/null
+++ b/app/graphql/mutations/container_repositories/destroy.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Mutations
+ module ContainerRepositories
+ class Destroy < Mutations::BaseMutation
+ include ::Mutations::PackageEventable
+
+ graphql_name 'DestroyContainerRepository'
+
+ authorize :destroy_container_image
+
+ argument :id,
+ ::Types::GlobalIDType[::ContainerRepository],
+ required: true,
+ description: 'ID of the container repository.'
+
+ field :container_repository,
+ Types::ContainerRepositoryType,
+ null: false,
+ description: 'The container repository policy after scheduling the deletion.'
+
+ def resolve(id:)
+ container_repository = authorized_find!(id: id)
+
+ container_repository.delete_scheduled!
+ DeleteContainerRepositoryWorker.perform_async(current_user.id, container_repository.id)
+ track_event(:delete_repository, :container)
+
+ {
+ container_repository: container_repository,
+ 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[::ContainerRepository].coerce_isolated_input(id)
+ GitlabSchema.find_by_gid(id)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/custom_emoji/create.rb b/app/graphql/mutations/custom_emoji/create.rb
new file mode 100644
index 00000000000..d912a29d12e
--- /dev/null
+++ b/app/graphql/mutations/custom_emoji/create.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Mutations
+ module CustomEmoji
+ class Create < BaseMutation
+ include Mutations::ResolvesGroup
+
+ graphql_name 'CreateCustomEmoji'
+
+ authorize :create_custom_emoji
+
+ field :custom_emoji,
+ Types::CustomEmojiType,
+ null: true,
+ description: 'The new custom emoji'
+
+ argument :group_path, GraphQL::ID_TYPE,
+ required: true,
+ description: 'Namespace full path the emoji is associated with'
+
+ argument :name, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Name of the emoji'
+
+ argument :url, GraphQL::STRING_TYPE,
+ required: true,
+ as: :file,
+ description: 'Location of the emoji file'
+
+ def resolve(group_path:, **args)
+ group = authorized_find!(group_path: group_path)
+ # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37911#note_444682238
+ args[:external] = true
+
+ custom_emoji = group.custom_emoji.create(args)
+
+ {
+ custom_emoji: custom_emoji.valid? ? custom_emoji : nil,
+ errors: errors_on_object(custom_emoji)
+ }
+ end
+
+ private
+
+ def find_object(group_path:)
+ resolve_group(full_path: group_path)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/labels/create.rb b/app/graphql/mutations/labels/create.rb
new file mode 100644
index 00000000000..cb03651618e
--- /dev/null
+++ b/app/graphql/mutations/labels/create.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Labels
+ class Create < BaseMutation
+ include Mutations::ResolvesResourceParent
+
+ graphql_name 'LabelCreate'
+
+ field :label,
+ Types::LabelType,
+ null: true,
+ description: 'The label after mutation'
+
+ argument :title, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Title of the label'
+
+ argument :description, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Description of the label'
+
+ argument :color, GraphQL::STRING_TYPE,
+ required: false,
+ default_value: Label::DEFAULT_COLOR,
+ description: "The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the CSS color names in https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords"
+
+ authorize :admin_label
+
+ def resolve(args)
+ parent = authorized_resource_parent_find!(args)
+ parent_key = parent.is_a?(Project) ? :project : :group
+
+ label = ::Labels::CreateService.new(args).execute(parent_key => parent)
+
+ {
+ label: label.persisted? ? label : nil,
+ errors: errors_on_object(label)
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/merge_requests/set_labels.rb b/app/graphql/mutations/merge_requests/set_labels.rb
index c1e45808593..712c68c9425 100644
--- a/app/graphql/mutations/merge_requests/set_labels.rb
+++ b/app/graphql/mutations/merge_requests/set_labels.rb
@@ -6,7 +6,7 @@ module Mutations
graphql_name 'MergeRequestSetLabels'
argument :label_ids,
- [GraphQL::ID_TYPE],
+ [::Types::GlobalIDType[Label]],
required: true,
description: <<~DESC
The Label IDs to set. Replaces existing labels by default.
@@ -23,10 +23,11 @@ module Mutations
merge_request = authorized_find!(project_path: project_path, iid: iid)
project = merge_request.project
- label_ids = label_ids
- .map { |gid| GlobalID.parse(gid) }
- .select(&method(:label_descendant?))
- .map(&:model_id) # MergeRequests::UpdateService expects integers
+ # TODO: remove this line when the compatibility layer is removed:
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ label_ids = label_ids.map { |id| ::Types::GlobalIDType[::Label].coerce_isolated_input(id) }
+ # MergeRequests::UpdateService expects integers
+ label_ids = label_ids.compact.map(&:model_id)
attribute_name = case operation_mode
when Types::MutationOperationModeEnum.enum[:append]
@@ -45,10 +46,6 @@ module Mutations
errors: errors_on_object(merge_request)
}
end
-
- def label_descendant?(gid)
- gid&.model_class&.ancestors&.include?(Label)
- end
end
end
end
diff --git a/app/graphql/mutations/metrics/dashboard/annotations/delete.rb b/app/graphql/mutations/metrics/dashboard/annotations/delete.rb
index 6e183e78d9b..d6731dfcafd 100644
--- a/app/graphql/mutations/metrics/dashboard/annotations/delete.rb
+++ b/app/graphql/mutations/metrics/dashboard/annotations/delete.rb
@@ -9,8 +9,7 @@ module Mutations
authorize :delete_metrics_dashboard_annotation
- argument :id,
- GraphQL::ID_TYPE,
+ argument :id, ::Types::GlobalIDType[::Metrics::Dashboard::Annotation],
required: true,
description: 'The global ID of the annotation to delete'
diff --git a/app/graphql/mutations/notes/reposition_image_diff_note.rb b/app/graphql/mutations/notes/reposition_image_diff_note.rb
new file mode 100644
index 00000000000..0d88bcd9a30
--- /dev/null
+++ b/app/graphql/mutations/notes/reposition_image_diff_note.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ # This mutation differs from the update note mutations as it checks the
+ # `reposition_note` permission, and doesn't allow updating a note's `body`.
+ class RepositionImageDiffNote < Mutations::Notes::Base
+ graphql_name 'RepositionImageDiffNote'
+
+ description 'Repositions a DiffNote on an image (a `Note` where the `position.positionType` is `"image"`)'
+
+ authorize :reposition_note
+
+ argument :id,
+ Types::GlobalIDType[DiffNote],
+ loads: Types::Notes::NoteType,
+ as: :note,
+ required: true,
+ description: 'The global id of the DiffNote to update'
+
+ argument :position,
+ Types::Notes::UpdateDiffImagePositionInputType,
+ required: true,
+ description: copy_field_description(Types::Notes::NoteType, :position)
+
+ def resolve(note:, position:)
+ authorize!(note)
+
+ pre_update_checks!(note, position)
+
+ updated_note = ::Notes::UpdateService.new(
+ note.project,
+ current_user,
+ note_params(note.position, position)
+ ).execute(note)
+
+ {
+ note: updated_note.reset,
+ errors: errors_on_object(updated_note)
+ }
+ end
+
+ private
+
+ # An ImageDiffNote does not exist as a class itself, but is instead
+ # just a `DiffNote` with a particular kind of `Gitlab::Diff::Position`.
+ # In addition to accepting a `DiffNote` Global ID we also need to
+ # perform this check.
+ def pre_update_checks!(note, position)
+ unless note.position&.on_image?
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ 'Resource is not an ImageDiffNote'
+ end
+ end
+
+ def note_params(old_position, new_position)
+ position = old_position.to_h.merge(new_position)
+
+ {
+ position: Gitlab::Diff::Position.new(position)
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/update/image_diff_note.rb b/app/graphql/mutations/notes/update/image_diff_note.rb
index ef70a8d2bf4..f4533cd9edb 100644
--- a/app/graphql/mutations/notes/update/image_diff_note.rb
+++ b/app/graphql/mutations/notes/update/image_diff_note.rb
@@ -47,12 +47,11 @@ module Mutations
end
def position_params(note, args)
- new_position = args[:position]&.to_h&.compact
- return unless new_position
+ return unless args[:position]
original_position = note.position.to_h
- Gitlab::Diff::Position.new(original_position.merge(new_position))
+ Gitlab::Diff::Position.new(original_position.merge(args[:position]))
end
end
end
diff --git a/app/graphql/mutations/releases/base.rb b/app/graphql/mutations/releases/base.rb
new file mode 100644
index 00000000000..d53cfbe6a11
--- /dev/null
+++ b/app/graphql/mutations/releases/base.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Releases
+ class Base < BaseMutation
+ include ResolvesProject
+
+ argument :project_path, GraphQL::ID_TYPE,
+ required: true,
+ description: 'Full path of the project the release is associated with'
+
+ private
+
+ def find_object(full_path:)
+ resolve_project(full_path: full_path)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/releases/create.rb b/app/graphql/mutations/releases/create.rb
new file mode 100644
index 00000000000..57c1541c368
--- /dev/null
+++ b/app/graphql/mutations/releases/create.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Releases
+ class Create < Base
+ graphql_name 'ReleaseCreate'
+
+ field :release,
+ Types::ReleaseType,
+ null: true,
+ description: 'The release after mutation'
+
+ argument :tag_name, GraphQL::STRING_TYPE,
+ required: true, as: :tag,
+ description: 'Name of the tag to associate with the release'
+
+ argument :ref, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'The commit SHA or branch name to use if creating a new tag'
+
+ argument :name, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Name of the release'
+
+ argument :description, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Description (also known as "release notes") of the release'
+
+ argument :released_at, Types::TimeType,
+ required: false,
+ description: 'The date when the release will be/was ready. Defaults to the current time.'
+
+ argument :milestones, [GraphQL::STRING_TYPE],
+ required: false,
+ description: 'The title of each milestone the release is associated with. GitLab Premium customers can specify group milestones.'
+
+ argument :assets, Types::ReleaseAssetsInputType,
+ required: false,
+ description: 'Assets associated to the release'
+
+ authorize :create_release
+
+ def resolve(project_path:, milestones: nil, assets: nil, **scalars)
+ project = authorized_find!(full_path: project_path)
+
+ params = {
+ **scalars,
+ milestones: milestones.presence || [],
+ assets: assets.to_h
+ }.with_indifferent_access
+
+ result = ::Releases::CreateService.new(project, current_user, params).execute
+
+ if result[:status] == :success
+ {
+ release: result[:release],
+ errors: []
+ }
+ else
+ {
+ release: nil,
+ errors: [result[:message]]
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/snippets/destroy.rb b/app/graphql/mutations/snippets/destroy.rb
index dc9a1e82575..4915d7dd77a 100644
--- a/app/graphql/mutations/snippets/destroy.rb
+++ b/app/graphql/mutations/snippets/destroy.rb
@@ -7,8 +7,7 @@ module Mutations
ERROR_MSG = 'Error deleting the snippet'
- argument :id,
- GraphQL::ID_TYPE,
+ argument :id, ::Types::GlobalIDType[::Snippet],
required: true,
description: 'The global id of the snippet to destroy'
diff --git a/app/graphql/mutations/snippets/mark_as_spam.rb b/app/graphql/mutations/snippets/mark_as_spam.rb
index 8cfbbae7c08..d6b96c699c0 100644
--- a/app/graphql/mutations/snippets/mark_as_spam.rb
+++ b/app/graphql/mutations/snippets/mark_as_spam.rb
@@ -5,8 +5,7 @@ module Mutations
class MarkAsSpam < Base
graphql_name 'MarkAsSpamSnippet'
- argument :id,
- GraphQL::ID_TYPE,
+ argument :id, ::Types::GlobalIDType[::Snippet],
required: true,
description: 'The global id of the snippet to update'
diff --git a/app/graphql/mutations/snippets/update.rb b/app/graphql/mutations/snippets/update.rb
index 74266880806..bcaa807e4c1 100644
--- a/app/graphql/mutations/snippets/update.rb
+++ b/app/graphql/mutations/snippets/update.rb
@@ -7,8 +7,7 @@ module Mutations
graphql_name 'UpdateSnippet'
- argument :id,
- GraphQL::ID_TYPE,
+ argument :id, ::Types::GlobalIDType[::Snippet],
required: true,
description: 'The global id of the snippet to update'
diff --git a/app/graphql/mutations/terraform/state/base.rb b/app/graphql/mutations/terraform/state/base.rb
new file mode 100644
index 00000000000..b1721c784b1
--- /dev/null
+++ b/app/graphql/mutations/terraform/state/base.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Terraform
+ module State
+ class Base < BaseMutation
+ authorize :admin_terraform_state
+
+ argument :id,
+ Types::GlobalIDType[::Terraform::State],
+ required: true,
+ description: 'Global ID of the Terraform state'
+
+ private
+
+ def find_object(id:)
+ GitlabSchema.find_by_gid(id)
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/terraform/state/delete.rb b/app/graphql/mutations/terraform/state/delete.rb
new file mode 100644
index 00000000000..f08219cb395
--- /dev/null
+++ b/app/graphql/mutations/terraform/state/delete.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Terraform
+ module State
+ class Delete < Base
+ graphql_name 'TerraformStateDelete'
+
+ def resolve(id:)
+ state = authorized_find!(id: id)
+ state.destroy
+
+ { errors: errors_on_object(state) }
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/terraform/state/lock.rb b/app/graphql/mutations/terraform/state/lock.rb
new file mode 100644
index 00000000000..d22c8de2560
--- /dev/null
+++ b/app/graphql/mutations/terraform/state/lock.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Terraform
+ module State
+ class Lock < Base
+ graphql_name 'TerraformStateLock'
+
+ def resolve(id:)
+ state = authorized_find!(id: id)
+
+ if state.locked?
+ state.errors.add(:base, 'state is already locked')
+ else
+ state.update(lock_xid: lock_xid, locked_by_user: current_user, locked_at: Time.current)
+ end
+
+ { errors: errors_on_object(state) }
+ end
+
+ private
+
+ def lock_xid
+ SecureRandom.uuid
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/terraform/state/unlock.rb b/app/graphql/mutations/terraform/state/unlock.rb
new file mode 100644
index 00000000000..0818dbd7fb3
--- /dev/null
+++ b/app/graphql/mutations/terraform/state/unlock.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Terraform
+ module State
+ class Unlock < Base
+ graphql_name 'TerraformStateUnlock'
+
+ def resolve(id:)
+ state = authorized_find!(id: id)
+ state.update(lock_xid: nil, locked_by_user: nil, locked_at: nil)
+
+ { errors: errors_on_object(state) }
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/todos/base.rb b/app/graphql/mutations/todos/base.rb
index 6db863796bc..4dab3bbc3f4 100644
--- a/app/graphql/mutations/todos/base.rb
+++ b/app/graphql/mutations/todos/base.rb
@@ -11,16 +11,6 @@ module Mutations
id = ::Types::GlobalIDType[::Todo].coerce_isolated_input(id)
GitlabSchema.find_by_gid(id)
end
-
- def map_to_global_ids(ids)
- return [] if ids.blank?
-
- ids.map { |id| to_global_id(id) }
- end
-
- def to_global_id(id)
- Gitlab::GlobalId.as_global_id(id, model_name: Todo.name).to_s
- end
end
end
end
diff --git a/app/graphql/mutations/todos/create.rb b/app/graphql/mutations/todos/create.rb
new file mode 100644
index 00000000000..53c88696fdd
--- /dev/null
+++ b/app/graphql/mutations/todos/create.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Todos
+ class Create < ::Mutations::Todos::Base
+ graphql_name 'TodoCreate'
+
+ authorize :create_todo
+
+ argument :target_id,
+ Types::GlobalIDType[Todoable],
+ required: true,
+ description: "The global ID of the to-do item's parent. Issues, merge requests, designs and epics are supported"
+
+ field :todo, Types::TodoType,
+ null: true,
+ description: 'The to-do created'
+
+ def resolve(target_id:)
+ id = ::Types::GlobalIDType[Todoable].coerce_isolated_input(target_id)
+ target = authorized_find!(id)
+
+ todo = TodoService.new.mark_todo(target, current_user)&.first
+ errors = errors_on_object(todo) if todo
+
+ {
+ todo: todo,
+ errors: errors
+ }
+ end
+
+ private
+
+ def find_object(id)
+ GitlabSchema.find_by_gid(id)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/todos/mark_all_done.rb b/app/graphql/mutations/todos/mark_all_done.rb
index 8b53658ddd5..97bbbeeaa2f 100644
--- a/app/graphql/mutations/todos/mark_all_done.rb
+++ b/app/graphql/mutations/todos/mark_all_done.rb
@@ -8,7 +8,7 @@ module Mutations
authorize :update_user
field :updated_ids,
- [GraphQL::ID_TYPE],
+ [::Types::GlobalIDType[::Todo]],
null: false,
deprecated: { reason: 'Use todos', milestone: '13.2' },
description: 'Ids of the updated todos'
@@ -23,7 +23,7 @@ module Mutations
updated_ids = mark_all_todos_done
{
- updated_ids: map_to_global_ids(updated_ids),
+ updated_ids: updated_ids,
todos: Todo.id_in(updated_ids),
errors: []
}
diff --git a/app/graphql/mutations/todos/restore_many.rb b/app/graphql/mutations/todos/restore_many.rb
index ea5f5414134..9e0a95c48ec 100644
--- a/app/graphql/mutations/todos/restore_many.rb
+++ b/app/graphql/mutations/todos/restore_many.rb
@@ -12,7 +12,7 @@ module Mutations
required: true,
description: 'The global ids of the todos to restore (a maximum of 50 is supported at once)'
- field :updated_ids, [GraphQL::ID_TYPE],
+ field :updated_ids, [::Types::GlobalIDType[Todo]],
null: false,
description: 'The ids of the updated todo items',
deprecated: { reason: 'Use todos', milestone: '13.2' }
@@ -28,7 +28,7 @@ module Mutations
updated_ids = restore(todos)
{
- updated_ids: gids_of(updated_ids),
+ updated_ids: updated_ids,
todos: Todo.id_in(updated_ids),
errors: errors_on_objects(todos)
}
@@ -36,10 +36,6 @@ module Mutations
private
- def gids_of(ids)
- ids.map { |id| Gitlab::GlobalId.as_global_id(id, model_name: Todo.name).to_s }
- end
-
def model_ids_of(ids)
ids.map do |gid|
# TODO: remove this line when the compatibility layer is removed
diff --git a/app/graphql/queries/design_management/design_permissions.query.graphql b/app/graphql/queries/design_management/design_permissions.query.graphql
new file mode 100644
index 00000000000..55dfa35129c
--- /dev/null
+++ b/app/graphql/queries/design_management/design_permissions.query.graphql
@@ -0,0 +1,13 @@
+query permissions($fullPath: ID!, $iid: String!) {
+ project(fullPath: $fullPath) {
+ __typename
+ id
+ issue(iid: $iid) {
+ __typename
+ userPermissions {
+ __typename
+ createDesign
+ }
+ }
+ }
+}
diff --git a/app/graphql/queries/design_management/get_design_list.query.graphql b/app/graphql/queries/design_management/get_design_list.query.graphql
new file mode 100644
index 00000000000..ade03d99797
--- /dev/null
+++ b/app/graphql/queries/design_management/get_design_list.query.graphql
@@ -0,0 +1,40 @@
+query getDesignList($fullPath: ID!, $iid: String!, $atVersion: ID) {
+ project(fullPath: $fullPath) {
+ __typename
+ id
+ issue(iid: $iid) {
+ __typename
+ designCollection {
+ __typename
+ copyState
+ designs(atVersion: $atVersion) {
+ __typename
+ nodes {
+ __typename
+ id
+ event
+ filename
+ notesCount
+ image
+ imageV432x230
+ currentUserTodos(state: pending) {
+ __typename
+ nodes {
+ __typename
+ id
+ }
+ }
+ }
+ }
+ versions {
+ __typename
+ nodes {
+ __typename
+ id
+ sha
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/graphql/queries/repository/files.query.graphql b/app/graphql/queries/repository/files.query.graphql
new file mode 100644
index 00000000000..232d98a932c
--- /dev/null
+++ b/app/graphql/queries/repository/files.query.graphql
@@ -0,0 +1,76 @@
+fragment PageInfo on PageInfo {
+ __typename
+ hasNextPage
+ hasPreviousPage
+ startCursor
+ endCursor
+}
+
+fragment TreeEntry on Entry {
+ __typename
+ id
+ sha
+ name
+ flatPath
+ type
+}
+
+query getFiles(
+ $projectPath: ID!
+ $path: String
+ $ref: String!
+ $pageSize: Int!
+ $nextPageCursor: String
+) {
+ project(fullPath: $projectPath) {
+ __typename
+ repository {
+ __typename
+ tree(path: $path, ref: $ref) {
+ __typename
+ trees(first: $pageSize, after: $nextPageCursor) {
+ __typename
+ edges {
+ __typename
+ node {
+ ...TreeEntry
+ webPath
+ }
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+ submodules(first: $pageSize, after: $nextPageCursor) {
+ __typename
+ edges {
+ __typename
+ node {
+ ...TreeEntry
+ webUrl
+ treeUrl
+ }
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+ blobs(first: $pageSize, after: $nextPageCursor) {
+ __typename
+ edges {
+ __typename
+ node {
+ ...TreeEntry
+ mode
+ webPath
+ lfsOid
+ }
+ }
+ pageInfo {
+ ...PageInfo
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/graphql/queries/repository/permissions.query.graphql b/app/graphql/queries/repository/permissions.query.graphql
new file mode 100644
index 00000000000..c0262a882cd
--- /dev/null
+++ b/app/graphql/queries/repository/permissions.query.graphql
@@ -0,0 +1,11 @@
+query getPermissions($projectPath: ID!) {
+ project(fullPath: $projectPath) {
+ __typename
+ userPermissions {
+ __typename
+ pushCode
+ forkProject
+ createMergeRequestIn
+ }
+ }
+}
diff --git a/app/graphql/queries/snippet/project_permissions.query.graphql b/app/graphql/queries/snippet/project_permissions.query.graphql
new file mode 100644
index 00000000000..0c38e4f8a07
--- /dev/null
+++ b/app/graphql/queries/snippet/project_permissions.query.graphql
@@ -0,0 +1,9 @@
+query CanCreateProjectSnippet($fullPath: ID!) {
+ project(fullPath: $fullPath) {
+ __typename
+ userPermissions {
+ __typename
+ createSnippet
+ }
+ }
+}
diff --git a/app/graphql/queries/snippet/snippet.query.graphql b/app/graphql/queries/snippet/snippet.query.graphql
new file mode 100644
index 00000000000..2205dc26642
--- /dev/null
+++ b/app/graphql/queries/snippet/snippet.query.graphql
@@ -0,0 +1,65 @@
+query GetSnippetQuery($ids: [SnippetID!]) {
+ snippets(ids: $ids) {
+ __typename
+ nodes {
+ __typename
+ id
+ title
+ description
+ descriptionHtml
+ createdAt
+ updatedAt
+ visibilityLevel
+ webUrl
+ httpUrlToRepo
+ sshUrlToRepo
+ blobs {
+ __typename
+ nodes {
+ __typename
+ binary
+ name
+ path
+ rawPath
+ size
+ externalStorage
+ renderedAsText
+ simpleViewer {
+ __typename
+ collapsed
+ renderError
+ tooLarge
+ type
+ fileType
+ }
+ richViewer {
+ __typename
+ collapsed
+ renderError
+ tooLarge
+ type
+ fileType
+ }
+ }
+ }
+ userPermissions {
+ __typename
+ adminSnippet
+ updateSnippet
+ }
+ project {
+ __typename
+ fullPath
+ webUrl
+ }
+ author {
+ __typename
+ id
+ avatarUrl
+ name
+ username
+ webUrl
+ }
+ }
+ }
+}
diff --git a/app/graphql/queries/snippet/snippet_blob_content.query.graphql b/app/graphql/queries/snippet/snippet_blob_content.query.graphql
new file mode 100644
index 00000000000..005f42ff726
--- /dev/null
+++ b/app/graphql/queries/snippet/snippet_blob_content.query.graphql
@@ -0,0 +1,18 @@
+query SnippetBlobContent($ids: [ID!], $rich: Boolean!, $paths: [String!]) {
+ snippets(ids: $ids) {
+ __typename
+ nodes {
+ __typename
+ id
+ blobs(paths: $paths) {
+ __typename
+ nodes {
+ __typename
+ path
+ richData @include(if: $rich)
+ plainData @skip(if: $rich)
+ }
+ }
+ }
+ }
+}
diff --git a/app/graphql/queries/snippet/user_permissions.query.graphql b/app/graphql/queries/snippet/user_permissions.query.graphql
new file mode 100644
index 00000000000..a4914189807
--- /dev/null
+++ b/app/graphql/queries/snippet/user_permissions.query.graphql
@@ -0,0 +1,9 @@
+query CanCreatePersonalSnippet {
+ currentUser {
+ __typename
+ userPermissions {
+ __typename
+ createSnippet
+ }
+ }
+}
diff --git a/app/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver.rb b/app/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver.rb
index aea3afa8ec5..9bac9f222ab 100644
--- a/app/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver.rb
+++ b/app/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver.rb
@@ -13,10 +13,20 @@ module Resolvers
required: true,
description: 'The type of measurement/statistics to retrieve'
- def resolve(identifier:)
+ argument :recorded_after, Types::TimeType,
+ required: false,
+ description: 'Measurement recorded after this date'
+
+ argument :recorded_before, Types::TimeType,
+ required: false,
+ description: 'Measurement recorded before this date'
+
+ def resolve(identifier:, recorded_before: nil, recorded_after: nil)
authorize!
::Analytics::InstanceStatistics::Measurement
+ .recorded_after(recorded_after)
+ .recorded_before(recorded_before)
.with_identifier(identifier)
.order_by_latest
end
diff --git a/app/graphql/resolvers/alert_management/alert_resolver.rb b/app/graphql/resolvers/alert_management/alert_resolver.rb
index dc9b1dbb5f4..c3219d9cdc3 100644
--- a/app/graphql/resolvers/alert_management/alert_resolver.rb
+++ b/app/graphql/resolvers/alert_management/alert_resolver.rb
@@ -19,7 +19,7 @@ module Resolvers
required: false
argument :search, GraphQL::STRING_TYPE,
- description: 'Search criteria for filtering alerts. This will search on title, description, service, monitoring_tool.',
+ description: 'Search query for title, description, service, or monitoring_tool.',
required: false
argument :assignee_username, GraphQL::STRING_TYPE,
diff --git a/app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb b/app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb
index 96ea4610aff..8fc0f9fd1ff 100644
--- a/app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb
+++ b/app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb
@@ -6,7 +6,7 @@ module Resolvers
type Types::AlertManagement::AlertStatusCountsType, null: true
argument :search, GraphQL::STRING_TYPE,
- description: 'Search criteria for filtering alerts. This will search on title, description, service, monitoring_tool.',
+ description: 'Search query for title, description, service, or monitoring_tool.',
required: false
argument :assignee_username, GraphQL::STRING_TYPE,
diff --git a/app/graphql/resolvers/alert_management/integrations_resolver.rb b/app/graphql/resolvers/alert_management/integrations_resolver.rb
new file mode 100644
index 00000000000..4d1fe367277
--- /dev/null
+++ b/app/graphql/resolvers/alert_management/integrations_resolver.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module AlertManagement
+ class IntegrationsResolver < BaseResolver
+ alias_method :project, :synchronized_object
+
+ type Types::AlertManagement::IntegrationType.connection_type, null: true
+
+ def resolve(**args)
+ http_integrations + prometheus_integrations
+ end
+
+ private
+
+ def prometheus_integrations
+ return [] unless Ability.allowed?(current_user, :admin_project, project)
+
+ Array(project.prometheus_service)
+ end
+
+ def http_integrations
+ return [] unless Ability.allowed?(current_user, :admin_operations, project)
+
+ ::AlertManagement::HttpIntegrationsFinder.new(project, {}).execute
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/assigned_merge_requests_resolver.rb b/app/graphql/resolvers/assigned_merge_requests_resolver.rb
index 172a8e298ad..30415ef5d2d 100644
--- a/app/graphql/resolvers/assigned_merge_requests_resolver.rb
+++ b/app/graphql/resolvers/assigned_merge_requests_resolver.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module Resolvers
- class AssignedMergeRequestsResolver < UserMergeRequestsResolver
+ class AssignedMergeRequestsResolver < UserMergeRequestsResolverBase
+ type ::Types::MergeRequestType.connection_type, null: true
accept_author
def user_role
diff --git a/app/graphql/resolvers/authored_merge_requests_resolver.rb b/app/graphql/resolvers/authored_merge_requests_resolver.rb
index bc796f8685a..1426ca83c06 100644
--- a/app/graphql/resolvers/authored_merge_requests_resolver.rb
+++ b/app/graphql/resolvers/authored_merge_requests_resolver.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module Resolvers
- class AuthoredMergeRequestsResolver < UserMergeRequestsResolver
+ class AuthoredMergeRequestsResolver < UserMergeRequestsResolverBase
+ type ::Types::MergeRequestType.connection_type, null: true
accept_assignee
def user_role
diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb
index 2b8854fb4d0..87a63231b22 100644
--- a/app/graphql/resolvers/base_resolver.rb
+++ b/app/graphql/resolvers/base_resolver.rb
@@ -8,32 +8,81 @@ module Resolvers
argument_class ::Types::BaseArgument
- def self.single
- @single ||= Class.new(self) do
- def ready?(**args)
- ready, early_return = super
- [ready, select_result(early_return)]
- end
+ def self.singular_type
+ return unless type
- def resolve(**args)
- select_result(super)
- end
+ unwrapped = type.unwrap
+
+ %i[node_type relay_node_type of_type itself].reduce(nil) do |t, m|
+ t || unwrapped.try(m)
+ end
+ end
- def single?
- true
+ def self.when_single(&block)
+ as_single << block
+
+ # Have we been called after defining the single version of this resolver?
+ if @single.present?
+ @single.instance_exec(&block)
+ end
+ end
+
+ def self.as_single
+ @as_single ||= []
+ end
+
+ def self.single_definition_blocks
+ ancestors.flat_map { |klass| klass.try(:as_single) || [] }
+ end
+
+ def self.single
+ @single ||= begin
+ parent = self
+ klass = Class.new(self) do
+ type parent.singular_type, null: true
+
+ def ready?(**args)
+ ready, early_return = super
+ [ready, select_result(early_return)]
+ end
+
+ def resolve(**args)
+ select_result(super)
+ end
+
+ def single?
+ true
+ end
+
+ def select_result(results)
+ results&.first
+ end
+
+ define_singleton_method :to_s do
+ "#{parent}.single"
+ end
end
- def select_result(results)
- results&.first
+ single_definition_blocks.each do |definition|
+ klass.instance_exec(&definition)
end
+
+ klass
end
end
def self.last
+ parent = self
@last ||= Class.new(self.single) do
+ type parent.singular_type, null: true
+
def select_result(results)
results&.last
end
+
+ define_singleton_method :to_s do
+ "#{parent}.last"
+ end
end
end
@@ -68,14 +117,13 @@ module Resolvers
end
end
+ # TODO: remove! This should never be necessary
+ # Remove as part of https://gitlab.com/gitlab-org/gitlab/-/issues/13984,
+ # since once we use that authorization approach, the object is guaranteed to
+ # be synchronized before any field.
def synchronized_object
strong_memoize(:synchronized_object) do
- case object
- when BatchLoader::GraphQL
- object.sync
- else
- object
- end
+ ::Gitlab::Graphql::Lazy.force(object)
end
end
diff --git a/app/graphql/resolvers/board_lists_resolver.rb b/app/graphql/resolvers/board_lists_resolver.rb
index 3384b37e2ce..ef12dfa19ff 100644
--- a/app/graphql/resolvers/board_lists_resolver.rb
+++ b/app/graphql/resolvers/board_lists_resolver.rb
@@ -7,7 +7,7 @@ module Resolvers
type Types::BoardListType, null: true
- argument :id, GraphQL::ID_TYPE,
+ argument :id, Types::GlobalIDType[List],
required: false,
description: 'Find a list by its global ID'
diff --git a/app/graphql/resolvers/boards_resolver.rb b/app/graphql/resolvers/boards_resolver.rb
index 82efd92d33f..42b6ce03118 100644
--- a/app/graphql/resolvers/boards_resolver.rb
+++ b/app/graphql/resolvers/boards_resolver.rb
@@ -4,7 +4,7 @@ module Resolvers
class BoardsResolver < BaseResolver
type Types::BoardType, null: true
- argument :id, GraphQL::ID_TYPE,
+ argument :id, ::Types::GlobalIDType[::Board],
required: false,
description: 'Find a board by its ID'
@@ -23,10 +23,13 @@ module Resolvers
private
- def extract_board_id(gid)
- return unless gid.present?
+ def extract_board_id(id)
+ return unless id.present?
- GitlabSchema.parse_gid(gid, expected_type: ::Board).model_id
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ id = Types::GlobalIDType[Board].coerce_isolated_input(id)
+ id.model_id
end
end
end
diff --git a/app/graphql/resolvers/ci/jobs_resolver.rb b/app/graphql/resolvers/ci/jobs_resolver.rb
new file mode 100644
index 00000000000..8a9ae42b375
--- /dev/null
+++ b/app/graphql/resolvers/ci/jobs_resolver.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Ci
+ class JobsResolver < BaseResolver
+ alias_method :pipeline, :object
+
+ argument :security_report_types, [Types::Security::ReportTypeEnum],
+ required: false,
+ description: 'Filter jobs by the type of security report they produce'
+
+ def resolve(security_report_types: [])
+ if security_report_types.present?
+ ::Security::SecurityJobsFinder.new(
+ pipeline: pipeline,
+ job_types: security_report_types
+ ).execute
+ else
+ pipeline.statuses
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/ci/runner_setup_resolver.rb b/app/graphql/resolvers/ci/runner_setup_resolver.rb
new file mode 100644
index 00000000000..241cd57f74b
--- /dev/null
+++ b/app/graphql/resolvers/ci/runner_setup_resolver.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Ci
+ class RunnerSetupResolver < BaseResolver
+ type Types::Ci::RunnerSetupType, null: true
+
+ argument :platform, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Platform to generate the instructions for'
+
+ argument :architecture, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Architecture to generate the instructions for'
+
+ argument :project_id, ::Types::GlobalIDType[::Project],
+ required: false,
+ description: 'Project to register the runner for'
+
+ argument :group_id, ::Types::GlobalIDType[::Group],
+ required: false,
+ description: 'Group to register the runner for'
+
+ def resolve(platform:, architecture:, **args)
+ instructions = Gitlab::Ci::RunnerInstructions.new(
+ { current_user: current_user, os: platform, arch: architecture }.merge(target_param(args))
+ )
+
+ {
+ install_instructions: instructions.install_script || other_install_instructions(platform),
+ register_instructions: instructions.register_command
+ }
+ ensure
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'User is not authorized to register a runner for the specified resource!' if instructions.errors.include?('Gitlab::Access::AccessDeniedError')
+ end
+
+ private
+
+ def other_install_instructions(platform)
+ Gitlab::Ci::RunnerInstructions::OTHER_ENVIRONMENTS[platform.to_sym][:installation_instructions_url]
+ end
+
+ def target_param(args)
+ project_param(args[:project_id]) || group_param(args[:group_id]) || {}
+ end
+
+ def project_param(project_id)
+ return unless project_id
+
+ { project: find_object(project_id) }
+ end
+
+ def group_param(group_id)
+ return unless group_id
+
+ { group: find_object(group_id) }
+ end
+
+ def find_object(gid)
+ GlobalID::Locator.locate(gid)
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/commit_pipelines_resolver.rb b/app/graphql/resolvers/commit_pipelines_resolver.rb
index 92a83523593..40af392200c 100644
--- a/app/graphql/resolvers/commit_pipelines_resolver.rb
+++ b/app/graphql/resolvers/commit_pipelines_resolver.rb
@@ -1,7 +1,9 @@
# frozen_string_literal: true
+# rubocop: disable Graphql/ResolverType
module Resolvers
class CommitPipelinesResolver < BaseResolver
+ # The GraphQL type here gets defined in this include
include ::ResolvesPipelines
alias_method :commit, :object
@@ -11,3 +13,4 @@ module Resolvers
end
end
end
+# rubocop: enable Graphql/ResolverType
diff --git a/app/graphql/resolvers/concerns/caching_array_resolver.rb b/app/graphql/resolvers/concerns/caching_array_resolver.rb
new file mode 100644
index 00000000000..4f2c8b98928
--- /dev/null
+++ b/app/graphql/resolvers/concerns/caching_array_resolver.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+# Concern that will eliminate N+1 queries for size-constrained
+# collections of items.
+#
+# **note**: The resolver will never load more items than
+# `@field.max_page_size` if defined, falling back to
+# `context.schema.default_max_page_size`.
+#
+# provided that:
+#
+# - the query can be uniquely determined by the object and the arguments
+# - the model class includes FromUnion
+# - the model class defines a scalar primary key
+#
+# This comes at the cost of returning arrays, not relations, so we don't get
+# any keyset pagination goodness. Consequently, this is only suitable for small-ish
+# result sets, as the full result set will be loaded into memory.
+#
+# To enforce this, the resolver limits the size of result sets to
+# `@field.max_page_size || context.schema.default_max_page_size`.
+#
+# **important**: If the cardinality of your collection is likely to be greater than 100,
+# then you will want to pass `max_page_size:` as part of the field definition
+# or (ideally) as part of the resolver `field_options`.
+#
+# How to implement:
+# --------------------
+#
+# Each including class operates on two generic parameters, A and R:
+# - A is any Object that can be used as a Hash key. Instances of A
+# are returned by `query_input` and then passed to `query_for`.
+# - R is any subclass of ApplicationRecord that includes FromUnion.
+# R must have a single scalar primary_key
+#
+# Classes must implement:
+# - #model_class -> Class[R]. (Must respond to :primary_key, and :from_union)
+# - #query_input(**kwargs) -> A (Must be hashable)
+# - #query_for(A) -> ActiveRecord::Relation[R]
+#
+# Note the relationship between query_input and query_for, one of which
+# consumes the input of the other
+# (i.e. `resolve(**args).sync == query_for(query_input(**args)).to_a`).
+#
+# Classes may implement:
+# - #item_found(A, R) (return value is ignored)
+# - max_union_size Integer (the maximum number of queries to run in any one union)
+module CachingArrayResolver
+ MAX_UNION_SIZE = 50
+
+ def resolve(**args)
+ key = query_input(**args)
+
+ BatchLoader::GraphQL.for(key).batch(**batch) do |keys, loader|
+ if keys.size == 1
+ # We can avoid the union entirely.
+ k = keys.first
+ limit(query_for(k)).each { |item| found(loader, k, item) }
+ else
+ queries = keys.map { |key| query_for(key) }
+
+ queries.in_groups_of(max_union_size, false).each do |group|
+ by_id = model_class
+ .from_union(tag(group), remove_duplicates: false)
+ .group_by { |r| r[primary_key] }
+
+ by_id.values.each do |item_group|
+ item = item_group.first
+ item_group.map(&:union_member_idx).each do |i|
+ found(loader, keys[i], item)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ # Override this to intercept the items once they are found
+ def item_found(query_input, item)
+ end
+
+ def max_union_size
+ MAX_UNION_SIZE
+ end
+
+ private
+
+ def primary_key
+ @primary_key ||= (model_class.primary_key || raise("No primary key for #{model_class}"))
+ end
+
+ def batch
+ { key: self.class, default_value: [] }
+ end
+
+ def found(loader, key, value)
+ loader.call(key) do |vs|
+ item_found(key, value)
+ vs << value
+ end
+ end
+
+ # Tag each row returned from each query with a the index of which query in
+ # the union it comes from. This lets us map the results back to the cache key.
+ def tag(queries)
+ queries.each_with_index.map do |q, i|
+ limit(q.select(all_fields, member_idx(i)))
+ end
+ end
+
+ def limit(query)
+ query.limit(query_limit) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
+ def all_fields
+ model_class.arel_table[Arel.star]
+ end
+
+ # rubocop: disable Graphql/Descriptions (false positive!)
+ def query_limit
+ field&.max_page_size.presence || context.schema.default_max_page_size
+ end
+ # rubocop: enable Graphql/Descriptions
+
+ def member_idx(idx)
+ ::Arel::Nodes::SqlLiteral.new(idx.to_s).as('union_member_idx')
+ end
+end
diff --git a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb
index fe6fa0bb262..4715b867ecb 100644
--- a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb
+++ b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb
@@ -29,7 +29,7 @@ module IssueResolverArguments
description: 'Usernames of users assigned to the issue'
argument :assignee_id, GraphQL::STRING_TYPE,
required: false,
- description: 'ID of a user assigned to the issues, "none" and "any" values supported'
+ description: 'ID of a user assigned to the issues, "none" and "any" values are supported'
argument :created_before, Types::TimeType,
required: false,
description: 'Issues created before this date'
diff --git a/app/graphql/resolvers/concerns/looks_ahead.rb b/app/graphql/resolvers/concerns/looks_ahead.rb
index 61f23920ebb..d468047b539 100644
--- a/app/graphql/resolvers/concerns/looks_ahead.rb
+++ b/app/graphql/resolvers/concerns/looks_ahead.rb
@@ -4,6 +4,7 @@ module LooksAhead
extend ActiveSupport::Concern
included do
+ extras [:lookahead]
attr_accessor :lookahead
end
diff --git a/app/graphql/resolvers/concerns/resolves_pipelines.rb b/app/graphql/resolvers/concerns/resolves_pipelines.rb
index 46d9e174deb..f061f5f1606 100644
--- a/app/graphql/resolvers/concerns/resolves_pipelines.rb
+++ b/app/graphql/resolvers/concerns/resolves_pipelines.rb
@@ -4,7 +4,7 @@ module ResolvesPipelines
extend ActiveSupport::Concern
included do
- type [Types::Ci::PipelineType], null: false
+ type Types::Ci::PipelineType.connection_type, null: false
argument :status,
Types::Ci::PipelineStatusEnum,
required: false,
diff --git a/app/graphql/resolvers/concerns/resolves_project.rb b/app/graphql/resolvers/concerns/resolves_project.rb
index 3c5ce3dab01..b2ee7d7e850 100644
--- a/app/graphql/resolvers/concerns/resolves_project.rb
+++ b/app/graphql/resolvers/concerns/resolves_project.rb
@@ -1,6 +1,9 @@
# frozen_string_literal: true
module ResolvesProject
+ # Accepts EITHER one of
+ # - full_path: String (see Project#full_path)
+ # - project_id: GlobalID. Arguments should be typed as: `::Types::GlobalIDType[Project]`
def resolve_project(full_path: nil, project_id: nil)
unless full_path.present? ^ project_id.present?
raise ::Gitlab::Graphql::Errors::ArgumentError, 'Incompatible arguments: projectId, projectPath.'
diff --git a/app/graphql/resolvers/concerns/resolves_snippets.rb b/app/graphql/resolvers/concerns/resolves_snippets.rb
index 483372bbf63..790ff4f774f 100644
--- a/app/graphql/resolvers/concerns/resolves_snippets.rb
+++ b/app/graphql/resolvers/concerns/resolves_snippets.rb
@@ -4,9 +4,9 @@ module ResolvesSnippets
extend ActiveSupport::Concern
included do
- type Types::SnippetType, null: false
+ type Types::SnippetType.connection_type, null: false
- argument :ids, [GraphQL::ID_TYPE],
+ argument :ids, [::Types::GlobalIDType[::Snippet]],
required: false,
description: 'Array of global snippet ids, e.g., "gid://gitlab/ProjectSnippet/1"'
@@ -32,16 +32,15 @@ module ResolvesSnippets
}.merge(options_by_type(args[:type]))
end
- def resolve_ids(ids)
- Array.wrap(ids).map { |id| resolve_gid(id, :id) }
- end
-
- def resolve_gid(gid, argument)
- return unless gid.present?
+ def resolve_ids(ids, type = ::Types::GlobalIDType[::Snippet])
+ Array.wrap(ids).map do |id|
+ next unless id.present?
- GlobalID.parse(gid)&.model_id.tap do |id|
- raise Gitlab::Graphql::Errors::ArgumentError, "Invalid global id format for param #{argument}" if id.nil?
- end
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ id = type.coerce_isolated_input(id)
+ id.model_id
+ end.compact
end
def options_by_type(type)
diff --git a/app/graphql/resolvers/container_repositories_resolver.rb b/app/graphql/resolvers/container_repositories_resolver.rb
new file mode 100644
index 00000000000..b4b2893a3b8
--- /dev/null
+++ b/app/graphql/resolvers/container_repositories_resolver.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class ContainerRepositoriesResolver < BaseResolver
+ include ::Mutations::PackageEventable
+
+ type Types::ContainerRepositoryType, null: true
+
+ argument :name, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Filter the container repositories by their name'
+
+ def resolve(name: nil)
+ ContainerRepositoriesFinder.new(user: current_user, subject: object, params: { name: name })
+ .execute
+ .tap { track_event(:list_repositories, :container) }
+ end
+ end
+end
diff --git a/app/graphql/resolvers/design_management/design_at_version_resolver.rb b/app/graphql/resolvers/design_management/design_at_version_resolver.rb
index fd9b349f974..1b69efebe4e 100644
--- a/app/graphql/resolvers/design_management/design_at_version_resolver.rb
+++ b/app/graphql/resolvers/design_management/design_at_version_resolver.rb
@@ -9,7 +9,7 @@ module Resolvers
authorize :read_design
- argument :id, GraphQL::ID_TYPE,
+ argument :id, ::Types::GlobalIDType[::DesignManagement::DesignAtVersion],
required: true,
description: 'The Global ID of the design at this version'
@@ -18,7 +18,10 @@ module Resolvers
end
def find_object(id:)
- dav = GitlabSchema.object_from_id(id, expected_type: ::DesignManagement::DesignAtVersion)
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ id = ::Types::GlobalIDType[::DesignManagement::DesignAtVersion].coerce_isolated_input(id)
+ dav = GitlabSchema.find_by_gid(id)
return unless consistent?(dav)
dav
@@ -35,7 +38,7 @@ module Resolvers
# that the DesignAtVersion as found by its ID does in fact belong
# to this issue.
def consistent?(dav)
- issue.nil? || (dav&.design&.issue_id == issue.id)
+ issue.nil? || (dav.present? && dav.design&.issue_id == issue.id)
end
def issue
diff --git a/app/graphql/resolvers/design_management/design_resolver.rb b/app/graphql/resolvers/design_management/design_resolver.rb
index 05bdbbbe407..e0a68bae397 100644
--- a/app/graphql/resolvers/design_management/design_resolver.rb
+++ b/app/graphql/resolvers/design_management/design_resolver.rb
@@ -3,7 +3,9 @@
module Resolvers
module DesignManagement
class DesignResolver < BaseResolver
- argument :id, GraphQL::ID_TYPE,
+ type ::Types::DesignManagement::DesignType, null: true
+
+ argument :id, ::Types::GlobalIDType[::DesignManagement::Design],
required: false,
description: 'Find a design by its ID'
@@ -50,7 +52,11 @@ module Resolvers
end
def parse_gid(gid)
- GitlabSchema.parse_gid(gid, expected_type: ::DesignManagement::Design).model_id
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ gid = ::Types::GlobalIDType[::DesignManagement::Design].coerce_isolated_input(gid)
+
+ gid.model_id
end
end
end
diff --git a/app/graphql/resolvers/design_management/designs_resolver.rb b/app/graphql/resolvers/design_management/designs_resolver.rb
index 955ea6304e0..c588142ea6b 100644
--- a/app/graphql/resolvers/design_management/designs_resolver.rb
+++ b/app/graphql/resolvers/design_management/designs_resolver.rb
@@ -3,16 +3,18 @@
module Resolvers
module DesignManagement
class DesignsResolver < BaseResolver
- argument :ids,
- [GraphQL::ID_TYPE],
+ DesignID = ::Types::GlobalIDType[::DesignManagement::Design]
+ VersionID = ::Types::GlobalIDType[::DesignManagement::Version]
+
+ type ::Types::DesignManagement::DesignType.connection_type, null: true
+
+ argument :ids, [DesignID],
required: false,
description: 'Filters designs by their ID'
- argument :filenames,
- [GraphQL::STRING_TYPE],
+ argument :filenames, [GraphQL::STRING_TYPE],
required: false,
description: 'Filters designs by their filename'
- argument :at_version,
- GraphQL::ID_TYPE,
+ argument :at_version, VersionID,
required: false,
description: 'Filters designs to only those that existed at the version. ' \
'If argument is omitted or nil then all designs will reflect the latest version'
@@ -36,11 +38,20 @@ module Resolvers
def version(at_version)
return unless at_version
- GitlabSchema.object_from_id(at_version, expected_type: ::DesignManagement::Version)&.sync
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ at_version = VersionID.coerce_isolated_input(at_version)
+ # TODO: when we get promises use this to make resolve lazy
+ Gitlab::Graphql::Lazy.force(GitlabSchema.find_by_gid(at_version))
end
- def design_ids(ids)
- ids&.map { |id| GlobalID.parse(id, expected_type: ::DesignManagement::Design).model_id }
+ def design_ids(gids)
+ return if gids.nil?
+
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ gids = gids.map { |id| DesignID.coerce_isolated_input(id) }
+ gids.map(&:model_id)
end
def issue
diff --git a/app/graphql/resolvers/design_management/version/design_at_version_resolver.rb b/app/graphql/resolvers/design_management/version/design_at_version_resolver.rb
index 03f7908780c..70021057f71 100644
--- a/app/graphql/resolvers/design_management/version/design_at_version_resolver.rb
+++ b/app/graphql/resolvers/design_management/version/design_at_version_resolver.rb
@@ -5,17 +5,20 @@ module Resolvers
module Version
# Resolver for a DesignAtVersion object given an implicit version context
class DesignAtVersionResolver < BaseResolver
+ DesignAtVersionID = ::Types::GlobalIDType[::DesignManagement::DesignAtVersion]
+ DesignID = ::Types::GlobalIDType[::DesignManagement::Design]
+
include Gitlab::Graphql::Authorize::AuthorizeResource
type Types::DesignManagement::DesignAtVersionType, null: true
authorize :read_design
- argument :id, GraphQL::ID_TYPE,
+ argument :id, DesignAtVersionID,
required: false,
as: :design_at_version_id,
description: 'The ID of the DesignAtVersion'
- argument :design_id, GraphQL::ID_TYPE,
+ argument :design_id, DesignID,
required: false,
description: 'The ID of a specific design'
argument :filename, GraphQL::STRING_TYPE,
@@ -29,6 +32,11 @@ module Resolvers
def resolve(design_id: nil, filename: nil, design_at_version_id: nil)
validate_arguments(design_id, filename, design_at_version_id)
+ # TODO: remove this when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ design_id &&= DesignID.coerce_isolated_input(design_id)
+ design_at_version_id &&= DesignAtVersionID.coerce_isolated_input(design_at_version_id)
+
return unless Ability.allowed?(current_user, :read_design, issue)
return specific_design_at_version(design_at_version_id) if design_at_version_id
@@ -49,7 +57,7 @@ module Resolvers
end
def specific_design_at_version(id)
- dav = GitlabSchema.object_from_id(id, expected_type: ::DesignManagement::DesignAtVersion)
+ dav = GitlabSchema.find_by_gid(id)
return unless consistent?(dav)
dav
@@ -65,8 +73,8 @@ module Resolvers
dav.design.visible_in?(version)
end
- def find(id, filename)
- ids = [parse_design_id(id).model_id] if id
+ def find(gid, filename)
+ ids = [gid.model_id] if gid
filenames = [filename] if filename
::DesignManagement::DesignsFinder
@@ -74,10 +82,6 @@ module Resolvers
.execute
end
- def parse_design_id(id)
- GitlabSchema.parse_gid(id, expected_type: ::DesignManagement::Design)
- end
-
def issue
version.issue
end
diff --git a/app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb b/app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb
index 5ccb2f3e311..a129d8620d4 100644
--- a/app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb
+++ b/app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb
@@ -11,8 +11,9 @@ module Resolvers
authorize :read_design
- argument :ids,
- [GraphQL::ID_TYPE],
+ DesignID = ::Types::GlobalIDType[::DesignManagement::Design]
+
+ argument :ids, [DesignID],
required: false,
description: 'Filters designs by their ID'
argument :filenames,
@@ -31,16 +32,19 @@ module Resolvers
private
def find(ids, filenames)
- ids = ids&.map { |id| parse_design_id(id).model_id }
-
::DesignManagement::DesignsFinder.new(issue, current_user,
- ids: ids,
+ ids: design_ids(ids),
filenames: filenames,
visible_at_version: version)
end
- def parse_design_id(id)
- GitlabSchema.parse_gid(id, expected_type: ::DesignManagement::Design)
+ def design_ids(gids)
+ return if gids.nil?
+
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ gids = gids.map { |id| DesignID.coerce_isolated_input(id) }
+ gids.map(&:model_id)
end
def issue
diff --git a/app/graphql/resolvers/design_management/version_in_collection_resolver.rb b/app/graphql/resolvers/design_management/version_in_collection_resolver.rb
index 9e729172881..ecd7ab3ee45 100644
--- a/app/graphql/resolvers/design_management/version_in_collection_resolver.rb
+++ b/app/graphql/resolvers/design_management/version_in_collection_resolver.rb
@@ -11,20 +11,25 @@ module Resolvers
alias_method :collection, :object
+ VersionID = ::Types::GlobalIDType[::DesignManagement::Version]
+
argument :sha, GraphQL::STRING_TYPE,
required: false,
description: "The SHA256 of a specific version"
- argument :id, GraphQL::ID_TYPE,
+ argument :id, VersionID,
+ as: :version_id,
required: false,
description: 'The Global ID of the version'
- def resolve(id: nil, sha: nil)
- check_args(id, sha)
+ def resolve(version_id: nil, sha: nil)
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ version_id &&= VersionID.coerce_isolated_input(version_id)
- gid = GitlabSchema.parse_gid(id, expected_type: ::DesignManagement::Version) if id
+ check_args(version_id, sha)
::DesignManagement::VersionsFinder
- .new(collection, current_user, sha: sha, version_id: gid&.model_id)
+ .new(collection, current_user, sha: sha, version_id: version_id&.model_id)
.execute
.first
end
diff --git a/app/graphql/resolvers/design_management/version_resolver.rb b/app/graphql/resolvers/design_management/version_resolver.rb
index b0e0843e6c8..1bc9c1a7cd6 100644
--- a/app/graphql/resolvers/design_management/version_resolver.rb
+++ b/app/graphql/resolvers/design_management/version_resolver.rb
@@ -9,7 +9,7 @@ module Resolvers
authorize :read_design
- argument :id, GraphQL::ID_TYPE,
+ argument :id, ::Types::GlobalIDType[::DesignManagement::Version],
required: true,
description: 'The Global ID of the version'
@@ -18,7 +18,11 @@ module Resolvers
end
def find_object(id:)
- GitlabSchema.object_from_id(id, expected_type: ::DesignManagement::Version)
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ id = ::Types::GlobalIDType[::DesignManagement::Version].coerce_isolated_input(id)
+
+ GitlabSchema.find_by_gid(id)
end
end
end
diff --git a/app/graphql/resolvers/design_management/versions_resolver.rb b/app/graphql/resolvers/design_management/versions_resolver.rb
index a62258dad5c..23858c8e991 100644
--- a/app/graphql/resolvers/design_management/versions_resolver.rb
+++ b/app/graphql/resolvers/design_management/versions_resolver.rb
@@ -7,12 +7,14 @@ module Resolvers
alias_method :design_or_collection, :object
+ VersionID = ::Types::GlobalIDType[::DesignManagement::Version]
+
argument :earlier_or_equal_to_sha, GraphQL::STRING_TYPE,
as: :sha,
required: false,
description: 'The SHA256 of the most recent acceptable version'
- argument :earlier_or_equal_to_id, GraphQL::ID_TYPE,
+ argument :earlier_or_equal_to_id, VersionID,
as: :id,
required: false,
description: 'The Global ID of the most recent acceptable version'
@@ -23,6 +25,9 @@ module Resolvers
end
def resolve(parent: nil, id: nil, sha: nil)
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ id &&= VersionID.coerce_isolated_input(id)
version = cutoff(parent, id, sha)
raise ::Gitlab::Graphql::Errors::ResourceNotAvailable, 'cutoff not found' unless version.present?
@@ -47,8 +52,7 @@ module Resolvers
end
end
- def specific_version(id, sha)
- gid = GitlabSchema.parse_gid(id, expected_type: ::DesignManagement::Version) if id
+ def specific_version(gid, sha)
find(sha: sha, version_id: gid&.model_id).first
end
@@ -58,8 +62,8 @@ module Resolvers
.execute
end
- def by_id(id)
- GitlabSchema.object_from_id(id, expected_type: ::DesignManagement::Version).sync
+ def by_id(gid)
+ ::Gitlab::Graphql::Lazy.force(GitlabSchema.find_by_gid(gid))
end
# Find an `at_version` argument passed to a parent node.
@@ -69,7 +73,11 @@ module Resolvers
# for consistency we should only present versions up to the given
# version here.
def at_version_arg(parent)
- ::Gitlab::Graphql::FindArgumentInParent.find(parent, :at_version, limit_depth: 4)
+ # TODO: remove coercion when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ version_id = ::Gitlab::Graphql::FindArgumentInParent.find(parent, :at_version, limit_depth: 4)
+ version_id &&= VersionID.coerce_isolated_input(version_id)
+ version_id
end
end
end
diff --git a/app/graphql/resolvers/echo_resolver.rb b/app/graphql/resolvers/echo_resolver.rb
index fe0b1893a23..6b85b700712 100644
--- a/app/graphql/resolvers/echo_resolver.rb
+++ b/app/graphql/resolvers/echo_resolver.rb
@@ -2,15 +2,16 @@
module Resolvers
class EchoResolver < BaseResolver
+ type ::GraphQL::STRING_TYPE, null: false
description 'Testing endpoint to validate the API with'
argument :text, GraphQL::STRING_TYPE, required: true,
description: 'Text to echo back'
- def resolve(**args)
- username = context[:current_user]&.username
+ def resolve(text:)
+ username = current_user&.username
- "#{username.inspect} says: #{args[:text]}"
+ "#{username.inspect} says: #{text}"
end
end
end
diff --git a/app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb b/app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb
index 5027403e95c..09e76dba645 100644
--- a/app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb
+++ b/app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb
@@ -3,19 +3,22 @@
module Resolvers
module ErrorTracking
class SentryDetailedErrorResolver < BaseResolver
- argument :id, GraphQL::ID_TYPE,
+ type Types::ErrorTracking::SentryDetailedErrorType, null: true
+
+ argument :id, ::Types::GlobalIDType[::Gitlab::ErrorTracking::DetailedError],
required: true,
description: 'ID of the Sentry issue'
- def resolve(**args)
- current_user = context[:current_user]
- issue_id = GlobalID.parse(args[:id])&.model_id
+ def resolve(id:)
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ id = ::Types::GlobalIDType[::Gitlab::ErrorTracking::DetailedError].coerce_isolated_input(id)
# Get data from Sentry
response = ::ErrorTracking::IssueDetailsService.new(
project,
current_user,
- { issue_id: issue_id }
+ { issue_id: id.model_id }
).execute
issue = response[:issue]
issue.gitlab_project = project if issue
diff --git a/app/graphql/resolvers/error_tracking/sentry_error_collection_resolver.rb b/app/graphql/resolvers/error_tracking/sentry_error_collection_resolver.rb
index e4b4854c273..d47cc2bae56 100644
--- a/app/graphql/resolvers/error_tracking/sentry_error_collection_resolver.rb
+++ b/app/graphql/resolvers/error_tracking/sentry_error_collection_resolver.rb
@@ -3,6 +3,8 @@
module Resolvers
module ErrorTracking
class SentryErrorCollectionResolver < BaseResolver
+ type Types::ErrorTracking::SentryErrorCollectionType, null: true
+
def resolve(**args)
project = object
diff --git a/app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb b/app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb
index c365baaf475..669b487db10 100644
--- a/app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb
+++ b/app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb
@@ -3,18 +3,20 @@
module Resolvers
module ErrorTracking
class SentryErrorStackTraceResolver < BaseResolver
- argument :id, GraphQL::ID_TYPE,
+ argument :id, ::Types::GlobalIDType[::Gitlab::ErrorTracking::DetailedError],
required: true,
description: 'ID of the Sentry issue'
- def resolve(**args)
- issue_id = GlobalID.parse(args[:id])&.model_id
+ def resolve(id:)
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ id = ::Types::GlobalIDType[::Gitlab::ErrorTracking::DetailedError].coerce_isolated_input(id)
# Get data from Sentry
response = ::ErrorTracking::IssueLatestEventService.new(
project,
current_user,
- { issue_id: issue_id }
+ { issue_id: id.model_id }
).execute
event = response[:latest_event]
diff --git a/app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb b/app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb
index 79f99709505..c5cf924ce7f 100644
--- a/app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb
+++ b/app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb
@@ -3,6 +3,8 @@
module Resolvers
module ErrorTracking
class SentryErrorsResolver < BaseResolver
+ type Types::ErrorTracking::SentryErrorType.connection_type, null: true
+
def resolve(**args)
args[:cursor] = args.delete(:after)
project = object.project
diff --git a/app/graphql/resolvers/group_issues_resolver.rb b/app/graphql/resolvers/group_issues_resolver.rb
index 1fa6c78e730..1db0ab08e31 100644
--- a/app/graphql/resolvers/group_issues_resolver.rb
+++ b/app/graphql/resolvers/group_issues_resolver.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# rubocop:disable Graphql/ResolverType (inherited from IssuesResolver)
module Resolvers
class GroupIssuesResolver < IssuesResolver
diff --git a/app/graphql/resolvers/group_members_resolver.rb b/app/graphql/resolvers/group_members_resolver.rb
index f34c873a8a9..d3aa376c29c 100644
--- a/app/graphql/resolvers/group_members_resolver.rb
+++ b/app/graphql/resolvers/group_members_resolver.rb
@@ -2,6 +2,8 @@
module Resolvers
class GroupMembersResolver < MembersResolver
+ type Types::GroupMemberType.connection_type, null: true
+
authorize :read_group_member
private
diff --git a/app/graphql/resolvers/group_merge_requests_resolver.rb b/app/graphql/resolvers/group_merge_requests_resolver.rb
index 5ee72e3f781..2bad974daf7 100644
--- a/app/graphql/resolvers/group_merge_requests_resolver.rb
+++ b/app/graphql/resolvers/group_merge_requests_resolver.rb
@@ -6,6 +6,8 @@ module Resolvers
alias_method :group, :synchronized_object
+ type Types::MergeRequestType.connection_type, null: true
+
include_subgroups 'merge requests'
accept_assignee
accept_author
diff --git a/app/graphql/resolvers/group_milestones_resolver.rb b/app/graphql/resolvers/group_milestones_resolver.rb
index 8d34cea4fa1..83b82e2720b 100644
--- a/app/graphql/resolvers/group_milestones_resolver.rb
+++ b/app/graphql/resolvers/group_milestones_resolver.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# rubocop:disable Graphql/ResolverType (inherited from MilestonesResolver)
module Resolvers
class GroupMilestonesResolver < MilestonesResolver
@@ -6,6 +7,8 @@ module Resolvers
required: false,
description: 'Also return milestones in all subgroups and subprojects'
+ type Types::MilestoneType.connection_type, null: true
+
private
def parent_id_parameters(args)
diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb
index 396ae02ae13..dd35219454f 100644
--- a/app/graphql/resolvers/issues_resolver.rb
+++ b/app/graphql/resolvers/issues_resolver.rb
@@ -12,7 +12,7 @@ module Resolvers
required: false,
default_value: 'created_desc'
- type Types::IssueType, null: true
+ type Types::IssueType.connection_type, null: true
NON_STABLE_CURSOR_SORTS = %i[priority_asc priority_desc
label_priority_asc label_priority_desc
diff --git a/app/graphql/resolvers/members_resolver.rb b/app/graphql/resolvers/members_resolver.rb
index 88a1ab71c45..523642e912f 100644
--- a/app/graphql/resolvers/members_resolver.rb
+++ b/app/graphql/resolvers/members_resolver.rb
@@ -5,6 +5,8 @@ module Resolvers
include Gitlab::Graphql::Authorize::AuthorizeResource
include LooksAhead
+ type Types::MemberInterface.connection_type, null: true
+
argument :search, GraphQL::STRING_TYPE,
required: false,
description: 'Search query'
diff --git a/app/graphql/resolvers/merge_request_pipelines_resolver.rb b/app/graphql/resolvers/merge_request_pipelines_resolver.rb
index b95e46d9cff..6590dfdc78c 100644
--- a/app/graphql/resolvers/merge_request_pipelines_resolver.rb
+++ b/app/graphql/resolvers/merge_request_pipelines_resolver.rb
@@ -1,7 +1,9 @@
# frozen_string_literal: true
+# rubocop: disable Graphql/ResolverType
module Resolvers
class MergeRequestPipelinesResolver < BaseResolver
+ # The GraphQL type here gets defined in this include
include ::ResolvesPipelines
alias_method :merge_request, :object
@@ -18,3 +20,4 @@ module Resolvers
end
end
end
+# rubocop: enable Graphql/ResolverType
diff --git a/app/graphql/resolvers/merge_request_resolver.rb b/app/graphql/resolvers/merge_request_resolver.rb
index a47a128ea32..4cad65fa697 100644
--- a/app/graphql/resolvers/merge_request_resolver.rb
+++ b/app/graphql/resolvers/merge_request_resolver.rb
@@ -6,6 +6,8 @@ module Resolvers
alias_method :project, :synchronized_object
+ type ::Types::MergeRequestType, null: true
+
argument :iid, GraphQL::STRING_TYPE,
required: true,
as: :iids,
diff --git a/app/graphql/resolvers/metadata_resolver.rb b/app/graphql/resolvers/metadata_resolver.rb
index 3a79e6434fb..26bfa81038c 100644
--- a/app/graphql/resolvers/metadata_resolver.rb
+++ b/app/graphql/resolvers/metadata_resolver.rb
@@ -5,7 +5,7 @@ module Resolvers
type Types::MetadataType, null: false
def resolve(**args)
- { version: Gitlab::VERSION, revision: Gitlab.revision }
+ ::InstanceMetadata.new
end
end
end
diff --git a/app/graphql/resolvers/milestones_resolver.rb b/app/graphql/resolvers/milestones_resolver.rb
index 84712b674db..564e388d571 100644
--- a/app/graphql/resolvers/milestones_resolver.rb
+++ b/app/graphql/resolvers/milestones_resolver.rb
@@ -25,7 +25,7 @@ module Resolvers
required: false,
description: 'A date that the milestone contains'
- type Types::MilestoneType, null: true
+ type Types::MilestoneType.connection_type, null: true
def resolve(**args)
validate_timeframe_params!(args)
diff --git a/app/graphql/resolvers/namespace_projects_resolver.rb b/app/graphql/resolvers/namespace_projects_resolver.rb
index c221cb9aed6..9f57c8f3405 100644
--- a/app/graphql/resolvers/namespace_projects_resolver.rb
+++ b/app/graphql/resolvers/namespace_projects_resolver.rb
@@ -23,7 +23,6 @@ module Resolvers
# The namespace could have been loaded in batch by `BatchLoader`.
# At this point we need the `id` or the `full_path` of the namespace
# to query for projects, so make sure it's loaded and not `nil` before continuing.
- namespace = object.respond_to?(:sync) ? object.sync : object
return Project.none if namespace.nil?
query = include_subgroups ? namespace.all_projects.with_route : namespace.projects.with_route
@@ -41,6 +40,14 @@ module Resolvers
complexity = super
complexity + 10
end
+
+ private
+
+ def namespace
+ strong_memoize(:namespace) do
+ object.respond_to?(:sync) ? object.sync : object
+ end
+ end
end
end
diff --git a/app/graphql/resolvers/project_members_resolver.rb b/app/graphql/resolvers/project_members_resolver.rb
index 1ca4e81f397..e64e8b845a5 100644
--- a/app/graphql/resolvers/project_members_resolver.rb
+++ b/app/graphql/resolvers/project_members_resolver.rb
@@ -1,9 +1,8 @@
# frozen_string_literal: true
+# rubocop:disable Graphql/ResolverType (inherited from MembersResolver)
module Resolvers
class ProjectMembersResolver < MembersResolver
- type Types::MemberInterface, null: true
-
authorize :read_project_member
private
diff --git a/app/graphql/resolvers/project_merge_requests_resolver.rb b/app/graphql/resolvers/project_merge_requests_resolver.rb
index ba13cb6e52c..bf082c0b182 100644
--- a/app/graphql/resolvers/project_merge_requests_resolver.rb
+++ b/app/graphql/resolvers/project_merge_requests_resolver.rb
@@ -2,6 +2,7 @@
module Resolvers
class ProjectMergeRequestsResolver < MergeRequestsResolver
+ type ::Types::MergeRequestType.connection_type, null: true
accept_assignee
accept_author
end
diff --git a/app/graphql/resolvers/project_milestones_resolver.rb b/app/graphql/resolvers/project_milestones_resolver.rb
index 976fc300b87..c88c9ce7219 100644
--- a/app/graphql/resolvers/project_milestones_resolver.rb
+++ b/app/graphql/resolvers/project_milestones_resolver.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# rubocop:disable Graphql/ResolverType (inherited from MilestonesResolver)
module Resolvers
class ProjectMilestonesResolver < MilestonesResolver
@@ -6,6 +7,8 @@ module Resolvers
required: false,
description: "Also return milestones in the project's parent group and its ancestors"
+ type Types::MilestoneType.connection_type, null: true
+
private
def parent_id_parameters(args)
diff --git a/app/graphql/resolvers/project_pipeline_resolver.rb b/app/graphql/resolvers/project_pipeline_resolver.rb
index 181c1e77109..4cf47dbdc60 100644
--- a/app/graphql/resolvers/project_pipeline_resolver.rb
+++ b/app/graphql/resolvers/project_pipeline_resolver.rb
@@ -2,6 +2,8 @@
module Resolvers
class ProjectPipelineResolver < BaseResolver
+ type ::Types::Ci::PipelineType, null: true
+
alias_method :project, :object
argument :iid, GraphQL::ID_TYPE,
diff --git a/app/graphql/resolvers/project_pipelines_resolver.rb b/app/graphql/resolvers/project_pipelines_resolver.rb
index 86094c46c2a..0171473a77f 100644
--- a/app/graphql/resolvers/project_pipelines_resolver.rb
+++ b/app/graphql/resolvers/project_pipelines_resolver.rb
@@ -1,13 +1,28 @@
# frozen_string_literal: true
+# The GraphQL type here gets defined in
+# https://gitlab.com/gitlab-org/gitlab/blob/master/app/graphql/resolvers/concerns/resolves_pipelines.rb#L7
+# rubocop: disable Graphql/ResolverType
module Resolvers
class ProjectPipelinesResolver < BaseResolver
+ include LooksAhead
include ResolvesPipelines
alias_method :project, :object
- def resolve(**args)
- resolve_pipelines(project, args)
+ def resolve_with_lookahead(**args)
+ apply_lookahead(resolve_pipelines(project, args))
+ end
+
+ private
+
+ def preloads
+ {
+ jobs: [:statuses],
+ upstream: [:triggered_by_pipeline],
+ downstream: [:triggered_pipelines]
+ }
end
end
end
+# rubocop: enable Graphql/ResolverType
diff --git a/app/graphql/resolvers/projects/jira_imports_resolver.rb b/app/graphql/resolvers/projects/jira_imports_resolver.rb
index aa9b7139f38..efd45c2c465 100644
--- a/app/graphql/resolvers/projects/jira_imports_resolver.rb
+++ b/app/graphql/resolvers/projects/jira_imports_resolver.rb
@@ -3,6 +3,8 @@
module Resolvers
module Projects
class JiraImportsResolver < BaseResolver
+ type Types::JiraImportType.connection_type, null: true
+
include Gitlab::Graphql::Authorize::AuthorizeResource
alias_method :project, :object
diff --git a/app/graphql/resolvers/projects/jira_projects_resolver.rb b/app/graphql/resolvers/projects/jira_projects_resolver.rb
index d017f973e17..31f42d305b0 100644
--- a/app/graphql/resolvers/projects/jira_projects_resolver.rb
+++ b/app/graphql/resolvers/projects/jira_projects_resolver.rb
@@ -5,6 +5,8 @@ module Resolvers
class JiraProjectsResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
+ type Types::Projects::Services::JiraProjectType.connection_type, null: true
+
argument :name,
GraphQL::STRING_TYPE,
required: false,
diff --git a/app/graphql/resolvers/projects/services_resolver.rb b/app/graphql/resolvers/projects/services_resolver.rb
index 40c64c24513..17d81e21c28 100644
--- a/app/graphql/resolvers/projects/services_resolver.rb
+++ b/app/graphql/resolvers/projects/services_resolver.rb
@@ -5,6 +5,8 @@ module Resolvers
class ServicesResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
+ type Types::Projects::ServiceType.connection_type, null: true
+
argument :active,
GraphQL::BOOLEAN_TYPE,
required: false,
diff --git a/app/graphql/resolvers/projects/snippets_resolver.rb b/app/graphql/resolvers/projects/snippets_resolver.rb
index 22895a24054..448918be2f5 100644
--- a/app/graphql/resolvers/projects/snippets_resolver.rb
+++ b/app/graphql/resolvers/projects/snippets_resolver.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# rubocop:disable Graphql/ResolverType (inherited from ResolvesSnippets)
module Resolvers
module Projects
diff --git a/app/graphql/resolvers/releases_resolver.rb b/app/graphql/resolvers/releases_resolver.rb
index 85892c2abeb..8e8127cf279 100644
--- a/app/graphql/resolvers/releases_resolver.rb
+++ b/app/graphql/resolvers/releases_resolver.rb
@@ -4,6 +4,10 @@ module Resolvers
class ReleasesResolver < BaseResolver
type Types::ReleaseType.connection_type, null: true
+ argument :sort, Types::ReleaseSortEnum,
+ required: false, default_value: :released_at_desc,
+ description: 'Sort releases by this criteria'
+
alias_method :project, :object
# This resolver has a custom singular resolver
@@ -11,12 +15,20 @@ module Resolvers
Resolvers::ReleaseResolver
end
- def resolve(**args)
+ SORT_TO_PARAMS_MAP = {
+ released_at_desc: { order_by: 'released_at', sort: 'desc' },
+ released_at_asc: { order_by: 'released_at', sort: 'asc' },
+ created_desc: { order_by: 'created_at', sort: 'desc' },
+ created_asc: { order_by: 'created_at', sort: 'asc' }
+ }.freeze
+
+ def resolve(sort:)
return unless Feature.enabled?(:graphql_release_data, project, default_enabled: true)
ReleasesFinder.new(
project,
- current_user
+ current_user,
+ SORT_TO_PARAMS_MAP[sort]
).execute
end
end
diff --git a/app/graphql/resolvers/snippets/blobs_resolver.rb b/app/graphql/resolvers/snippets/blobs_resolver.rb
index dc28358cab6..3a0dcb50faf 100644
--- a/app/graphql/resolvers/snippets/blobs_resolver.rb
+++ b/app/graphql/resolvers/snippets/blobs_resolver.rb
@@ -5,6 +5,8 @@ module Resolvers
class BlobsResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
+ type Types::Snippets::BlobType.connection_type, null: true
+
alias_method :snippet, :object
argument :paths, [GraphQL::STRING_TYPE],
diff --git a/app/graphql/resolvers/snippets_resolver.rb b/app/graphql/resolvers/snippets_resolver.rb
index 530a288a25b..77099565df0 100644
--- a/app/graphql/resolvers/snippets_resolver.rb
+++ b/app/graphql/resolvers/snippets_resolver.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# rubocop:disable Graphql/ResolverType (inherited from ResolvesSnippets)
module Resolvers
class SnippetsResolver < BaseResolver
@@ -8,11 +9,11 @@ module Resolvers
alias_method :user, :object
- argument :author_id, GraphQL::ID_TYPE,
+ argument :author_id, ::Types::GlobalIDType[::User],
required: false,
description: 'The ID of an author'
- argument :project_id, GraphQL::ID_TYPE,
+ argument :project_id, ::Types::GlobalIDType[::Project],
required: false,
description: 'The ID of a project'
@@ -36,9 +37,11 @@ module Resolvers
private
def snippet_finder_params(args)
+ # TODO: remove the type arguments when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
super
- .merge(author: resolve_gid(args[:author_id], :author),
- project: resolve_gid(args[:project_id], :project),
+ .merge(author: resolve_ids(args[:author_id], ::Types::GlobalIDType[::User]),
+ project: resolve_ids(args[:project_id], ::Types::GlobalIDType[::Project]),
explore: args[:explore])
end
end
diff --git a/app/graphql/resolvers/todo_resolver.rb b/app/graphql/resolvers/todo_resolver.rb
index bd5f8f274cd..9a8f7a71154 100644
--- a/app/graphql/resolvers/todo_resolver.rb
+++ b/app/graphql/resolvers/todo_resolver.rb
@@ -2,7 +2,7 @@
module Resolvers
class TodoResolver < BaseResolver
- type Types::TodoType, null: true
+ type Types::TodoType.connection_type, null: true
alias_method :target, :object
diff --git a/app/graphql/resolvers/tree_resolver.rb b/app/graphql/resolvers/tree_resolver.rb
index 5aad1c71b40..075a1929c47 100644
--- a/app/graphql/resolvers/tree_resolver.rb
+++ b/app/graphql/resolvers/tree_resolver.rb
@@ -2,6 +2,8 @@
module Resolvers
class TreeResolver < BaseResolver
+ type Types::Tree::TreeType, null: true
+
argument :path, GraphQL::STRING_TYPE,
required: false,
default_value: '',
diff --git a/app/graphql/resolvers/user_merge_requests_resolver.rb b/app/graphql/resolvers/user_merge_requests_resolver_base.rb
index b0d6e159f73..47967fe69f9 100644
--- a/app/graphql/resolvers/user_merge_requests_resolver.rb
+++ b/app/graphql/resolvers/user_merge_requests_resolver_base.rb
@@ -1,14 +1,14 @@
# frozen_string_literal: true
module Resolvers
- class UserMergeRequestsResolver < MergeRequestsResolver
+ class UserMergeRequestsResolverBase < MergeRequestsResolver
include ResolvesProject
argument :project_path, GraphQL::STRING_TYPE,
required: false,
description: 'The full-path of the project the authored merge requests should be in. Incompatible with projectId.'
- argument :project_id, GraphQL::ID_TYPE,
+ argument :project_id, ::Types::GlobalIDType[::Project],
required: false,
description: 'The global ID of the project the authored merge requests should be in. Incompatible with projectPath.'
@@ -50,8 +50,10 @@ module Resolvers
end
def load_project(project_path, project_id)
- @project = resolve_project(full_path: project_path, project_id: project_id)
- @project = @project.sync if @project.respond_to?(:sync)
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ project_id &&= ::Types::GlobalIDType[::Project].coerce_isolated_input(project_id)
+ @project = ::Gitlab::Graphql::Lazy.force(resolve_project(full_path: project_path, project_id: project_id))
end
def no_results_possible?(args)
diff --git a/app/graphql/resolvers/user_resolver.rb b/app/graphql/resolvers/user_resolver.rb
index a34cecba491..06c1f0cb42d 100644
--- a/app/graphql/resolvers/user_resolver.rb
+++ b/app/graphql/resolvers/user_resolver.rb
@@ -6,7 +6,7 @@ module Resolvers
type Types::UserType, null: true
- argument :id, GraphQL::ID_TYPE,
+ argument :id, Types::GlobalIDType[User],
required: false,
description: 'ID of the User'
diff --git a/app/graphql/resolvers/users/group_count_resolver.rb b/app/graphql/resolvers/users/group_count_resolver.rb
new file mode 100644
index 00000000000..5033c26554a
--- /dev/null
+++ b/app/graphql/resolvers/users/group_count_resolver.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Users
+ class GroupCountResolver < BaseResolver
+ alias_method :user, :object
+
+ def resolve(**args)
+ return unless can_read_group_count?
+
+ BatchLoader::GraphQL.for(user.id).batch do |user_ids, loader|
+ results = UserGroupsCounter.new(user_ids).execute
+
+ results.each do |user_id, count|
+ loader.call(user_id, count)
+ end
+ end
+ end
+
+ def can_read_group_count?
+ current_user&.can?(:read_group_count, user)
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/users/snippets_resolver.rb b/app/graphql/resolvers/users/snippets_resolver.rb
index d757640b5ff..c2d42437ffd 100644
--- a/app/graphql/resolvers/users/snippets_resolver.rb
+++ b/app/graphql/resolvers/users/snippets_resolver.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+# rubocop:disable Graphql/ResolverType (inherited from ResolvesSnippets)
module Resolvers
module Users
diff --git a/app/graphql/resolvers/users_resolver.rb b/app/graphql/resolvers/users_resolver.rb
index 110a283b42e..f5838642141 100644
--- a/app/graphql/resolvers/users_resolver.rb
+++ b/app/graphql/resolvers/users_resolver.rb
@@ -4,6 +4,7 @@ module Resolvers
class UsersResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
+ type Types::UserType.connection_type, null: true
description 'Find Users'
argument :ids, [GraphQL::ID_TYPE],
@@ -18,10 +19,14 @@ module Resolvers
required: false,
default_value: 'created_desc'
- def resolve(ids: nil, usernames: nil, sort: nil)
+ argument :search, GraphQL::STRING_TYPE,
+ required: false,
+ description: "Query to search users by name, username, or primary email."
+
+ def resolve(ids: nil, usernames: nil, sort: nil, search: nil)
authorize!
- ::UsersFinder.new(context[:current_user], finder_params(ids, usernames, sort)).execute
+ ::UsersFinder.new(context[:current_user], finder_params(ids, usernames, sort, search)).execute
end
def ready?(**args)
@@ -42,11 +47,12 @@ module Resolvers
private
- def finder_params(ids, usernames, sort)
+ def finder_params(ids, usernames, sort, search)
params = {}
params[:sort] = sort if sort
params[:username] = usernames if usernames
params[:id] = parse_gids(ids) if ids
+ params[:search] = search if search
params
end
diff --git a/app/graphql/types/alert_management/http_integration_type.rb b/app/graphql/types/alert_management/http_integration_type.rb
new file mode 100644
index 00000000000..88782050b94
--- /dev/null
+++ b/app/graphql/types/alert_management/http_integration_type.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Types
+ module AlertManagement
+ class HttpIntegrationType < BaseObject
+ graphql_name 'AlertManagementHttpIntegration'
+ description 'An endpoint and credentials used to accept alerts for a project'
+
+ implements(Types::AlertManagement::IntegrationType)
+
+ authorize :admin_operations
+
+ def type
+ :http
+ end
+
+ def api_url
+ nil
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/alert_management/integration_type.rb b/app/graphql/types/alert_management/integration_type.rb
new file mode 100644
index 00000000000..bf599885584
--- /dev/null
+++ b/app/graphql/types/alert_management/integration_type.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module Types
+ module AlertManagement
+ module IntegrationType
+ include Types::BaseInterface
+ graphql_name 'AlertManagementIntegration'
+
+ field :id,
+ GraphQL::ID_TYPE,
+ null: false,
+ description: 'ID of the integration'
+
+ field :type,
+ AlertManagement::IntegrationTypeEnum,
+ null: false,
+ description: 'Type of integration'
+
+ field :name,
+ GraphQL::STRING_TYPE,
+ null: true,
+ description: 'Name of the integration'
+
+ field :active,
+ GraphQL::BOOLEAN_TYPE,
+ null: true,
+ description: 'Whether the endpoint is currently accepting alerts'
+
+ field :token,
+ GraphQL::STRING_TYPE,
+ null: true,
+ description: 'Token used to authenticate alert notification requests'
+
+ field :url,
+ GraphQL::STRING_TYPE,
+ null: true,
+ description: 'Endpoint which accepts alert notifications'
+
+ field :api_url,
+ GraphQL::STRING_TYPE,
+ null: true,
+ description: 'URL at which Prometheus metrics can be queried to populate the metrics dashboard'
+
+ definition_methods do
+ def resolve_type(object, context)
+ if object.is_a?(::PrometheusService)
+ Types::AlertManagement::PrometheusIntegrationType
+ else
+ Types::AlertManagement::HttpIntegrationType
+ end
+ end
+ end
+
+ orphan_types Types::AlertManagement::PrometheusIntegrationType,
+ Types::AlertManagement::HttpIntegrationType
+ end
+ end
+end
diff --git a/app/graphql/types/alert_management/integration_type_enum.rb b/app/graphql/types/alert_management/integration_type_enum.rb
new file mode 100644
index 00000000000..2f9be549e58
--- /dev/null
+++ b/app/graphql/types/alert_management/integration_type_enum.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Types
+ module AlertManagement
+ class IntegrationTypeEnum < BaseEnum
+ graphql_name 'AlertManagementIntegrationType'
+ description 'Values of types of integrations'
+
+ value 'PROMETHEUS', 'Prometheus integration', value: :prometheus
+ value 'HTTP', 'Integration with any monitoring tool', value: :http
+ end
+ end
+end
diff --git a/app/graphql/types/alert_management/prometheus_integration_type.rb b/app/graphql/types/alert_management/prometheus_integration_type.rb
new file mode 100644
index 00000000000..f605e325b8b
--- /dev/null
+++ b/app/graphql/types/alert_management/prometheus_integration_type.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Types
+ module AlertManagement
+ class PrometheusIntegrationType < BaseObject
+ include ::Gitlab::Routing
+
+ graphql_name 'AlertManagementPrometheusIntegration'
+ description 'An endpoint and credentials used to accept Prometheus alerts for a project'
+
+ implements(Types::AlertManagement::IntegrationType)
+
+ authorize :admin_project
+
+ alias_method :prometheus_service, :object
+
+ def name
+ prometheus_service.title
+ end
+
+ def type
+ :prometheus
+ end
+
+ def token
+ prometheus_service.project&.alerting_setting&.token
+ end
+
+ def url
+ prometheus_service.project && notify_project_prometheus_alerts_url(prometheus_service.project, format: :json)
+ end
+
+ def active
+ prometheus_service.manual_configuration?
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/availability_enum.rb b/app/graphql/types/availability_enum.rb
new file mode 100644
index 00000000000..61686b9359f
--- /dev/null
+++ b/app/graphql/types/availability_enum.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Types
+ class AvailabilityEnum < BaseEnum
+ graphql_name 'AvailabilityEnum'
+ description 'User availability status'
+
+ ::UserStatus.availabilities.keys.each do |availability_value|
+ value availability_value.upcase, value: availability_value, description: availability_value.titleize
+ end
+ end
+end
diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb
index 70e665f8fc3..9c36c83d4a3 100644
--- a/app/graphql/types/base_object.rb
+++ b/app/graphql/types/base_object.rb
@@ -8,6 +8,12 @@ module Types
field_class Types::BaseField
+ def self.accepts(*types)
+ @accepts ||= []
+ @accepts += types
+ @accepts
+ end
+
# All graphql fields exposing an id, should expose a global id.
def id
GitlabSchema.id_from_object(object)
@@ -16,5 +22,13 @@ module Types
def current_user
context[:current_user]
end
+
+ def self.assignable?(object)
+ assignable = accepts
+
+ return true if assignable.blank?
+
+ assignable.any? { |cls| object.is_a?(cls) }
+ end
end
end
diff --git a/app/graphql/types/board_type.rb b/app/graphql/types/board_type.rb
index f5dc9e08427..2a7b318e283 100644
--- a/app/graphql/types/board_type.rb
+++ b/app/graphql/types/board_type.rb
@@ -4,7 +4,7 @@ module Types
class BoardType < BaseObject
graphql_name 'Board'
description 'Represents a project or group board'
-
+ accepts ::Board
authorize :read_board
field :id, type: GraphQL::ID_TYPE, null: false,
diff --git a/app/graphql/types/ci/detailed_status_type.rb b/app/graphql/types/ci/detailed_status_type.rb
index f4a50115ee6..6d8af400ac4 100644
--- a/app/graphql/types/ci/detailed_status_type.rb
+++ b/app/graphql/types/ci/detailed_status_type.rb
@@ -30,7 +30,7 @@ module Types
if obj.has_action?
{
button_title: obj.action_button_title,
- icon: obj.icon,
+ icon: obj.action_icon,
method: obj.action_method,
path: obj.action_path,
title: obj.action_title
diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb
index 0ee1ad47b62..feaff4e81d8 100644
--- a/app/graphql/types/ci/job_type.rb
+++ b/app/graphql/types/ci/job_type.rb
@@ -6,6 +6,9 @@ module Types
class JobType < BaseObject
graphql_name 'CiJob'
+ field :pipeline, Types::Ci::PipelineType, null: false,
+ description: 'Pipeline the job belongs to',
+ resolve: -> (build, _, _) { Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Pipeline, build.pipeline_id).find }
field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the job'
field :needs, JobType.connection_type, null: true,
diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb
index c508b746317..c25db39f600 100644
--- a/app/graphql/types/ci/pipeline_type.rb
+++ b/app/graphql/types/ci/pipeline_type.rb
@@ -13,49 +13,89 @@ module Types
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the pipeline'
+
field :iid, GraphQL::STRING_TYPE, null: false,
description: 'Internal ID of the pipeline'
field :sha, GraphQL::STRING_TYPE, null: false,
description: "SHA of the pipeline's commit"
+
field :before_sha, GraphQL::STRING_TYPE, null: true,
description: 'Base SHA of the source branch'
+
field :status, PipelineStatusEnum, null: false,
description: "Status of the pipeline (#{::Ci::Pipeline.all_state_names.compact.join(', ').upcase})"
+
field :detailed_status, Types::Ci::DetailedStatusType, null: false,
description: 'Detailed status of the pipeline',
resolve: -> (obj, _args, ctx) { obj.detailed_status(ctx[:current_user]) }
+
field :config_source, PipelineConfigSourceEnum, null: true,
description: "Config source of the pipeline (#{::Enums::Ci::Pipeline.config_sources.keys.join(', ').upcase})"
+
field :duration, GraphQL::INT_TYPE, null: true,
description: 'Duration of the pipeline in seconds'
+
field :coverage, GraphQL::FLOAT_TYPE, null: true,
description: 'Coverage percentage'
+
field :created_at, Types::TimeType, null: false,
description: "Timestamp of the pipeline's creation"
+
field :updated_at, Types::TimeType, null: false,
description: "Timestamp of the pipeline's last activity"
+
field :started_at, Types::TimeType, null: true,
description: 'Timestamp when the pipeline was started'
+
field :finished_at, Types::TimeType, null: true,
description: "Timestamp of the pipeline's completion"
+
field :committed_at, Types::TimeType, null: true,
description: "Timestamp of the pipeline's commit"
+
field :stages, Types::Ci::StageType.connection_type, null: true,
description: 'Stages of the pipeline',
extras: [:lookahead],
resolver: Resolvers::Ci::PipelineStagesResolver
+
field :user, Types::UserType, null: true,
description: 'Pipeline user',
resolve: -> (pipeline, _args, _context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, pipeline.user_id).find }
+
field :retryable, GraphQL::BOOLEAN_TYPE,
description: 'Specifies if a pipeline can be retried',
method: :retryable?,
null: false
+
field :cancelable, GraphQL::BOOLEAN_TYPE,
description: 'Specifies if a pipeline can be canceled',
method: :cancelable?,
null: false
+
+ field :jobs,
+ ::Types::Ci::JobType.connection_type,
+ null: true,
+ description: 'Jobs belonging to the pipeline',
+ resolver: ::Resolvers::Ci::JobsResolver
+
+ field :source_job, Types::Ci::JobType, null: true,
+ description: 'Job where pipeline was triggered from'
+
+ field :downstream, Types::Ci::PipelineType.connection_type, null: true,
+ description: 'Pipelines this pipeline will trigger',
+ method: :triggered_pipelines_with_preloads
+
+ field :upstream, Types::Ci::PipelineType, null: true,
+ description: 'Pipeline that triggered the pipeline',
+ method: :triggered_by_pipeline
+
+ field :path, GraphQL::STRING_TYPE, null: true,
+ description: "Relative path to the pipeline's page",
+ resolve: -> (obj, _args, _ctx) { ::Gitlab::Routing.url_helpers.project_pipeline_path(obj.project, obj) }
+
+ field :project, Types::ProjectType, null: true,
+ description: 'Project the pipeline belongs to'
end
end
end
diff --git a/app/graphql/types/ci/runner_setup_type.rb b/app/graphql/types/ci/runner_setup_type.rb
new file mode 100644
index 00000000000..66abcf65adf
--- /dev/null
+++ b/app/graphql/types/ci/runner_setup_type.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ # rubocop: disable Graphql/AuthorizeTypes
+ class RunnerSetupType < BaseObject
+ graphql_name 'RunnerSetup'
+
+ field :install_instructions, GraphQL::STRING_TYPE, null: false,
+ description: 'Instructions for installing the runner on the specified architecture'
+ field :register_instructions, GraphQL::STRING_TYPE, null: true,
+ description: 'Instructions for registering the runner'
+ end
+ end
+end
diff --git a/app/graphql/types/commit_type.rb b/app/graphql/types/commit_type.rb
index dd4b4c3b114..c24b47f08ef 100644
--- a/app/graphql/types/commit_type.rb
+++ b/app/graphql/types/commit_type.rb
@@ -40,16 +40,9 @@ module Types
field :author, type: Types::UserType, null: true,
description: 'Author of the commit'
- field :pipelines, Types::Ci::PipelineType.connection_type,
+ field :pipelines,
null: true,
description: 'Pipelines of the commit ordered latest first',
resolver: Resolvers::CommitPipelinesResolver
-
- field :latest_pipeline,
- type: Types::Ci::PipelineType,
- null: true,
- deprecated: { reason: 'Use `pipelines`', milestone: '12.5' },
- description: 'Latest pipeline of the commit',
- resolver: Resolvers::CommitPipelinesResolver.last
end
end
diff --git a/app/graphql/types/container_repository_cleanup_status_enum.rb b/app/graphql/types/container_repository_cleanup_status_enum.rb
new file mode 100644
index 00000000000..6e654e65360
--- /dev/null
+++ b/app/graphql/types/container_repository_cleanup_status_enum.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Types
+ class ContainerRepositoryCleanupStatusEnum < BaseEnum
+ graphql_name 'ContainerRepositoryCleanupStatus'
+ description 'Status of the tags cleanup of a container repository'
+
+ value 'UNSCHEDULED', value: 'cleanup_unscheduled', description: 'The tags cleanup is not scheduled. This is the default state.'
+ value 'SCHEDULED', value: 'cleanup_scheduled', description: 'The tags cleanup is scheduled and is going to be executed shortly.'
+ value 'UNFINISHED', value: 'cleanup_unfinished', description: 'The tags cleanup has been partially executed. There are still remaining tags to delete.'
+ value 'ONGOING', value: 'cleanup_ongoing', description: 'The tags cleanup is ongoing.'
+ end
+end
diff --git a/app/graphql/types/container_repository_details_type.rb b/app/graphql/types/container_repository_details_type.rb
new file mode 100644
index 00000000000..34523f3ea4a
--- /dev/null
+++ b/app/graphql/types/container_repository_details_type.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Types
+ class ContainerRepositoryDetailsType < Types::ContainerRepositoryType
+ graphql_name 'ContainerRepositoryDetails'
+
+ description 'Details of a container repository'
+
+ authorize :read_container_image
+
+ field :tags,
+ Types::ContainerRepositoryTagType.connection_type,
+ null: true,
+ description: 'Tags of the container repository',
+ max_page_size: 20
+
+ def can_delete
+ Ability.allowed?(current_user, :destroy_container_image, object)
+ end
+ end
+end
diff --git a/app/graphql/types/container_repository_status_enum.rb b/app/graphql/types/container_repository_status_enum.rb
new file mode 100644
index 00000000000..8f3ba8f1083
--- /dev/null
+++ b/app/graphql/types/container_repository_status_enum.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Types
+ class ContainerRepositoryStatusEnum < BaseEnum
+ graphql_name 'ContainerRepositoryStatus'
+ description 'Status of a container repository'
+
+ ::ContainerRepository.statuses.keys.each do |status|
+ value status.upcase, value: status, description: "#{status.titleize} status."
+ end
+ end
+end
diff --git a/app/graphql/types/container_repository_tag_type.rb b/app/graphql/types/container_repository_tag_type.rb
new file mode 100644
index 00000000000..25e605b689d
--- /dev/null
+++ b/app/graphql/types/container_repository_tag_type.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Types
+ class ContainerRepositoryTagType < BaseObject
+ graphql_name 'ContainerRepositoryTag'
+
+ description 'A tag from a container repository'
+
+ authorize :read_container_image
+
+ field :name, GraphQL::STRING_TYPE, null: false, description: 'Name of the tag.'
+ field :path, GraphQL::STRING_TYPE, null: false, description: 'Path of the tag.'
+ field :location, GraphQL::STRING_TYPE, null: false, description: 'URL of the tag.'
+ field :digest, GraphQL::STRING_TYPE, null: false, description: 'Digest of the tag.'
+ field :revision, GraphQL::STRING_TYPE, null: false, description: 'Revision of the tag.'
+ field :short_revision, GraphQL::STRING_TYPE, null: false, description: 'Short revision of the tag.'
+ field :total_size, GraphQL::INT_TYPE, null: false, description: 'The size of the tag.'
+ field :created_at, Types::TimeType, null: false, description: 'Timestamp when the tag was created.'
+ field :can_delete, GraphQL::BOOLEAN_TYPE, null: false, description: 'Can the current user delete this tag.'
+
+ def can_delete
+ Ability.allowed?(current_user, :destroy_container_image, object)
+ end
+ end
+end
diff --git a/app/graphql/types/container_repository_type.rb b/app/graphql/types/container_repository_type.rb
new file mode 100644
index 00000000000..45d19fdbc50
--- /dev/null
+++ b/app/graphql/types/container_repository_type.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Types
+ class ContainerRepositoryType < BaseObject
+ graphql_name 'ContainerRepository'
+
+ description 'A container repository'
+
+ authorize :read_container_image
+
+ field :id, GraphQL::ID_TYPE, null: false, description: 'ID of the container repository.'
+ field :name, GraphQL::STRING_TYPE, null: false, description: 'Name of the container repository.'
+ field :path, GraphQL::STRING_TYPE, null: false, description: 'Path of the container repository.'
+ field :location, GraphQL::STRING_TYPE, null: false, description: 'URL of the container repository.'
+ field :created_at, Types::TimeType, null: false, description: 'Timestamp when the container repository was created.'
+ field :updated_at, Types::TimeType, null: false, description: 'Timestamp when the container repository was updated.'
+ field :expiration_policy_started_at, Types::TimeType, null: true, description: 'Timestamp when the cleanup done by the expiration policy was started on the container repository.'
+ field :expiration_policy_cleanup_status, Types::ContainerRepositoryCleanupStatusEnum, null: true, description: 'The tags cleanup status for the container repository.'
+ field :status, Types::ContainerRepositoryStatusEnum, null: true, description: 'Status of the container repository.'
+ field :tags_count, GraphQL::INT_TYPE, null: false, description: 'Number of tags associated with this image.'
+ field :can_delete, GraphQL::BOOLEAN_TYPE, null: false, description: 'Can the current user delete the container repository.'
+
+ def can_delete
+ Ability.allowed?(current_user, :update_container_image, object)
+ end
+ end
+end
diff --git a/app/graphql/types/countable_connection_type.rb b/app/graphql/types/countable_connection_type.rb
index 2538366b786..f67194d99b3 100644
--- a/app/graphql/types/countable_connection_type.rb
+++ b/app/graphql/types/countable_connection_type.rb
@@ -3,7 +3,7 @@
module Types
# rubocop: disable Graphql/AuthorizeTypes
class CountableConnectionType < GraphQL::Types::Relay::BaseConnection
- field :count, Integer, null: false,
+ field :count, GraphQL::INT_TYPE, null: false,
description: 'Total count of collection'
def count
diff --git a/app/graphql/types/custom_emoji_type.rb b/app/graphql/types/custom_emoji_type.rb
new file mode 100644
index 00000000000..f7d1a7800bc
--- /dev/null
+++ b/app/graphql/types/custom_emoji_type.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Types
+ class CustomEmojiType < BaseObject
+ graphql_name 'CustomEmoji'
+ description 'A custom emoji uploaded by user'
+
+ authorize :read_custom_emoji
+
+ field :id, ::Types::GlobalIDType[::CustomEmoji],
+ null: false,
+ description: 'The ID of the emoji'
+
+ field :name, GraphQL::STRING_TYPE,
+ null: false,
+ description: 'The name of the emoji'
+
+ field :url, GraphQL::STRING_TYPE,
+ null: false,
+ method: :file,
+ description: 'The link to file of the emoji'
+
+ field :external, GraphQL::BOOLEAN_TYPE,
+ null: false,
+ description: 'Whether the emoji is an external link'
+ end
+end
diff --git a/app/graphql/types/environment_type.rb b/app/graphql/types/environment_type.rb
index e4631a4a903..e3885668643 100644
--- a/app/graphql/types/environment_type.rb
+++ b/app/graphql/types/environment_type.rb
@@ -18,9 +18,8 @@ module Types
field :state, GraphQL::STRING_TYPE, null: false,
description: 'State of the environment, for example: available/stopped'
- field :path, GraphQL::STRING_TYPE, null: true,
- description: 'The path to the environment. Will always return null ' \
- 'if `expose_environment_path_in_alert_details` feature flag is disabled'
+ field :path, GraphQL::STRING_TYPE, null: false,
+ description: 'The path to the environment.'
field :metrics_dashboard, Types::Metrics::DashboardType, null: true,
description: 'Metrics dashboard schema for the environment',
diff --git a/app/graphql/types/global_id_type.rb b/app/graphql/types/global_id_type.rb
index 9ae9ba32c13..4c51d4248dd 100644
--- a/app/graphql/types/global_id_type.rb
+++ b/app/graphql/types/global_id_type.rb
@@ -30,6 +30,8 @@ module Types
# @param value [String]
# @return [GID]
def self.coerce_input(value, _ctx)
+ return if value.nil?
+
gid = GlobalID.parse(value)
raise GraphQL::CoercionError, "#{value.inspect} is not a valid Global ID" if gid.nil?
raise GraphQL::CoercionError, "#{value.inspect} is not a Gitlab Global ID" unless gid.app == GlobalID.app
diff --git a/app/graphql/types/grafana_integration_type.rb b/app/graphql/types/grafana_integration_type.rb
index 7db733fc62a..6625af36f82 100644
--- a/app/graphql/types/grafana_integration_type.rb
+++ b/app/graphql/types/grafana_integration_type.rb
@@ -16,13 +16,5 @@ module Types
description: 'Timestamp of the issue\'s creation'
field :updated_at, Types::TimeType, null: false,
description: 'Timestamp of the issue\'s last activity'
-
- field :token, GraphQL::STRING_TYPE, null: false,
- deprecated: { reason: 'Plain text token has been masked for security reasons', milestone: '12.7' },
- description: 'API token for the Grafana integration'
-
- def token
- object.masked_token
- end
end
end
diff --git a/app/graphql/types/group_invitation_type.rb b/app/graphql/types/group_invitation_type.rb
new file mode 100644
index 00000000000..0372ce178ff
--- /dev/null
+++ b/app/graphql/types/group_invitation_type.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Types
+ class GroupInvitationType < BaseObject
+ expose_permissions Types::PermissionTypes::Group
+ authorize :read_group
+
+ implements InvitationInterface
+
+ graphql_name 'GroupInvitation'
+ description 'Represents a Group Invitation'
+
+ field :group, Types::GroupType, null: true,
+ description: 'Group that a User is invited to',
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.source_id).find }
+ end
+end
diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb
index 199cc0308c5..fb028184488 100644
--- a/app/graphql/types/group_type.rb
+++ b/app/graphql/types/group_type.rb
@@ -17,6 +17,10 @@ module Types
group.avatar_url(only_path: false)
end
+ field :custom_emoji, Types::CustomEmojiType.connection_type, null: true,
+ description: 'Custom emoji within this namespace',
+ feature_flag: :custom_emoji
+
field :share_with_group_lock, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if sharing a project with another group within this group is prevented'
@@ -82,11 +86,16 @@ module Types
end
field :group_members,
- Types::GroupMemberType.connection_type,
description: 'A membership of a user within this group',
- extras: [:lookahead],
resolver: Resolvers::GroupMembersResolver
+ field :container_repositories,
+ Types::ContainerRepositoryType.connection_type,
+ null: true,
+ description: 'Container repositories of the project',
+ resolver: Resolvers::ContainerRepositoriesResolver,
+ authorize: :read_container_image
+
def label(title:)
BatchLoader::GraphQL.for(title).batch(key: group) do |titles, loader, args|
LabelsFinder
diff --git a/app/graphql/types/invitation_interface.rb b/app/graphql/types/invitation_interface.rb
new file mode 100644
index 00000000000..a29716c292e
--- /dev/null
+++ b/app/graphql/types/invitation_interface.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Types
+ module InvitationInterface
+ include BaseInterface
+
+ field :email, GraphQL::STRING_TYPE, null: false,
+ description: 'Email of the member to invite'
+
+ field :access_level, Types::AccessLevelType, null: true,
+ description: 'GitLab::Access level'
+
+ field :created_by, Types::UserType, null: true,
+ description: 'User that authorized membership'
+
+ field :created_at, Types::TimeType, null: true,
+ description: 'Date and time the membership was created'
+
+ field :updated_at, Types::TimeType, null: true,
+ description: 'Date and time the membership was last updated'
+
+ field :expires_at, Types::TimeType, null: true,
+ description: 'Date and time the membership expires'
+
+ field :user, Types::UserType, null: true,
+ description: 'User that is associated with the member object'
+
+ definition_methods do
+ def resolve_type(object, context)
+ case object
+ when GroupMember
+ Types::GroupInvitationType
+ when ProjectMember
+ Types::ProjectInvitationType
+ else
+ raise ::Gitlab::Graphql::Errors::BaseError, "Unknown member type #{object.class.name}"
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/issue_connection_type.rb b/app/graphql/types/issue_connection_type.rb
new file mode 100644
index 00000000000..2e0f05f741e
--- /dev/null
+++ b/app/graphql/types/issue_connection_type.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ class IssueConnectionType < CountableConnectionType
+ end
+end
+
+Types::IssueConnectionType.prepend_if_ee('::EE::Types::IssueConnectionType')
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index 487508f448f..49c84f75e1a 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -4,7 +4,7 @@ module Types
class IssueType < BaseObject
graphql_name 'Issue'
- connection_type_class(Types::CountableConnectionType)
+ connection_type_class(Types::IssueConnectionType)
implements(Types::Notes::NoteableType)
implements(Types::CurrentUserTodos)
@@ -41,6 +41,9 @@ module Types
field :assignees, Types::UserType.connection_type, null: true,
description: 'Assignees of the issue'
+ field :updated_by, Types::UserType, null: true,
+ description: 'User that last updated the issue'
+
field :labels, Types::LabelType.connection_type, null: true,
description: 'Labels of the issue'
field :milestone, Types::MilestoneType, null: true,
@@ -59,6 +62,8 @@ module Types
description: 'Number of downvotes the issue has received'
field :user_notes_count, GraphQL::INT_TYPE, null: false,
description: 'Number of user notes of the issue'
+ field :user_discussions_count, GraphQL::INT_TYPE, null: false,
+ description: 'Number of user discussions in the issue'
field :web_path, GraphQL::STRING_TYPE, null: false, method: :issue_path,
description: 'Web path of the issue'
field :web_url, GraphQL::STRING_TYPE, null: false,
@@ -68,12 +73,19 @@ module Types
field :participants, Types::UserType.connection_type, null: true, complexity: 5,
description: 'List of participants in the issue'
+ field :emails_disabled, GraphQL::BOOLEAN_TYPE, null: false,
+ method: :project_emails_disabled?,
+ description: 'Indicates if a project has email notifications disabled: `true` if email notifications are disabled'
field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false, complexity: 5,
description: 'Indicates the currently logged in user is subscribed to the issue'
field :time_estimate, GraphQL::INT_TYPE, null: false,
description: 'Time estimate of the issue'
field :total_time_spent, GraphQL::INT_TYPE, null: false,
description: 'Total time reported as spent on the issue'
+ field :human_time_estimate, GraphQL::STRING_TYPE, null: true,
+ description: 'Human-readable time estimate of the issue'
+ field :human_total_time_spent, GraphQL::STRING_TYPE, null: true,
+ description: 'Human-readable total time reported as spent on the issue'
field :closed_at, Types::TimeType, null: true,
description: 'Timestamp of when the issue was closed'
@@ -86,11 +98,6 @@ module Types
field :task_completion_status, Types::TaskCompletionStatus, null: false,
description: 'Task completion status of the issue'
- field :designs, Types::DesignManagement::DesignCollectionType, null: true,
- method: :design_collection,
- deprecated: { reason: 'Use `designCollection`', milestone: '12.2' },
- description: 'The designs associated with this issue'
-
field :design_collection, Types::DesignManagement::DesignCollectionType, null: true,
description: 'Collection of design images associated with this issue'
@@ -106,14 +113,48 @@ module Types
field :severity, Types::IssuableSeverityEnum, null: true,
description: 'Severity level of the incident'
+ field :moved, GraphQL::BOOLEAN_TYPE, method: :moved?, null: true,
+ description: 'Indicates if issue got moved from other project'
+
+ field :moved_to, Types::IssueType, null: true,
+ description: 'Updated Issue after it got moved to another project'
+
+ def user_notes_count
+ BatchLoader::GraphQL.for(object.id).batch(key: :issue_user_notes_count) do |ids, loader, args|
+ counts = Note.count_for_collection(ids, 'Issue').index_by(&:noteable_id)
+
+ ids.each do |id|
+ loader.call(id, counts[id]&.count || 0)
+ end
+ end
+ end
+
+ def user_discussions_count
+ BatchLoader::GraphQL.for(object.id).batch(key: :issue_user_discussions_count) do |ids, loader, args|
+ counts = Note.count_for_collection(ids, 'Issue', 'COUNT(DISTINCT discussion_id) as count').index_by(&:noteable_id)
+
+ ids.each do |id|
+ loader.call(id, counts[id]&.count || 0)
+ end
+ end
+ end
+
def author
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
end
+ def updated_by
+ Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.updated_by_id).find
+ end
+
def milestone
Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, object.milestone_id).find
end
+ def moved_to
+ Gitlab::Graphql::Loaders::BatchModelLoader.new(Issue, object.moved_to_id).find
+ end
+
def discussion_locked
!!object.discussion_locked
end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 372aeac055b..e68d6706c43 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -68,6 +68,8 @@ module Types
description: 'SHA of the merge request commit (set once merged)'
field :user_notes_count, GraphQL::INT_TYPE, null: true,
description: 'User notes count of the merge request'
+ field :user_discussions_count, GraphQL::INT_TYPE, null: true,
+ description: 'Number of user discussions in the merge request'
field :should_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :should_remove_source_branch?, null: true,
description: 'Indicates if the source branch of the merge request will be deleted after merge'
field :force_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :force_remove_source_branch?, null: true,
@@ -86,9 +88,6 @@ module Types
description: 'Rebase commit SHA of the merge request'
field :rebase_in_progress, GraphQL::BOOLEAN_TYPE, method: :rebase_in_progress?, null: false, calls_gitaly: true,
description: 'Indicates if there is a rebase currently in progress for the merge request'
- field :merge_commit_message, GraphQL::STRING_TYPE, method: :default_merge_commit_message, null: true,
- deprecated: { reason: 'Use `defaultMergeCommitMessage`', milestone: '11.8' },
- description: 'Default merge commit message of the merge request'
field :default_merge_commit_message, GraphQL::STRING_TYPE, null: true,
description: 'Default merge commit message of the merge request'
field :merge_ongoing, GraphQL::BOOLEAN_TYPE, method: :merge_ongoing?, null: false,
@@ -112,14 +111,13 @@ module Types
field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline,
description: 'The pipeline running on the branch HEAD of the merge request'
- field :pipelines, Types::Ci::PipelineType.connection_type,
+ field :pipelines,
null: true,
description: 'Pipelines for the merge request',
resolver: Resolvers::MergeRequestPipelinesResolver
field :milestone, Types::MilestoneType, null: true,
- description: 'The milestone of the merge request',
- resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find }
+ description: 'The milestone of the merge request'
field :assignees, Types::UserType.connection_type, null: true, complexity: 5,
description: 'Assignees of the merge request'
field :author, Types::UserType, null: true,
@@ -159,17 +157,25 @@ module Types
object.approved_by_users
end
- # rubocop: disable CodeReuse/ActiveRecord
def user_notes_count
BatchLoader::GraphQL.for(object.id).batch(key: :merge_request_user_notes_count) do |ids, loader, args|
- counts = Note.where(noteable_type: 'MergeRequest', noteable_id: ids).user.group(:noteable_id).count
+ counts = Note.count_for_collection(ids, 'MergeRequest').index_by(&:noteable_id)
+
+ ids.each do |id|
+ loader.call(id, counts[id]&.count || 0)
+ end
+ end
+ end
+
+ def user_discussions_count
+ BatchLoader::GraphQL.for(object.id).batch(key: :merge_request_user_discussions_count) do |ids, loader, args|
+ counts = Note.count_for_collection(ids, 'MergeRequest', 'COUNT(DISTINCT discussion_id) as count').index_by(&:noteable_id)
ids.each do |id|
- loader.call(id, counts[id] || 0)
+ loader.call(id, counts[id]&.count || 0)
end
end
end
- # rubocop: enable CodeReuse/ActiveRecord
def diff_stats(path: nil)
stats = Array.wrap(object.diff_stats&.to_a)
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 3f48e7b4a16..75ccac6d590 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -11,6 +11,13 @@ module Types
mount_mutation Mutations::AlertManagement::UpdateAlertStatus
mount_mutation Mutations::AlertManagement::Alerts::SetAssignees
mount_mutation Mutations::AlertManagement::Alerts::Todo::Create
+ mount_mutation Mutations::AlertManagement::HttpIntegration::Create
+ mount_mutation Mutations::AlertManagement::HttpIntegration::Update
+ mount_mutation Mutations::AlertManagement::HttpIntegration::ResetToken
+ mount_mutation Mutations::AlertManagement::HttpIntegration::Destroy
+ mount_mutation Mutations::AlertManagement::PrometheusIntegration::Create
+ mount_mutation Mutations::AlertManagement::PrometheusIntegration::Update
+ mount_mutation Mutations::AlertManagement::PrometheusIntegration::ResetToken
mount_mutation Mutations::AwardEmojis::Add
mount_mutation Mutations::AwardEmojis::Remove
mount_mutation Mutations::AwardEmojis::Toggle
@@ -22,6 +29,7 @@ module Types
mount_mutation Mutations::Boards::Lists::Destroy
mount_mutation Mutations::Branches::Create, calls_gitaly: true
mount_mutation Mutations::Commits::Create, calls_gitaly: true
+ mount_mutation Mutations::CustomEmoji::Create, feature_flag: :custom_emoji
mount_mutation Mutations::Discussions::ToggleResolve
mount_mutation Mutations::Issues::Create
mount_mutation Mutations::Issues::SetAssignees
@@ -32,6 +40,7 @@ module Types
mount_mutation Mutations::Issues::SetSubscription
mount_mutation Mutations::Issues::Update
mount_mutation Mutations::Issues::Move
+ mount_mutation Mutations::Labels::Create
mount_mutation Mutations::MergeRequests::Create
mount_mutation Mutations::MergeRequests::Update
mount_mutation Mutations::MergeRequests::SetLabels
@@ -53,7 +62,13 @@ module Types
description: 'Updates a DiffNote on an image (a `Note` where the `position.positionType` is `"image"`). ' \
'If the body of the Note contains only quick actions, the Note will be ' \
'destroyed during the update, and no Note will be returned'
+ mount_mutation Mutations::Notes::RepositionImageDiffNote
mount_mutation Mutations::Notes::Destroy
+ mount_mutation Mutations::Releases::Create
+ mount_mutation Mutations::Terraform::State::Delete
+ mount_mutation Mutations::Terraform::State::Lock
+ mount_mutation Mutations::Terraform::State::Unlock
+ mount_mutation Mutations::Todos::Create
mount_mutation Mutations::Todos::MarkDone
mount_mutation Mutations::Todos::Restore
mount_mutation Mutations::Todos::MarkAllDone
@@ -68,6 +83,7 @@ module Types
mount_mutation Mutations::DesignManagement::Delete, calls_gitaly: true
mount_mutation Mutations::DesignManagement::Move
mount_mutation Mutations::ContainerExpirationPolicies::Update
+ mount_mutation Mutations::ContainerRepositories::Destroy
mount_mutation Mutations::Ci::PipelineCancel
mount_mutation Mutations::Ci::PipelineDestroy
mount_mutation Mutations::Ci::PipelineRetry
diff --git a/app/graphql/types/notes/update_diff_image_position_input_type.rb b/app/graphql/types/notes/update_diff_image_position_input_type.rb
index af99764f9f2..1b915b65ae9 100644
--- a/app/graphql/types/notes/update_diff_image_position_input_type.rb
+++ b/app/graphql/types/notes/update_diff_image_position_input_type.rb
@@ -23,6 +23,14 @@ module Types
argument :height, GraphQL::INT_TYPE,
required: false,
description: copy_field_description(Types::Notes::DiffPositionType, :height)
+
+ def prepare
+ to_h.compact.tap do |properties|
+ if properties.empty?
+ raise GraphQL::ExecutionError, "At least one property of `#{self.class.graphql_name}` must be set"
+ end
+ end
+ end
end
# rubocop: enable Graphql/AuthorizeTypes
end
diff --git a/app/graphql/types/permission_types/custom_emoji.rb b/app/graphql/types/permission_types/custom_emoji.rb
new file mode 100644
index 00000000000..0b2e7da44f5
--- /dev/null
+++ b/app/graphql/types/permission_types/custom_emoji.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Types
+ module PermissionTypes
+ class CustomEmoji < BasePermissionType
+ graphql_name 'CustomEmojiPermissions'
+
+ abilities :create_custom_emoji, :read_custom_emoji
+ end
+ end
+end
diff --git a/app/graphql/types/permission_types/note.rb b/app/graphql/types/permission_types/note.rb
index a585d3daaa8..923f2b660fe 100644
--- a/app/graphql/types/permission_types/note.rb
+++ b/app/graphql/types/permission_types/note.rb
@@ -5,7 +5,7 @@ module Types
class Note < BasePermissionType
graphql_name 'NotePermissions'
- abilities :read_note, :create_note, :admin_note, :resolve_note, :award_emoji
+ abilities :read_note, :create_note, :admin_note, :resolve_note, :reposition_note, :award_emoji
end
end
end
diff --git a/app/graphql/types/project_invitation_type.rb b/app/graphql/types/project_invitation_type.rb
new file mode 100644
index 00000000000..a5367a4f204
--- /dev/null
+++ b/app/graphql/types/project_invitation_type.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Types
+ class ProjectInvitationType < BaseObject
+ graphql_name 'ProjectInvitation'
+ description 'Represents a Project Membership Invitation'
+
+ expose_permissions Types::PermissionTypes::Project
+
+ implements InvitationInterface
+
+ authorize :read_project
+
+ field :project, Types::ProjectType, null: true,
+ description: 'Project ID for the project of the invitation'
+
+ def project
+ Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.source_id).find
+ end
+ end
+end
diff --git a/app/graphql/types/project_statistics_type.rb b/app/graphql/types/project_statistics_type.rb
index b3916e42e92..26cb5ab59b5 100644
--- a/app/graphql/types/project_statistics_type.rb
+++ b/app/graphql/types/project_statistics_type.rb
@@ -10,18 +10,20 @@ module Types
description: 'Commit count of the project'
field :storage_size, GraphQL::FLOAT_TYPE, null: false,
- description: 'Storage size of the project'
+ description: 'Storage size of the project in bytes'
field :repository_size, GraphQL::FLOAT_TYPE, null: false,
- description: 'Repository size of the project'
+ description: 'Repository size of the project in bytes'
field :lfs_objects_size, GraphQL::FLOAT_TYPE, null: false,
- description: 'Large File Storage (LFS) object size of the project'
+ description: 'Large File Storage (LFS) object size of the project in bytes'
field :build_artifacts_size, GraphQL::FLOAT_TYPE, null: false,
- description: 'Build artifacts size of the project'
+ description: 'Build artifacts size of the project in bytes'
field :packages_size, GraphQL::FLOAT_TYPE, null: false,
- description: 'Packages size of the project'
+ description: 'Packages size of the project in bytes'
field :wiki_size, GraphQL::FLOAT_TYPE, null: true,
- description: 'Wiki size of the project'
+ description: 'Wiki size of the project in bytes'
field :snippets_size, GraphQL::FLOAT_TYPE, null: true,
- description: 'Snippets size of the project'
+ description: 'Snippets size of the project in bytes'
+ field :uploads_size, GraphQL::FLOAT_TYPE, null: true,
+ description: 'Uploads size of the project in bytes'
end
end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index c7fc193abe8..5a436886117 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -161,7 +161,6 @@ module Types
resolver: Resolvers::ProjectMilestonesResolver
field :project_members,
- Types::MemberInterface.connection_type,
description: 'Members of the project',
resolver: Resolvers::ProjectMembersResolver
@@ -188,9 +187,9 @@ module Types
resolver: Resolvers::PackagesResolver
field :pipelines,
- Types::Ci::PipelineType.connection_type,
null: true,
description: 'Build pipelines of the project',
+ extras: [:lookahead],
resolver: Resolvers::ProjectPipelinesResolver
field :pipeline,
@@ -267,6 +266,12 @@ module Types
description: 'Counts of alerts by status for the project',
resolver: Resolvers::AlertManagement::AlertStatusCountsResolver
+ field :alert_management_integrations,
+ Types::AlertManagement::IntegrationType.connection_type,
+ null: true,
+ description: 'Integrations which can receive alerts for the project',
+ resolver: Resolvers::AlertManagement::IntegrationsResolver
+
field :releases,
Types::ReleaseType.connection_type,
null: true,
@@ -285,6 +290,12 @@ module Types
null: true,
description: 'The container expiration policy of the project'
+ field :container_repositories,
+ Types::ContainerRepositoryType.connection_type,
+ null: true,
+ description: 'Container repositories of the project',
+ resolver: Resolvers::ContainerRepositoriesResolver
+
field :label,
Types::LabelType,
null: true,
diff --git a/app/graphql/types/projects/namespace_project_sort_enum.rb b/app/graphql/types/projects/namespace_project_sort_enum.rb
index 1e13deb6508..ede29748beb 100644
--- a/app/graphql/types/projects/namespace_project_sort_enum.rb
+++ b/app/graphql/types/projects/namespace_project_sort_enum.rb
@@ -7,6 +7,7 @@ module Types
description 'Values for sorting projects'
value 'SIMILARITY', 'Most similar to the search query', value: :similarity
+ value 'STORAGE', 'Sort by storage size', value: :storage
end
end
end
diff --git a/app/graphql/types/projects/service_type_enum.rb b/app/graphql/types/projects/service_type_enum.rb
index 340fdff6b86..34e06c67be6 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'
- ::Service.services_types.each do |service_type|
+ ::Service.available_services_types(include_dev: false).each do |service_type|
value service_type.underscore.upcase, value: service_type
end
end
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index bd4b53bdaa7..d194b0979b3 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -50,10 +50,14 @@ module Types
field :milestone, ::Types::MilestoneType,
null: true,
description: 'Find a milestone' do
- argument :id, ::Types::GlobalIDType[Milestone],
- required: true,
- description: 'Find a milestone by its ID'
- end
+ argument :id, ::Types::GlobalIDType[Milestone], required: true, description: 'Find a milestone by its ID'
+ end
+
+ field :container_repository, Types::ContainerRepositoryDetailsType,
+ null: true,
+ description: 'Find a container repository' do
+ argument :id, ::Types::GlobalIDType[::ContainerRepository], required: true, description: 'The global ID of the container repository'
+ end
field :user, Types::UserType,
null: true,
@@ -84,6 +88,10 @@ module Types
null: true, description: 'Supported runner platforms',
resolver: Resolvers::Ci::RunnerPlatformsResolver
+ field :runner_setup, Types::Ci::RunnerSetupType, null: true,
+ description: 'Get runner setup instructions',
+ resolver: Resolvers::Ci::RunnerSetupResolver
+
def design_management
DesignManagementObject.new(nil)
end
@@ -101,6 +109,13 @@ module Types
id = ::Types::GlobalIDType[Milestone].coerce_isolated_input(id)
GitlabSchema.find_by_gid(id)
end
+
+ def container_repository(id:)
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ id = ::Types::GlobalIDType[::ContainerRepository].coerce_isolated_input(id)
+ GitlabSchema.find_by_gid(id)
+ end
end
end
diff --git a/app/graphql/types/release_asset_link_input_type.rb b/app/graphql/types/release_asset_link_input_type.rb
new file mode 100644
index 00000000000..d13861fad8f
--- /dev/null
+++ b/app/graphql/types/release_asset_link_input_type.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ class ReleaseAssetLinkInputType < BaseInputObject
+ graphql_name 'ReleaseAssetLinkInput'
+ description 'Fields that are available when modifying a release asset link'
+
+ argument :name, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Name of the asset link'
+
+ argument :url, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'URL of the asset link'
+
+ argument :direct_asset_path, GraphQL::STRING_TYPE,
+ required: false, as: :filepath,
+ description: 'Relative path for a direct asset link'
+
+ argument :link_type, Types::ReleaseAssetLinkTypeEnum,
+ required: false, default_value: 'other',
+ description: 'The type of the asset link'
+ end
+end
diff --git a/app/graphql/types/release_asset_link_type.rb b/app/graphql/types/release_asset_link_type.rb
index 0e519ece791..8fb051f5627 100644
--- a/app/graphql/types/release_asset_link_type.rb
+++ b/app/graphql/types/release_asset_link_type.rb
@@ -24,10 +24,8 @@ module Types
def direct_asset_url
return object.url unless object.filepath
- release = object.release
- project = release.project
-
- Gitlab::Routing.url_helpers.project_release_url(project, release) << object.filepath
+ release = object.release.present
+ release.download_url(object.filepath)
end
end
end
diff --git a/app/graphql/types/release_asset_link_type_enum.rb b/app/graphql/types/release_asset_link_type_enum.rb
index 01862ada56d..70601b9f8da 100644
--- a/app/graphql/types/release_asset_link_type_enum.rb
+++ b/app/graphql/types/release_asset_link_type_enum.rb
@@ -3,7 +3,7 @@
module Types
class ReleaseAssetLinkTypeEnum < BaseEnum
graphql_name 'ReleaseAssetLinkType'
- description 'Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`'
+ description 'Type of the link: `other`, `runbook`, `image`, `package`'
::Releases::Link.link_types.keys.each do |link_type|
value link_type.upcase, value: link_type, description: "#{link_type.titleize} link type"
diff --git a/app/graphql/types/release_assets_input_type.rb b/app/graphql/types/release_assets_input_type.rb
new file mode 100644
index 00000000000..3fcb517e044
--- /dev/null
+++ b/app/graphql/types/release_assets_input_type.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ class ReleaseAssetsInputType < BaseInputObject
+ graphql_name 'ReleaseAssetsInput'
+ description 'Fields that are available when modifying release assets'
+
+ argument :links, [Types::ReleaseAssetLinkInputType],
+ required: false,
+ description: 'A list of asset links to associate to the release'
+ end
+end
diff --git a/app/graphql/types/release_links_type.rb b/app/graphql/types/release_links_type.rb
index f61a16f5b67..619bb1e6c3a 100644
--- a/app/graphql/types/release_links_type.rb
+++ b/app/graphql/types/release_links_type.rb
@@ -12,12 +12,18 @@ module Types
field :self_url, GraphQL::STRING_TYPE, null: true,
description: 'HTTP URL of the release'
- field :merge_requests_url, GraphQL::STRING_TYPE, null: true,
- description: 'HTTP URL of the merge request page filtered by this release'
- field :issues_url, GraphQL::STRING_TYPE, null: true,
- description: 'HTTP URL of the issues page filtered by this release'
field :edit_url, GraphQL::STRING_TYPE, null: true,
description: "HTTP URL of the release's edit page",
authorize: :update_release
+ field :opened_merge_requests_url, GraphQL::STRING_TYPE, null: true,
+ description: 'HTTP URL of the merge request page, filtered by this release and `state=open`'
+ field :merged_merge_requests_url, GraphQL::STRING_TYPE, null: true,
+ description: 'HTTP URL of the merge request page , filtered by this release and `state=merged`'
+ field :closed_merge_requests_url, GraphQL::STRING_TYPE, null: true,
+ description: 'HTTP URL of the merge request page , filtered by this release and `state=closed`'
+ field :opened_issues_url, GraphQL::STRING_TYPE, null: true,
+ description: 'HTTP URL of the issues page, filtered by this release and `state=open`'
+ field :closed_issues_url, GraphQL::STRING_TYPE, null: true,
+ description: 'HTTP URL of the issues page, filtered by this release and `state=closed`'
end
end
diff --git a/app/graphql/types/release_sort_enum.rb b/app/graphql/types/release_sort_enum.rb
new file mode 100644
index 00000000000..2f9af1bced9
--- /dev/null
+++ b/app/graphql/types/release_sort_enum.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Types
+ # Not inheriting from Types::SortEnum since we only want
+ # to implement a subset of the sort values it defines.
+ class ReleaseSortEnum < BaseEnum
+ graphql_name 'ReleaseSort'
+ description 'Values for sorting releases'
+
+ # Borrowed from Types::SortEnum
+ # These values/descriptions should stay in-sync as much as possible.
+ value 'CREATED_DESC', 'Created at descending order', value: :created_desc
+ value 'CREATED_ASC', 'Created at ascending order', value: :created_asc
+
+ value 'RELEASED_AT_DESC', 'Released at by descending order', value: :released_at_desc
+ value 'RELEASED_AT_ASC', 'Released at by ascending order', value: :released_at_asc
+ end
+end
diff --git a/app/graphql/types/root_storage_statistics_type.rb b/app/graphql/types/root_storage_statistics_type.rb
index 224e8c7ee03..21448b33bb5 100644
--- a/app/graphql/types/root_storage_statistics_type.rb
+++ b/app/graphql/types/root_storage_statistics_type.rb
@@ -14,5 +14,6 @@ module Types
field :wiki_size, GraphQL::FLOAT_TYPE, null: false, description: 'The wiki size in bytes'
field :snippets_size, GraphQL::FLOAT_TYPE, null: false, description: 'The snippets size in bytes'
field :pipeline_artifacts_size, GraphQL::FLOAT_TYPE, null: false, description: 'The CI pipeline artifacts size in bytes'
+ field :uploads_size, GraphQL::FLOAT_TYPE, null: false, description: 'The uploads size in bytes'
end
end
diff --git a/app/graphql/types/security/report_type_enum.rb b/app/graphql/types/security/report_type_enum.rb
new file mode 100644
index 00000000000..ee67f68b27b
--- /dev/null
+++ b/app/graphql/types/security/report_type_enum.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Types
+ module Security
+ class ReportTypeEnum < BaseEnum
+ graphql_name 'SecurityReportTypeEnum'
+
+ ::Security::SecurityJobsFinder.allowed_job_types.each do |report_type|
+ value report_type.upcase,
+ value: report_type,
+ description: "#{report_type.upcase.to_s.tr('_', ' ')} scan report"
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/snippet_type.rb b/app/graphql/types/snippet_type.rb
index 495c25c1776..f587adf207f 100644
--- a/app/graphql/types/snippet_type.rb
+++ b/app/graphql/types/snippet_type.rb
@@ -13,7 +13,7 @@ module Types
expose_permissions Types::PermissionTypes::Snippet
- field :id, GraphQL::ID_TYPE,
+ field :id, Types::GlobalIDType[::Snippet],
description: 'ID of the snippet',
null: false
diff --git a/app/graphql/types/terraform/state_type.rb b/app/graphql/types/terraform/state_type.rb
index f25f3a7789b..05b6d130f19 100644
--- a/app/graphql/types/terraform/state_type.rb
+++ b/app/graphql/types/terraform/state_type.rb
@@ -7,6 +7,8 @@ module Types
authorize :read_terraform_state
+ connection_type_class(Types::CountableConnectionType)
+
field :id, GraphQL::ID_TYPE,
null: false,
description: 'ID of the Terraform state'
@@ -25,6 +27,11 @@ module Types
null: true,
description: 'Timestamp the Terraform state was locked'
+ field :latest_version, Types::Terraform::StateVersionType,
+ complexity: 3,
+ null: true,
+ description: 'The latest version of the Terraform state'
+
field :created_at, Types::TimeType,
null: false,
description: 'Timestamp the Terraform state was created'
diff --git a/app/graphql/types/terraform/state_version_type.rb b/app/graphql/types/terraform/state_version_type.rb
new file mode 100644
index 00000000000..b1fbe42ecaf
--- /dev/null
+++ b/app/graphql/types/terraform/state_version_type.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Types
+ module Terraform
+ class StateVersionType < BaseObject
+ graphql_name 'TerraformStateVersion'
+
+ authorize :read_terraform_state
+
+ field :id, GraphQL::ID_TYPE,
+ null: false,
+ description: 'ID of the Terraform state version'
+
+ field :created_by_user, Types::UserType,
+ null: true,
+ authorize: :read_user,
+ description: 'The user that created this version',
+ resolve: -> (version, _, _) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, version.created_by_user_id).find }
+
+ field :job, Types::Ci::JobType,
+ null: true,
+ authorize: :read_build,
+ description: 'The job that created this version',
+ resolve: -> (version, _, _) { Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Build, version.ci_build_id).find }
+
+ field :created_at, Types::TimeType,
+ null: false,
+ description: 'Timestamp the version was created'
+
+ field :updated_at, Types::TimeType,
+ null: false,
+ description: 'Timestamp the version was updated'
+ end
+ end
+end
diff --git a/app/graphql/types/user_status_type.rb b/app/graphql/types/user_status_type.rb
index ff277c1f8e8..9cf6c862d3d 100644
--- a/app/graphql/types/user_status_type.rb
+++ b/app/graphql/types/user_status_type.rb
@@ -11,5 +11,7 @@ module Types
description: 'User status message'
field :emoji, GraphQL::STRING_TYPE, null: true,
description: 'String representation of emoji'
+ field :availability, Types::AvailabilityEnum, null: false,
+ description: 'User availability status'
end
end
diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb
index 8047708776d..11c5369f726 100644
--- a/app/graphql/types/user_type.rb
+++ b/app/graphql/types/user_type.rb
@@ -32,6 +32,10 @@ module Types
field :group_memberships, Types::GroupMemberType.connection_type, null: true,
description: 'Group memberships of the user',
method: :group_members
+ field :group_count, GraphQL::INT_TYPE, null: true,
+ resolver: Resolvers::Users::GroupCountResolver,
+ description: 'Group count for the user',
+ feature_flag: :user_group_counts
field :status, Types::UserStatusType, null: true,
description: 'User status'
field :project_memberships, Types::ProjectMemberType.connection_type, null: true,
@@ -42,10 +46,10 @@ module Types
resolver: Resolvers::UserStarredProjectsResolver
# Merge request field: MRs can be either authored or assigned:
- field :authored_merge_requests, Types::MergeRequestType.connection_type, null: true,
+ field :authored_merge_requests,
resolver: Resolvers::AuthoredMergeRequestsResolver,
description: 'Merge Requests authored by the user'
- field :assigned_merge_requests, Types::MergeRequestType.connection_type, null: true,
+ field :assigned_merge_requests,
resolver: Resolvers::AssignedMergeRequestsResolver,
description: 'Merge Requests assigned to the user'