Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/graphql')
-rw-r--r--app/graphql/graphql_triggers.rb2
-rw-r--r--app/graphql/mutations/boards/issues/issue_move_list.rb28
-rw-r--r--app/graphql/mutations/ci/job/artifacts_destroy.rb38
-rw-r--r--app/graphql/mutations/ci/job_artifact/destroy.rb39
-rw-r--r--app/graphql/mutations/ci/runner/bulk_delete.rb4
-rw-r--r--app/graphql/mutations/ci/runner/update.rb46
-rw-r--r--app/graphql/mutations/custom_emoji/create.rb4
-rw-r--r--app/graphql/mutations/custom_emoji/destroy.rb4
-rw-r--r--app/graphql/mutations/dependency_proxy/group_settings/update.rb5
-rw-r--r--app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb9
-rw-r--r--app/graphql/mutations/releases/create.rb2
-rw-r--r--app/graphql/mutations/todos/restore_many.rb4
-rw-r--r--app/graphql/queries/repository/blob_info.query.graphql62
-rw-r--r--app/graphql/resolvers/ci/job_token_scope_resolver.rb4
-rw-r--r--app/graphql/resolvers/ci/runner_jobs_resolver.rb10
-rw-r--r--app/graphql/resolvers/ci/runner_owner_project_resolver.rb15
-rw-r--r--app/graphql/resolvers/ci/runner_projects_resolver.rb63
-rw-r--r--app/graphql/resolvers/ci/test_suite_resolver.rb3
-rw-r--r--app/graphql/resolvers/concerns/issue_resolver_arguments.rb58
-rw-r--r--app/graphql/resolvers/concerns/looks_ahead.rb8
-rw-r--r--app/graphql/resolvers/concerns/project_search_arguments.rb36
-rw-r--r--app/graphql/resolvers/concerns/search_arguments.rb39
-rw-r--r--app/graphql/resolvers/crm/organization_state_counts_resolver.rb26
-rw-r--r--app/graphql/resolvers/crm/organizations_resolver.rb6
-rw-r--r--app/graphql/resolvers/deployment_resolver.rb20
-rw-r--r--app/graphql/resolvers/deployments_resolver.rb39
-rw-r--r--app/graphql/resolvers/environments/last_deployment_resolver.rb44
-rw-r--r--app/graphql/resolvers/environments_resolver.rb4
-rw-r--r--app/graphql/resolvers/group_packages_resolver.rb10
-rw-r--r--app/graphql/resolvers/members_resolver.rb4
-rw-r--r--app/graphql/resolvers/package_details_resolver.rb10
-rw-r--r--app/graphql/resolvers/project_jobs_resolver.rb10
-rw-r--r--app/graphql/resolvers/projects/branch_rules_resolver.rb15
-rw-r--r--app/graphql/resolvers/projects_resolver.rb32
-rw-r--r--app/graphql/resolvers/work_items_resolver.rb48
-rw-r--r--app/graphql/types/base_field.rb31
-rw-r--r--app/graphql/types/branch_protections/base_access_level_type.rb22
-rw-r--r--app/graphql/types/branch_protections/merge_access_level_type.rb11
-rw-r--r--app/graphql/types/branch_protections/push_access_level_type.rb11
-rw-r--r--app/graphql/types/branch_rules/branch_protection_type.rb29
-rw-r--r--app/graphql/types/ci/config_variable_type.rb22
-rw-r--r--app/graphql/types/ci/group_variable_connection_type.rb17
-rw-r--r--app/graphql/types/ci/group_variable_type.rb17
-rw-r--r--app/graphql/types/ci/instance_variable_type.rb28
-rw-r--r--app/graphql/types/ci/job_artifact_type.rb9
-rw-r--r--app/graphql/types/ci/job_type.rb6
-rw-r--r--app/graphql/types/ci/manual_variable_type.rb12
-rw-r--r--app/graphql/types/ci/project_variable_connection_type.rb17
-rw-r--r--app/graphql/types/ci/project_variable_type.rb13
-rw-r--r--app/graphql/types/ci/runner_membership_filter_enum.rb8
-rw-r--r--app/graphql/types/ci/runner_type.rb22
-rw-r--r--app/graphql/types/ci/variable_interface.rb24
-rw-r--r--app/graphql/types/clusters/agent_type.rb2
-rw-r--r--app/graphql/types/customer_relations/contact_sort_enum.rb4
-rw-r--r--app/graphql/types/customer_relations/organization_sort_enum.rb21
-rw-r--r--app/graphql/types/customer_relations/organization_state_counts_type.rb24
-rw-r--r--app/graphql/types/customer_relations/organization_state_enum.rb8
-rw-r--r--app/graphql/types/deployment_details_type.rb15
-rw-r--r--app/graphql/types/deployment_status_enum.rb14
-rw-r--r--app/graphql/types/deployment_tag_type.rb20
-rw-r--r--app/graphql/types/deployment_type.rb69
-rw-r--r--app/graphql/types/deployments_order_by_input_type.rb24
-rw-r--r--app/graphql/types/environment_type.rb41
-rw-r--r--app/graphql/types/group_type.rb14
-rw-r--r--app/graphql/types/member_sort_enum.rb13
-rw-r--r--app/graphql/types/merge_request_type.rb9
-rw-r--r--app/graphql/types/merge_requests/detailed_merge_status_enum.rb3
-rw-r--r--app/graphql/types/mutation_type.rb6
-rw-r--r--app/graphql/types/packages/package_details_type.rb2
-rw-r--r--app/graphql/types/project_type.rb479
-rw-r--r--app/graphql/types/projects/branch_rule_type.rb33
-rw-r--r--app/graphql/types/query_type.rb2
-rw-r--r--app/graphql/types/sort_direction_enum.rb11
-rw-r--r--app/graphql/types/subscription_type.rb7
-rw-r--r--app/graphql/types/timelog_type.rb2
-rw-r--r--app/graphql/types/work_items/widgets/description_type.rb14
76 files changed, 1454 insertions, 433 deletions
diff --git a/app/graphql/graphql_triggers.rb b/app/graphql/graphql_triggers.rb
index b39875b83a9..8086d8c02a4 100644
--- a/app/graphql/graphql_triggers.rb
+++ b/app/graphql/graphql_triggers.rb
@@ -21,3 +21,5 @@ module GraphqlTriggers
GitlabSchema.subscriptions.trigger('issuableDatesUpdated', { issuable_id: issuable.to_gid }, issuable)
end
end
+
+GraphqlTriggers.prepend_mod
diff --git a/app/graphql/mutations/boards/issues/issue_move_list.rb b/app/graphql/mutations/boards/issues/issue_move_list.rb
index 14fe9714f99..e9cae80e5f9 100644
--- a/app/graphql/mutations/boards/issues/issue_move_list.rb
+++ b/app/graphql/mutations/boards/issues/issue_move_list.rb
@@ -38,10 +38,16 @@ module Mutations
required: false,
description: 'ID of issue that should be placed after the current issue.'
+ argument :position_in_list, GraphQL::Types::Int,
+ required: false,
+ description: "Position of issue within the board list. Positions start at 0. "\
+ "Use #{::Boards::Issues::MoveService::LIST_END_POSITION} to move to the end of the list."
+
def ready?(**args)
if move_arguments(args).blank?
raise Gitlab::Graphql::Errors::ArgumentError,
- 'At least one of the arguments fromListId, toListId, afterId or beforeId is required'
+ 'At least one of the arguments ' \
+ 'fromListId, toListId, positionInList, moveAfterId, or moveBeforeId is required'
end
if move_list_arguments(args).one?
@@ -49,6 +55,24 @@ module Mutations
'Both fromListId and toListId must be present'
end
+ if args[:position_in_list].present?
+ if move_list_arguments(args).empty?
+ raise Gitlab::Graphql::Errors::ArgumentError,
+ 'Both fromListId and toListId are required when positionInList is given'
+ end
+
+ if args[:move_before_id].present? || args[:move_after_id].present?
+ raise Gitlab::Graphql::Errors::ArgumentError,
+ 'positionInList is mutually exclusive with any of moveBeforeId or moveAfterId'
+ end
+
+ if args[:position_in_list] != ::Boards::Issues::MoveService::LIST_END_POSITION &&
+ args[:position_in_list] < 0
+ raise Gitlab::Graphql::Errors::ArgumentError,
+ "positionInList must be >= 0 or #{::Boards::Issues::MoveService::LIST_END_POSITION}"
+ end
+ end
+
super
end
@@ -77,7 +101,7 @@ module Mutations
end
def move_arguments(args)
- args.slice(:from_list_id, :to_list_id, :move_after_id, :move_before_id)
+ args.slice(:from_list_id, :to_list_id, :position_in_list, :move_after_id, :move_before_id)
end
def error_for(result)
diff --git a/app/graphql/mutations/ci/job/artifacts_destroy.rb b/app/graphql/mutations/ci/job/artifacts_destroy.rb
new file mode 100644
index 00000000000..c27ab9c4d89
--- /dev/null
+++ b/app/graphql/mutations/ci/job/artifacts_destroy.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Ci
+ module Job
+ class ArtifactsDestroy < Base
+ graphql_name 'JobArtifactsDestroy'
+
+ authorize :destroy_artifacts
+
+ field :job,
+ Types::Ci::JobType,
+ null: true,
+ description: 'Job with artifacts to be deleted.'
+
+ field :destroyed_artifacts_count,
+ GraphQL::Types::Int,
+ null: false,
+ description: 'Number of artifacts deleted.'
+
+ def find_object(id: )
+ GlobalID::Locator.locate(id)
+ end
+
+ def resolve(id:)
+ job = authorized_find!(id: id)
+
+ result = ::Ci::JobArtifacts::DestroyBatchService.new(job.job_artifacts, pick_up_at: Time.current).execute
+ {
+ job: job,
+ destroyed_artifacts_count: result[:destroyed_artifacts_count],
+ errors: Array(result[:errors])
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/ci/job_artifact/destroy.rb b/app/graphql/mutations/ci/job_artifact/destroy.rb
new file mode 100644
index 00000000000..47b3535d631
--- /dev/null
+++ b/app/graphql/mutations/ci/job_artifact/destroy.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Ci
+ module JobArtifact
+ class Destroy < BaseMutation
+ graphql_name 'ArtifactDestroy'
+
+ authorize :destroy_artifacts
+
+ ArtifactID = ::Types::GlobalIDType[::Ci::JobArtifact]
+
+ argument :id,
+ ArtifactID,
+ required: true,
+ description: 'ID of the artifact to delete.'
+
+ field :artifact,
+ Types::Ci::JobArtifactType,
+ null: true,
+ description: 'Deleted artifact.'
+
+ def find_object(id: )
+ GlobalID::Locator.locate(id)
+ end
+
+ def resolve(id:)
+ artifact = authorized_find!(id: id)
+
+ if artifact.destroy
+ { errors: [] }
+ else
+ { errors: artifact.errors.full_messages }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/ci/runner/bulk_delete.rb b/app/graphql/mutations/ci/runner/bulk_delete.rb
index 4c1c2967799..4265099d28e 100644
--- a/app/graphql/mutations/ci/runner/bulk_delete.rb
+++ b/app/graphql/mutations/ci/runner/bulk_delete.rb
@@ -40,9 +40,7 @@ module Mutations
private
def model_ids_of(ids)
- ids.map do |gid|
- gid.model_id.to_i
- end.compact
+ ids.filter_map { |gid| gid.model_id.to_i }
end
def find_all_runners_by_ids(ids)
diff --git a/app/graphql/mutations/ci/runner/update.rb b/app/graphql/mutations/ci/runner/update.rb
index 1c6cf6989bf..f98138646be 100644
--- a/app/graphql/mutations/ci/runner/update.rb
+++ b/app/graphql/mutations/ci/runner/update.rb
@@ -48,8 +48,13 @@ module Mutations
description: 'Indicates the runner is able to run untagged jobs.'
argument :tag_list, [GraphQL::Types::String],
- required: false,
- description: 'Tags associated with the runner.'
+ required: false,
+ description: 'Tags associated with the runner.'
+
+ argument :associated_projects, [::Types::GlobalIDType[::Project]],
+ required: false,
+ description: 'Projects associated with the runner. Available only for project runners.',
+ prepare: -> (global_ids, ctx) { global_ids&.filter_map { |gid| gid.model_id.to_i } }
field :runner,
Types::Ci::RunnerType,
@@ -59,16 +64,47 @@ module Mutations
def resolve(id:, **runner_attrs)
runner = authorized_find!(id)
- unless ::Ci::Runners::UpdateRunnerService.new(runner).update(runner_attrs)
- return { runner: nil, errors: runner.errors.full_messages }
+ associated_projects_ids = runner_attrs.delete(:associated_projects)
+
+ response = { runner: runner, errors: [] }
+ ::Ci::Runner.transaction do
+ associate_runner_projects(response, runner, associated_projects_ids) if associated_projects_ids.present?
+ update_runner(response, runner, runner_attrs)
end
- { runner: runner, errors: [] }
+ response
end
def find_object(id)
GitlabSchema.find_by_gid(id)
end
+
+ private
+
+ def associate_runner_projects(response, runner, associated_project_ids)
+ unless runner.project_type?
+ raise Gitlab::Graphql::Errors::ArgumentError,
+ "associatedProjects must not be specified for '#{runner.runner_type}' scope"
+ end
+
+ result = ::Ci::Runners::SetRunnerAssociatedProjectsService.new(
+ runner: runner,
+ current_user: current_user,
+ project_ids: associated_project_ids
+ ).execute
+ return if result.success?
+
+ response[:errors] = result.errors
+ raise ActiveRecord::Rollback
+ end
+
+ def update_runner(response, runner, attrs)
+ result = ::Ci::Runners::UpdateRunnerService.new(runner).execute(attrs)
+ return if result.success?
+
+ response[:errors] = result.errors
+ raise ActiveRecord::Rollback
+ end
end
end
end
diff --git a/app/graphql/mutations/custom_emoji/create.rb b/app/graphql/mutations/custom_emoji/create.rb
index 269ea6c9999..535ff44a7fd 100644
--- a/app/graphql/mutations/custom_emoji/create.rb
+++ b/app/graphql/mutations/custom_emoji/create.rb
@@ -28,6 +28,10 @@ module Mutations
description: 'Location of the emoji file.'
def resolve(group_path:, **args)
+ if Feature.disabled?(:custom_emoji)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'Custom emoji feature is disabled'
+ end
+
group = authorized_find!(group_path: group_path)
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37911#note_444682238
args[:external] = true
diff --git a/app/graphql/mutations/custom_emoji/destroy.rb b/app/graphql/mutations/custom_emoji/destroy.rb
index 863b8152cc7..64e3f2ed7d3 100644
--- a/app/graphql/mutations/custom_emoji/destroy.rb
+++ b/app/graphql/mutations/custom_emoji/destroy.rb
@@ -17,6 +17,10 @@ module Mutations
description: 'Global ID of the custom emoji to destroy.'
def resolve(id:)
+ if Feature.disabled?(:custom_emoji)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'Custom emoji feature is disabled'
+ end
+
custom_emoji = authorized_find!(id: id)
custom_emoji.destroy!
diff --git a/app/graphql/mutations/dependency_proxy/group_settings/update.rb b/app/graphql/mutations/dependency_proxy/group_settings/update.rb
index 65c919db3c3..6be07edd883 100644
--- a/app/graphql/mutations/dependency_proxy/group_settings/update.rb
+++ b/app/graphql/mutations/dependency_proxy/group_settings/update.rb
@@ -8,6 +8,11 @@ module Mutations
include Mutations::ResolvesGroup
+ description 'These settings can be adjusted by the group Owner or Maintainer. However, in GitLab 16.0, we ' \
+ 'will be limiting this to the Owner role. ' \
+ '[GitLab-#364441](https://gitlab.com/gitlab-org/gitlab/-/issues/364441) proposes making ' \
+ 'this change to match the permissions level in the user interface.'
+
authorize :admin_dependency_proxy
argument :group_path,
diff --git a/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb b/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb
index 31ae29d896b..bb1da9278ff 100644
--- a/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb
+++ b/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb
@@ -6,6 +6,8 @@ module Mutations
class PromoteFromNote < Base
graphql_name 'TimelineEventPromoteFromNote'
+ include NotesHelper
+
argument :note_id, Types::GlobalIDType[::Note],
required: true,
description: 'Note ID from which the timeline event promoted.'
@@ -20,7 +22,7 @@ module Mutations
incident,
current_user,
promoted_from_note: note,
- note: note.note,
+ note: build_note_string(note),
occurred_at: note.created_at,
editable: true
).execute
@@ -38,6 +40,11 @@ module Mutations
super
end
+ def build_note_string(note)
+ commented = _('commented')
+ "@#{note.author.username} [#{commented}](#{noteable_note_url(note)}): '#{note.note}'"
+ end
+
def raise_noteable_not_incident!
raise_resource_not_available_error! 'Note does not belong to an incident'
end
diff --git a/app/graphql/mutations/releases/create.rb b/app/graphql/mutations/releases/create.rb
index 70a0e71c869..ba1fa8d446c 100644
--- a/app/graphql/mutations/releases/create.rb
+++ b/app/graphql/mutations/releases/create.rb
@@ -32,7 +32,7 @@ module Mutations
argument :released_at, Types::TimeType,
required: false,
- description: 'Date and time for the release. Defaults to the current date and time.'
+ description: 'Date and time for the release. Defaults to the current time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). Only provide this field if creating an upcoming or historical release.'
argument :milestones, [GraphQL::Types::String],
required: false,
diff --git a/app/graphql/mutations/todos/restore_many.rb b/app/graphql/mutations/todos/restore_many.rb
index fe0ad6df65b..20913a9e7da 100644
--- a/app/graphql/mutations/todos/restore_many.rb
+++ b/app/graphql/mutations/todos/restore_many.rb
@@ -32,9 +32,7 @@ module Mutations
private
def model_ids_of(ids)
- ids.map do |gid|
- gid.model_id.to_i
- end.compact
+ ids.filter_map { |gid| gid.model_id.to_i }
end
def raise_too_many_todos_requested_error
diff --git a/app/graphql/queries/repository/blob_info.query.graphql b/app/graphql/queries/repository/blob_info.query.graphql
new file mode 100644
index 00000000000..fd463436ed4
--- /dev/null
+++ b/app/graphql/queries/repository/blob_info.query.graphql
@@ -0,0 +1,62 @@
+query getBlobInfo(
+ $projectPath: ID!
+ $filePath: String!
+ $ref: String!
+ $shouldFetchRawText: Boolean!
+) {
+ project(fullPath: $projectPath) {
+ __typename
+ id
+ repository {
+ __typename
+ empty
+ blobs(paths: [$filePath], ref: $ref) {
+ __typename
+ nodes {
+ __typename
+ id
+ webPath
+ name
+ size
+ rawSize
+ rawTextBlob @include(if: $shouldFetchRawText)
+ fileType
+ language
+ path
+ blamePath
+ editBlobPath
+ gitpodBlobUrl
+ ideEditPath
+ forkAndEditPath
+ ideForkAndEditPath
+ codeNavigationPath
+ projectBlobPathRoot
+ forkAndViewPath
+ environmentFormattedExternalUrl
+ environmentExternalUrlForRouteMap
+ canModifyBlob
+ canCurrentUserPushToBranch
+ archived
+ storedExternally
+ externalStorage
+ externalStorageUrl
+ rawPath
+ replacePath
+ pipelineEditorPath
+ simpleViewer {
+ fileType
+ tooLarge
+ type
+ renderError
+ }
+ richViewer {
+ fileType
+ tooLarge
+ type
+ renderError
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/graphql/resolvers/ci/job_token_scope_resolver.rb b/app/graphql/resolvers/ci/job_token_scope_resolver.rb
index ca76a7b94fc..7c6aedad1d6 100644
--- a/app/graphql/resolvers/ci/job_token_scope_resolver.rb
+++ b/app/graphql/resolvers/ci/job_token_scope_resolver.rb
@@ -6,14 +6,12 @@ module Resolvers
include Gitlab::Graphql::Authorize::AuthorizeResource
authorize :admin_project
- description 'Container for resources that can be accessed by a CI job token from the current project. Null if job token scope setting is disabled.'
+ description 'Container for resources that can be accessed by a CI job token from the current project.'
type ::Types::Ci::JobTokenScopeType, null: true
def resolve
authorize!(object)
- return unless object.ci_job_token_scope_enabled?
-
::Ci::JobToken::Scope.new(object)
end
end
diff --git a/app/graphql/resolvers/ci/runner_jobs_resolver.rb b/app/graphql/resolvers/ci/runner_jobs_resolver.rb
index 2f6ca09d031..de00aadaea8 100644
--- a/app/graphql/resolvers/ci/runner_jobs_resolver.rb
+++ b/app/graphql/resolvers/ci/runner_jobs_resolver.rb
@@ -9,6 +9,7 @@ module Resolvers
type ::Types::Ci::JobType.connection_type, null: true
authorize :read_builds
authorizes_object!
+ extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
argument :statuses, [::Types::Ci::JobStatusEnum],
required: false,
@@ -16,15 +17,6 @@ module Resolvers
alias_method :runner, :object
- def ready?(**args)
- context[self.class] ||= { executions: 0 }
- context[self.class][:executions] += 1
-
- raise GraphQL::ExecutionError, "Jobs can be requested for only one runner at a time" if context[self.class][:executions] > 1
-
- super
- end
-
def resolve_with_lookahead(statuses: nil)
jobs = ::Ci::JobsFinder.new(current_user: current_user, runner: runner, params: { scope: statuses }).execute
diff --git a/app/graphql/resolvers/ci/runner_owner_project_resolver.rb b/app/graphql/resolvers/ci/runner_owner_project_resolver.rb
index 14b5f8f90eb..da8fab93619 100644
--- a/app/graphql/resolvers/ci/runner_owner_project_resolver.rb
+++ b/app/graphql/resolvers/ci/runner_owner_project_resolver.rb
@@ -9,7 +9,7 @@ module Resolvers
alias_method :runner, :object
- def resolve_with_lookahead(**args)
+ def resolve_with_lookahead(**_args)
resolve_owner
end
@@ -19,6 +19,8 @@ module Resolvers
}
end
+ private
+
def filtered_preloads
selection = lookahead
@@ -27,8 +29,6 @@ module Resolvers
end
end
- private
-
def resolve_owner
return unless runner.project_type?
@@ -48,14 +48,13 @@ module Resolvers
.transform_values { |runner_projects| runner_projects.first.project_id }
project_ids = owner_project_id_by_runner_id.values.uniq
- all_preloads = unconditional_includes + filtered_preloads
- owner_relation = Project.all
- owner_relation = owner_relation.preload(*all_preloads) if all_preloads.any?
- projects = owner_relation.where(id: project_ids).index_by(&:id)
+ projects = Project.where(id: project_ids)
+ Preloaders::ProjectPolicyPreloader.new(projects, current_user).execute
+ projects_by_id = projects.index_by(&:id)
runner_ids.each do |runner_id|
owner_project_id = owner_project_id_by_runner_id[runner_id]
- loader.call(runner_id, projects[owner_project_id])
+ loader.call(runner_id, projects_by_id[owner_project_id])
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/graphql/resolvers/ci/runner_projects_resolver.rb b/app/graphql/resolvers/ci/runner_projects_resolver.rb
new file mode 100644
index 00000000000..ca3b4ebb797
--- /dev/null
+++ b/app/graphql/resolvers/ci/runner_projects_resolver.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Ci
+ class RunnerProjectsResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+ include LooksAhead
+ include ProjectSearchArguments
+
+ type Types::ProjectType.connection_type, null: true
+ authorize :read_runner
+ authorizes_object!
+
+ alias_method :runner, :object
+
+ argument :sort, GraphQL::Types::String,
+ required: false,
+ default_value: 'id_asc', # TODO: Remove in %16.0 and move :sort to ProjectSearchArguments, see https://gitlab.com/gitlab-org/gitlab/-/issues/372117
+ deprecated: {
+ reason: 'Default sort order will change in 16.0. ' \
+ 'Specify `"id_asc"` if query results\' order is important',
+ milestone: '15.4'
+ },
+ description: "Sort order of results. Format: '<field_name>_<sort_direction>', " \
+ "for example: 'id_desc' or 'name_asc'"
+
+ def resolve_with_lookahead(**args)
+ return unless runner.project_type?
+
+ # rubocop:disable CodeReuse/ActiveRecord
+ BatchLoader::GraphQL.for(runner.id).batch(key: :runner_projects) do |runner_ids, loader|
+ plucked_runner_and_project_ids = ::Ci::RunnerProject
+ .select(:runner_id, :project_id)
+ .where(runner_id: runner_ids)
+ .pluck(:runner_id, :project_id)
+
+ project_ids = plucked_runner_and_project_ids.collect { |_runner_id, project_id| project_id }.uniq
+ projects = ProjectsFinder
+ .new(current_user: current_user,
+ params: project_finder_params(args),
+ project_ids_relation: project_ids)
+ .execute
+ Preloaders::ProjectPolicyPreloader.new(projects, current_user).execute
+ projects_by_id = projects.index_by(&:id)
+
+ # In plucked_runner_and_project_ids, first() represents the runner ID, and second() the project ID,
+ # so let's group the project IDs by runner ID
+ runner_project_ids_by_runner_id =
+ plucked_runner_and_project_ids
+ .group_by(&:first)
+ .transform_values { |values| values.map(&:second).filter_map { |project_id| projects_by_id[project_id] } }
+
+ runner_ids.each do |runner_id|
+ runner_projects = runner_project_ids_by_runner_id[runner_id] || []
+
+ loader.call(runner_id, runner_projects)
+ end
+ end
+ # rubocop:enable CodeReuse/ActiveRecord
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/ci/test_suite_resolver.rb b/app/graphql/resolvers/ci/test_suite_resolver.rb
index f758e217b47..a2d3af9c664 100644
--- a/app/graphql/resolvers/ci/test_suite_resolver.rb
+++ b/app/graphql/resolvers/ci/test_suite_resolver.rb
@@ -28,7 +28,8 @@ module Resolvers
def load_test_suite_data(builds)
suite = builds.sum do |build|
- build.collect_test_reports!(Gitlab::Ci::Reports::TestReport.new)
+ test_report = build.collect_test_reports!(Gitlab::Ci::Reports::TestReport.new)
+ test_report.get_suite(build.test_suite_name)
end
Gitlab::Ci::Reports::TestFailureHistory.new(suite.failed.values, pipeline.project).load!
diff --git a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb
index fe213936f55..8295bd58388 100644
--- a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb
+++ b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb
@@ -76,24 +76,11 @@ module IssueResolverArguments
end
def resolve_with_lookahead(**args)
- # The project could have been loaded in batch by `BatchLoader`.
- # At this point we need the `id` of the project to query for issues, so
- # make sure it's loaded and not `nil` before continuing.
- parent = object.respond_to?(:sync) ? object.sync : object
- return Issue.none if parent.nil?
-
- # Will need to be made group & namespace aware with
- # https://gitlab.com/gitlab-org/gitlab-foss/issues/54520
- args[:not] = args[:not].to_h if args[:not].present?
- args[:iids] ||= [args.delete(:iid)].compact if args[:iid]
- args[:attempt_project_search_optimizations] = true if args[:search].present?
+ return Issue.none if resource_parent.nil?
- prepare_assignee_username_params(args)
- prepare_release_tag_params(args)
+ finder = IssuesFinder.new(current_user, prepare_finder_params(args))
- finder = IssuesFinder.new(current_user, args)
-
- continue_issue_resolve(parent, finder, **args)
+ continue_issue_resolve(resource_parent, finder, **args)
end
def ready?(**args)
@@ -103,7 +90,6 @@ module IssueResolverArguments
params_not_mutually_exclusive(args, mutually_exclusive_milestone_args)
params_not_mutually_exclusive(args.fetch(:not, {}), mutually_exclusive_milestone_args)
params_not_mutually_exclusive(args, mutually_exclusive_release_tag_args)
- validate_anonymous_search_access! if args[:search].present?
super
end
@@ -128,6 +114,17 @@ module IssueResolverArguments
private
+ def prepare_finder_params(args)
+ params = super(args)
+ params[:iids] ||= [params.delete(:iid)].compact if params[:iid]
+ params[:attempt_project_search_optimizations] = true if params[:search].present?
+
+ prepare_assignee_username_params(params)
+ prepare_release_tag_params(params)
+
+ params
+ end
+
def prepare_release_tag_params(args)
release_tag_wildcard = args.delete(:release_tag_wildcard_id)
return if release_tag_wildcard.blank?
@@ -135,20 +132,13 @@ module IssueResolverArguments
args[:release_tag] ||= release_tag_wildcard
end
- def mutually_exclusive_release_tag_args
- [:release_tag, :release_tag_wildcard_id]
- end
-
def prepare_assignee_username_params(args)
args[:assignee_username] = args.delete(:assignee_usernames) if args[:assignee_usernames].present?
args[:not][:assignee_username] = args[:not].delete(:assignee_usernames) if args.dig(:not, :assignee_usernames).present?
end
- def params_not_mutually_exclusive(args, mutually_exclusive_args)
- if args.slice(*mutually_exclusive_args).compact.size > 1
- arg_str = mutually_exclusive_args.map { |x| x.to_s.camelize(:lower) }.join(', ')
- raise ::Gitlab::Graphql::Errors::ArgumentError, "only one of [#{arg_str}] arguments is allowed at the same time."
- end
+ def mutually_exclusive_release_tag_args
+ [:release_tag, :release_tag_wildcard_id]
end
def mutually_exclusive_milestone_args
@@ -158,4 +148,20 @@ module IssueResolverArguments
def mutually_exclusive_assignee_username_args
[:assignee_usernames, :assignee_username]
end
+
+ def params_not_mutually_exclusive(args, mutually_exclusive_args)
+ if args.slice(*mutually_exclusive_args).compact.size > 1
+ arg_str = mutually_exclusive_args.map { |x| x.to_s.camelize(:lower) }.join(', ')
+ raise ::Gitlab::Graphql::Errors::ArgumentError, "only one of [#{arg_str}] arguments is allowed at the same time."
+ end
+ end
+
+ def resource_parent
+ # The project could have been loaded in batch by `BatchLoader`.
+ # At this point we need the `id` of the project to query for issues, so
+ # make sure it's loaded and not `nil` before continuing.
+ strong_memoize(:resource_parent) do
+ object.respond_to?(:sync) ? object.sync : object
+ end
+ end
end
diff --git a/app/graphql/resolvers/concerns/looks_ahead.rb b/app/graphql/resolvers/concerns/looks_ahead.rb
index 644b2a11460..b548dc1e175 100644
--- a/app/graphql/resolvers/concerns/looks_ahead.rb
+++ b/app/graphql/resolvers/concerns/looks_ahead.rb
@@ -33,10 +33,14 @@ module LooksAhead
end
def filtered_preloads
- selection = node_selection
+ nodes = node_selection
+
+ return [] unless nodes
+
+ selected_fields = nodes.selections.map(&:name)
preloads.each.flat_map do |name, requirements|
- selection&.selects?(name) ? requirements : []
+ selected_fields.include?(name) ? requirements : []
end
end
diff --git a/app/graphql/resolvers/concerns/project_search_arguments.rb b/app/graphql/resolvers/concerns/project_search_arguments.rb
new file mode 100644
index 00000000000..7e03963f412
--- /dev/null
+++ b/app/graphql/resolvers/concerns/project_search_arguments.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module ProjectSearchArguments
+ extend ActiveSupport::Concern
+
+ included do
+ argument :membership, GraphQL::Types::Boolean,
+ required: false,
+ description: 'Return only projects that the current user is a member of.'
+
+ argument :search, GraphQL::Types::String,
+ required: false,
+ description: 'Search query, which can be for the project name, a path, or a description.'
+
+ argument :search_namespaces, GraphQL::Types::Boolean,
+ required: false,
+ description: 'Include namespace in project search.'
+
+ argument :topics, type: [GraphQL::Types::String],
+ required: false,
+ description: 'Filter projects by topics.'
+ end
+
+ private
+
+ def project_finder_params(params)
+ {
+ without_deleted: true,
+ non_public: params[:membership],
+ search: params[:search],
+ search_namespaces: params[:search_namespaces],
+ sort: params[:sort],
+ topic: params[:topics]
+ }.compact
+ end
+end
diff --git a/app/graphql/resolvers/concerns/search_arguments.rb b/app/graphql/resolvers/concerns/search_arguments.rb
index 7f480f9d0b6..95c6dbf7497 100644
--- a/app/graphql/resolvers/concerns/search_arguments.rb
+++ b/app/graphql/resolvers/concerns/search_arguments.rb
@@ -7,12 +7,49 @@ module SearchArguments
argument :search, GraphQL::Types::String,
required: false,
description: 'Search query for title or description.'
+ argument :in, [Types::IssuableSearchableFieldEnum],
+ required: false,
+ description: <<~DESC
+ Specify the fields to perform the search in.
+ Defaults to `[TITLE, DESCRIPTION]`. Requires the `search` argument.'
+ DESC
+ end
+
+ def ready?(**args)
+ validate_search_in_params!(args)
+ validate_anonymous_search_access!(args)
+
+ super
end
- def validate_anonymous_search_access!
+ private
+
+ def validate_anonymous_search_access!(args)
+ return unless args[:search].present?
return if current_user.present? || Feature.disabled?(:disable_anonymous_search, type: :ops)
raise ::Gitlab::Graphql::Errors::ArgumentError,
"User must be authenticated to include the `search` argument."
end
+
+ def validate_search_in_params!(args)
+ return unless args[:in].present? && args[:search].blank?
+
+ raise Gitlab::Graphql::Errors::ArgumentError,
+ '`search` should be present when including the `in` argument'
+ end
+
+ def prepare_finder_params(args)
+ prepare_search_params(args)
+ end
+
+ def prepare_search_params(args)
+ return args unless args[:search].present?
+
+ parent_type = resource_parent.is_a?(Project) ? :project : :group
+ args[:"attempt_#{parent_type}_search_optimizations"] = true
+ args[:in] = args[:in].join(',') if args[:in].present?
+
+ args
+ end
end
diff --git a/app/graphql/resolvers/crm/organization_state_counts_resolver.rb b/app/graphql/resolvers/crm/organization_state_counts_resolver.rb
new file mode 100644
index 00000000000..c16a4bd24ea
--- /dev/null
+++ b/app/graphql/resolvers/crm/organization_state_counts_resolver.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Crm
+ class OrganizationStateCountsResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ authorize :read_crm_organization
+ authorizes_object!
+
+ type Types::CustomerRelations::OrganizationStateCountsType, null: true
+
+ argument :search, GraphQL::Types::String,
+ required: false,
+ description: 'Search term to find organizations with.'
+
+ argument :state, Types::CustomerRelations::OrganizationStateEnum,
+ required: false,
+ description: 'State of the organizations to search for.'
+
+ def resolve(**args)
+ ::Crm::OrganizationsFinder.counts_by_state(context[:current_user], args.merge({ group: object }))
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/crm/organizations_resolver.rb b/app/graphql/resolvers/crm/organizations_resolver.rb
index ca0a908ee22..719834f406d 100644
--- a/app/graphql/resolvers/crm/organizations_resolver.rb
+++ b/app/graphql/resolvers/crm/organizations_resolver.rb
@@ -10,6 +10,11 @@ module Resolvers
type Types::CustomerRelations::OrganizationType, null: true
+ argument :sort, Types::CustomerRelations::OrganizationSortEnum,
+ description: 'Criteria to sort organizations by.',
+ required: false,
+ default_value: { field: 'name', direction: :asc }
+
argument :search, GraphQL::Types::String,
required: false,
description: 'Search term used to find organizations with.'
@@ -24,6 +29,7 @@ module Resolvers
def resolve(**args)
args[:ids] = resolve_ids(args.delete(:ids))
+ args.delete(:state) if args[:state] == :all
::Crm::OrganizationsFinder.new(current_user, { group: group }.merge(args)).execute
end
diff --git a/app/graphql/resolvers/deployment_resolver.rb b/app/graphql/resolvers/deployment_resolver.rb
new file mode 100644
index 00000000000..7d9ce0f023c
--- /dev/null
+++ b/app/graphql/resolvers/deployment_resolver.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class DeploymentResolver < BaseResolver
+ argument :iid,
+ GraphQL::Types::ID,
+ required: true,
+ description: 'Project-level internal ID of the Deployment.'
+
+ type Types::DeploymentType, null: true
+
+ alias_method :project, :object
+
+ def resolve(iid:)
+ return unless project.present? && project.is_a?(::Project)
+
+ Deployment.for_iid(project, iid)
+ end
+ end
+end
diff --git a/app/graphql/resolvers/deployments_resolver.rb b/app/graphql/resolvers/deployments_resolver.rb
new file mode 100644
index 00000000000..341d23c2ccb
--- /dev/null
+++ b/app/graphql/resolvers/deployments_resolver.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class DeploymentsResolver < BaseResolver
+ argument :statuses, [Types::DeploymentStatusEnum],
+ description: 'Statuses of the deployments.',
+ required: false,
+ as: :status
+
+ argument :order_by, Types::DeploymentsOrderByInputType,
+ description: 'Order by a specified field.',
+ required: false
+
+ type Types::DeploymentType, null: true
+
+ alias_method :environment, :object
+
+ def resolve(**args)
+ return unless environment.present? && environment.is_a?(::Environment)
+
+ args = transform_args_for_finder(**args)
+
+ # GraphQL BatchLoader shouldn't be used here because pagination query will be inefficient
+ # that fetches thousands of rows before limiting and offsetting.
+ DeploymentsFinder.new(environment: environment.id, **args).execute
+ end
+
+ private
+
+ def transform_args_for_finder(**args)
+ if (order_by = args.delete(:order_by))
+ order_by = order_by.to_h.map { |k, v| { order_by: k.to_s, sort: v } }.first
+ args.merge!(order_by)
+ end
+
+ args
+ end
+ end
+end
diff --git a/app/graphql/resolvers/environments/last_deployment_resolver.rb b/app/graphql/resolvers/environments/last_deployment_resolver.rb
new file mode 100644
index 00000000000..76f80112673
--- /dev/null
+++ b/app/graphql/resolvers/environments/last_deployment_resolver.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Environments
+ class LastDeploymentResolver < BaseResolver
+ argument :status,
+ Types::DeploymentStatusEnum,
+ required: true,
+ description: 'Status of the Deployment.'
+
+ type Types::DeploymentType, null: true
+
+ def resolve(status:)
+ return unless object.present? && object.is_a?(::Environment)
+
+ validate!(status)
+
+ find_last_deployment(status)
+ end
+
+ private
+
+ def find_last_deployment(status)
+ BatchLoader::GraphQL.for(object).batch(key: status) do |environments, loader, args|
+ association_name = "last_#{args[:key]}_deployment".to_sym
+
+ Preloaders::Environments::DeploymentPreloader.new(environments)
+ .execute_with_union(association_name, {})
+
+ environments.each do |environment|
+ loader.call(environment, environment.public_send(association_name)) # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
+ end
+
+ def validate!(status)
+ unless Deployment::FINISHED_STATUSES.include?(status.to_sym) ||
+ Deployment::UPCOMING_STATUSES.include?(status.to_sym)
+ raise Gitlab::Graphql::Errors::ArgumentError, "\"#{status}\" status is not supported."
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/environments_resolver.rb b/app/graphql/resolvers/environments_resolver.rb
index 934c1ba2738..f265e2183d0 100644
--- a/app/graphql/resolvers/environments_resolver.rb
+++ b/app/graphql/resolvers/environments_resolver.rb
@@ -21,8 +21,8 @@ module Resolvers
def resolve(**args)
return unless project.present?
- Environments::EnvironmentsFinder.new(project, context[:current_user], args).execute
- rescue Environments::EnvironmentsFinder::InvalidStatesError => e
+ ::Environments::EnvironmentsFinder.new(project, context[:current_user], args).execute
+ rescue ::Environments::EnvironmentsFinder::InvalidStatesError => e
raise Gitlab::Graphql::Errors::ArgumentError, e.message
end
end
diff --git a/app/graphql/resolvers/group_packages_resolver.rb b/app/graphql/resolvers/group_packages_resolver.rb
index b48e0b75190..e6a6abb39dd 100644
--- a/app/graphql/resolvers/group_packages_resolver.rb
+++ b/app/graphql/resolvers/group_packages_resolver.rb
@@ -5,6 +5,8 @@ module Resolvers
class GroupPackagesResolver < PackagesBaseResolver
# The GraphQL type is defined in the extended class
+ extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
+
argument :sort, Types::Packages::PackageGroupSortEnum,
description: 'Sort packages by this criteria.',
required: false,
@@ -15,14 +17,6 @@ module Resolvers
project_path_asc: { order_by: 'project_path', sort: 'asc' }
}).freeze
- def ready?(**args)
- context[self.class] ||= { executions: 0 }
- context[self.class][:executions] += 1
- raise GraphQL::ExecutionError, "Packages can be requested only for one group at a time" if context[self.class][:executions] > 1
-
- super
- end
-
def resolve(sort:, **filters)
return unless packages_available?
diff --git a/app/graphql/resolvers/members_resolver.rb b/app/graphql/resolvers/members_resolver.rb
index 827db54134a..3d7894fdd6a 100644
--- a/app/graphql/resolvers/members_resolver.rb
+++ b/app/graphql/resolvers/members_resolver.rb
@@ -11,6 +11,10 @@ module Resolvers
required: false,
description: 'Search query.'
+ argument :sort, ::Types::MemberSortEnum,
+ required: false,
+ description: 'sort query.'
+
def resolve_with_lookahead(**args)
authorize!(object)
diff --git a/app/graphql/resolvers/package_details_resolver.rb b/app/graphql/resolvers/package_details_resolver.rb
index 705d3900cd2..b77c6b1112b 100644
--- a/app/graphql/resolvers/package_details_resolver.rb
+++ b/app/graphql/resolvers/package_details_resolver.rb
@@ -2,20 +2,14 @@
module Resolvers
class PackageDetailsResolver < BaseResolver
+ extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
+
type ::Types::Packages::PackageDetailsType, null: true
argument :id, ::Types::GlobalIDType[::Packages::Package],
required: true,
description: 'Global ID of the package.'
- def ready?(**args)
- context[self.class] ||= { executions: 0 }
- context[self.class][:executions] += 1
- raise GraphQL::ExecutionError, "Package details can be requested only for one package at a time" if context[self.class][:executions] > 1
-
- super
- end
-
def resolve(id:)
GitlabSchema.find_by_gid(id)
end
diff --git a/app/graphql/resolvers/project_jobs_resolver.rb b/app/graphql/resolvers/project_jobs_resolver.rb
index b09158d475d..4d13a4a3fae 100644
--- a/app/graphql/resolvers/project_jobs_resolver.rb
+++ b/app/graphql/resolvers/project_jobs_resolver.rb
@@ -8,6 +8,7 @@ module Resolvers
type ::Types::Ci::JobType.connection_type, null: true
authorize :read_build
authorizes_object!
+ extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
argument :statuses, [::Types::Ci::JobStatusEnum],
required: false,
@@ -15,15 +16,6 @@ module Resolvers
alias_method :project, :object
- def ready?(**args)
- context[self.class] ||= { executions: 0 }
- context[self.class][:executions] += 1
-
- raise GraphQL::ExecutionError, "Jobs can be requested for only one project at a time" if context[self.class][:executions] > 1
-
- super
- end
-
def resolve_with_lookahead(statuses: nil)
jobs = ::Ci::JobsFinder.new(current_user: current_user, project: project, params: { scope: statuses }).execute
diff --git a/app/graphql/resolvers/projects/branch_rules_resolver.rb b/app/graphql/resolvers/projects/branch_rules_resolver.rb
new file mode 100644
index 00000000000..6c8b416bcea
--- /dev/null
+++ b/app/graphql/resolvers/projects/branch_rules_resolver.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Projects
+ class BranchRulesResolver < BaseResolver
+ type Types::Projects::BranchRuleType.connection_type, null: false
+
+ alias_method :project, :object
+
+ def resolve(**args)
+ project.protected_branches
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/projects_resolver.rb b/app/graphql/resolvers/projects_resolver.rb
index facf8ffe36f..4d1e1b867da 100644
--- a/app/graphql/resolvers/projects_resolver.rb
+++ b/app/graphql/resolvers/projects_resolver.rb
@@ -2,31 +2,18 @@
module Resolvers
class ProjectsResolver < BaseResolver
- type Types::ProjectType, null: true
-
- argument :membership, GraphQL::Types::Boolean,
- required: false,
- description: 'Limit projects that the current user is a member of.'
+ include ProjectSearchArguments
- argument :search, GraphQL::Types::String,
- required: false,
- description: 'Search query for project name, path, or description.'
+ type Types::ProjectType, null: true
argument :ids, [GraphQL::Types::ID],
required: false,
description: 'Filter projects by IDs.'
- argument :search_namespaces, GraphQL::Types::Boolean,
- required: false,
- description: 'Include namespace in project search.'
-
argument :sort, GraphQL::Types::String,
required: false,
- description: 'Sort order of results.'
-
- argument :topics, type: [GraphQL::Types::String],
- required: false,
- description: 'Filters projects by topics.'
+ description: "Sort order of results. Format: '<field_name>_<sort_direction>', " \
+ "for example: 'id_desc' or 'name_asc'"
def resolve(**args)
ProjectsFinder
@@ -36,17 +23,6 @@ module Resolvers
private
- def project_finder_params(params)
- {
- without_deleted: true,
- non_public: params[:membership],
- search: params[:search],
- search_namespaces: params[:search_namespaces],
- sort: params[:sort],
- topic: params[:topics]
- }.compact
- end
-
def parse_gids(gids)
gids&.map { |gid| GitlabSchema.parse_gid(gid, expected_type: ::Project).model_id }
end
diff --git a/app/graphql/resolvers/work_items_resolver.rb b/app/graphql/resolvers/work_items_resolver.rb
index 055984db3cb..9c7931a4edb 100644
--- a/app/graphql/resolvers/work_items_resolver.rb
+++ b/app/graphql/resolvers/work_items_resolver.rb
@@ -26,27 +26,31 @@ module Resolvers
required: false
def resolve_with_lookahead(**args)
- # The project could have been loaded in batch by `BatchLoader`.
- # At this point we need the `id` of the project to query for issues, so
- # make sure it's loaded and not `nil` before continuing.
- parent = object.respond_to?(:sync) ? object.sync : object
- return WorkItem.none if parent.nil? || !parent.work_items_feature_flag_enabled?
+ return WorkItem.none if resource_parent.nil? || !resource_parent.work_items_feature_flag_enabled?
- args[:iids] ||= [args.delete(:iid)].compact if args[:iid]
- args[:attempt_project_search_optimizations] = true if args[:search].present?
+ finder = ::WorkItems::WorkItemsFinder.new(current_user, prepare_finder_params(args))
- finder = ::WorkItems::WorkItemsFinder.new(current_user, args)
-
- Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all { |q| apply_lookahead(q) }
+ Gitlab::Graphql::Loaders::IssuableLoader.new(resource_parent, finder).batching_find_all { |q| apply_lookahead(q) }
end
- def ready?(**args)
- validate_anonymous_search_access! if args[:search].present?
+ private
- super
+ def preloads
+ {
+ last_edited_by: :last_edited_by
+ }
end
- private
+ # Allows to apply lookahead for fields
+ # selected from WidgetInterface
+ override :node_selection
+ def node_selection
+ selected_fields = super
+
+ return unless selected_fields
+
+ selected_fields.selection(:widgets)
+ end
def unconditional_includes
[
@@ -56,6 +60,22 @@ module Resolvers
:author
]
end
+
+ def prepare_finder_params(args)
+ params = super(args)
+ params[:iids] ||= [params.delete(:iid)].compact if params[:iid]
+
+ params
+ end
+
+ def resource_parent
+ # The project could have been loaded in batch by `BatchLoader`.
+ # At this point we need the `id` of the project to query for work items, so
+ # make sure it's loaded and not `nil` before continuing.
+ strong_memoize(:resource_parent) do
+ object.respond_to?(:sync) ? object.sync : object
+ end
+ end
end
end
diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb
index 1c43432594a..6f64e5b5053 100644
--- a/app/graphql/types/base_field.rb
+++ b/app/graphql/types/base_field.rb
@@ -17,8 +17,6 @@ module Types
@requires_argument = !!kwargs.delete(:requires_argument)
@authorize = Array.wrap(kwargs.delete(:authorize))
kwargs[:complexity] = field_complexity(kwargs[:resolver_class], kwargs[:complexity])
- @feature_flag = kwargs[:_deprecated_feature_flag]
- kwargs = check_feature_flag(kwargs)
@deprecation = gitlab_deprecation(kwargs)
after_connection_extensions = kwargs.delete(:late_extensions) || []
@@ -91,16 +89,8 @@ module Types
@constant_complexity
end
- def visible?(context)
- return false if feature_flag.present? && !Feature.enabled?(feature_flag)
-
- super
- end
-
private
- attr_reader :feature_flag
-
def field_authorized?(object, ctx)
object = object.node if object.is_a?(GraphQL::Pagination::Connection::Edge)
@@ -123,27 +113,6 @@ module Types
@authorization ||= ::Gitlab::Graphql::Authorize::ObjectAuthorization.new(@authorize)
end
- def feature_documentation_message(key, description)
- message_parts = ["#{description} Available only when feature flag `#{key}` is enabled."]
-
- message_parts << if Feature::Definition.has_definition?(key) && Feature::Definition.default_enabled?(key)
- "This flag is enabled by default."
- else
- "This flag is disabled by default, because the feature is experimental and is subject to change without notice."
- end
-
- message_parts.join(' ')
- end
-
- def check_feature_flag(args)
- ff = args.delete(:_deprecated_feature_flag)
- return args unless ff.present?
-
- args[:description] = feature_documentation_message(ff, args[:description])
-
- args
- end
-
def field_complexity(resolver_class, current)
return current if current.present? && current > 0
diff --git a/app/graphql/types/branch_protections/base_access_level_type.rb b/app/graphql/types/branch_protections/base_access_level_type.rb
new file mode 100644
index 00000000000..472733a6bc5
--- /dev/null
+++ b/app/graphql/types/branch_protections/base_access_level_type.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Types
+ module BranchProtections
+ class BaseAccessLevelType < Types::BaseObject
+ authorize :read_protected_branch
+
+ field :access_level,
+ type: GraphQL::Types::Int,
+ null: false,
+ description: 'GitLab::Access level.'
+
+ field :access_level_description,
+ type: GraphQL::Types::String,
+ null: false,
+ description: 'Human readable representation for this access level.',
+ hash_key: 'humanize'
+ end
+ end
+end
+
+Types::BranchProtections::BaseAccessLevelType.prepend_mod
diff --git a/app/graphql/types/branch_protections/merge_access_level_type.rb b/app/graphql/types/branch_protections/merge_access_level_type.rb
new file mode 100644
index 00000000000..85295e1ba25
--- /dev/null
+++ b/app/graphql/types/branch_protections/merge_access_level_type.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Types
+ module BranchProtections
+ class MergeAccessLevelType < BaseAccessLevelType # rubocop:disable Graphql/AuthorizeTypes
+ graphql_name 'MergeAccessLevel'
+ description 'Represents the merge access level of a branch protection.'
+ accepts ::ProtectedBranch::MergeAccessLevel
+ end
+ end
+end
diff --git a/app/graphql/types/branch_protections/push_access_level_type.rb b/app/graphql/types/branch_protections/push_access_level_type.rb
new file mode 100644
index 00000000000..bfbdc4edbea
--- /dev/null
+++ b/app/graphql/types/branch_protections/push_access_level_type.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Types
+ module BranchProtections
+ class PushAccessLevelType < BaseAccessLevelType # rubocop:disable Graphql/AuthorizeTypes
+ graphql_name 'PushAccessLevel'
+ description 'Represents the push access level of a branch protection.'
+ accepts ::ProtectedBranch::PushAccessLevel
+ end
+ end
+end
diff --git a/app/graphql/types/branch_rules/branch_protection_type.rb b/app/graphql/types/branch_rules/branch_protection_type.rb
new file mode 100644
index 00000000000..4177a6f92a1
--- /dev/null
+++ b/app/graphql/types/branch_rules/branch_protection_type.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Types
+ module BranchRules
+ class BranchProtectionType < BaseObject
+ graphql_name 'BranchProtection'
+ description 'Branch protection details for a branch rule.'
+ accepts ::ProtectedBranch
+ authorize :read_protected_branch
+
+ field :merge_access_levels,
+ type: Types::BranchProtections::MergeAccessLevelType.connection_type,
+ null: true,
+ description: 'Details about who can merge when this branch is the source branch.'
+
+ field :push_access_levels,
+ type: Types::BranchProtections::PushAccessLevelType.connection_type,
+ null: true,
+ description: 'Details about who can push when this branch is the source branch.'
+
+ field :allow_force_push,
+ type: GraphQL::Types::Boolean,
+ null: false,
+ description: 'Toggle force push to the branch for users with write access.'
+ end
+ end
+end
+
+Types::BranchRules::BranchProtectionType.prepend_mod_with('Types::BranchRules::BranchProtectionType')
diff --git a/app/graphql/types/ci/config_variable_type.rb b/app/graphql/types/ci/config_variable_type.rb
new file mode 100644
index 00000000000..87ae026c2c1
--- /dev/null
+++ b/app/graphql/types/ci/config_variable_type.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ class ConfigVariableType < BaseObject # rubocop:disable Graphql/AuthorizeTypes
+ graphql_name 'CiConfigVariable'
+ description 'CI/CD config variables.'
+
+ field :key, GraphQL::Types::String,
+ null: true,
+ description: 'Name of the variable.'
+
+ field :description, GraphQL::Types::String,
+ null: true,
+ description: 'Description for the CI/CD config variable.'
+
+ field :value, GraphQL::Types::String,
+ null: true,
+ description: 'Value of the variable.'
+ end
+ end
+end
diff --git a/app/graphql/types/ci/group_variable_connection_type.rb b/app/graphql/types/ci/group_variable_connection_type.rb
new file mode 100644
index 00000000000..1f55dde6697
--- /dev/null
+++ b/app/graphql/types/ci/group_variable_connection_type.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ # rubocop: disable Graphql/AuthorizeTypes
+ class GroupVariableConnectionType < GraphQL::Types::Relay::BaseConnection
+ field :limit, GraphQL::Types::Int,
+ null: false,
+ description: 'Maximum amount of group CI/CD variables.'
+
+ def limit
+ ::Plan.default.actual_limits.group_ci_variables
+ end
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/ci/group_variable_type.rb b/app/graphql/types/ci/group_variable_type.rb
index 3322f741342..f9ed54f0d10 100644
--- a/app/graphql/types/ci/group_variable_type.rb
+++ b/app/graphql/types/ci/group_variable_type.rb
@@ -7,19 +7,20 @@ module Types
graphql_name 'CiGroupVariable'
description 'CI/CD variables for a group.'
+ connection_type_class(Types::Ci::GroupVariableConnectionType)
implements(VariableInterface)
field :environment_scope, GraphQL::Types::String,
- null: true,
- description: 'Scope defining the environments that can use the variable.'
-
- field :protected, GraphQL::Types::Boolean,
- null: true,
- description: 'Indicates whether the variable is protected.'
+ null: true,
+ description: 'Scope defining the environments that can use the variable.'
field :masked, GraphQL::Types::Boolean,
- null: true,
- description: 'Indicates whether the variable is masked.'
+ null: true,
+ description: 'Indicates whether the variable is masked.'
+
+ field :protected, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates whether the variable is protected.'
end
end
end
diff --git a/app/graphql/types/ci/instance_variable_type.rb b/app/graphql/types/ci/instance_variable_type.rb
index f564a2f59a0..7ffc52deb73 100644
--- a/app/graphql/types/ci/instance_variable_type.rb
+++ b/app/graphql/types/ci/instance_variable_type.rb
@@ -9,21 +9,29 @@ module Types
implements(VariableInterface)
+ field :id, GraphQL::Types::ID,
+ null: false,
+ description: 'ID of the variable.'
+
field :environment_scope, GraphQL::Types::String,
- null: true,
- deprecated: {
- reason: 'No longer used, only available for GroupVariableType and ProjectVariableType',
- milestone: '15.3'
- },
- description: 'Scope defining the environments that can use the variable.'
+ null: true,
+ deprecated: {
+ reason: 'No longer used, only available for GroupVariableType and ProjectVariableType',
+ milestone: '15.3'
+ },
+ description: 'Scope defining the environments that can use the variable.'
field :protected, GraphQL::Types::Boolean,
- null: true,
- description: 'Indicates whether the variable is protected.'
+ null: true,
+ description: 'Indicates whether the variable is protected.'
field :masked, GraphQL::Types::Boolean,
- null: true,
- description: 'Indicates whether the variable is masked.'
+ null: true,
+ description: 'Indicates whether the variable is masked.'
+
+ field :raw, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates whether the variable is raw.'
def environment_scope
nil
diff --git a/app/graphql/types/ci/job_artifact_type.rb b/app/graphql/types/ci/job_artifact_type.rb
index a6ab445702c..6346d50de3a 100644
--- a/app/graphql/types/ci/job_artifact_type.rb
+++ b/app/graphql/types/ci/job_artifact_type.rb
@@ -6,6 +6,9 @@ module Types
class JobArtifactType < BaseObject
graphql_name 'CiJobArtifact'
+ field :id, Types::GlobalIDType[::Ci::JobArtifact], null: false,
+ description: 'ID of the artifact.'
+
field :download_path, GraphQL::Types::String, null: true,
description: "URL for downloading the artifact's file."
@@ -16,6 +19,12 @@ module Types
description: 'File name of the artifact.',
method: :filename
+ field :size, GraphQL::Types::Int, null: false,
+ description: 'Size of the artifact in bytes.'
+
+ field :expire_at, Types::TimeType, null: true,
+ description: 'Expiry date of the artifact.'
+
def download_path
::Gitlab::Routing.url_helpers.download_project_job_artifacts_path(
object.project,
diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb
index 4ea9a016e74..ab6103d9469 100644
--- a/app/graphql/types/ci/job_type.rb
+++ b/app/graphql/types/ci/job_type.rb
@@ -92,6 +92,8 @@ module Types
description: 'Indicates the job is stuck.'
field :triggered, GraphQL::Types::Boolean, null: true,
description: 'Whether the job was triggered.'
+ field :web_path, GraphQL::Types::String, null: true,
+ description: 'Web path of the job.'
def kind
return ::Ci::Build unless [::Ci::Build, ::Ci::Bridge].include?(object.class)
@@ -181,6 +183,10 @@ module Types
::Gitlab::Routing.url_helpers.project_commits_path(object.project, ref_name)
end
+ def web_path
+ ::Gitlab::Routing.url_helpers.project_job_path(object.project, object)
+ end
+
def coverage
object&.coverage
end
diff --git a/app/graphql/types/ci/manual_variable_type.rb b/app/graphql/types/ci/manual_variable_type.rb
index d6f59c1d249..ed92a6645b4 100644
--- a/app/graphql/types/ci/manual_variable_type.rb
+++ b/app/graphql/types/ci/manual_variable_type.rb
@@ -10,12 +10,12 @@ module Types
implements(VariableInterface)
field :environment_scope, GraphQL::Types::String,
- null: true,
- deprecated: {
- reason: 'No longer used, only available for GroupVariableType and ProjectVariableType',
- milestone: '15.3'
- },
- description: 'Scope defining the environments that can use the variable.'
+ null: true,
+ deprecated: {
+ reason: 'No longer used, only available for GroupVariableType and ProjectVariableType',
+ milestone: '15.3'
+ },
+ description: 'Scope defining the environments that can use the variable.'
def environment_scope
nil
diff --git a/app/graphql/types/ci/project_variable_connection_type.rb b/app/graphql/types/ci/project_variable_connection_type.rb
new file mode 100644
index 00000000000..c3cdc425f10
--- /dev/null
+++ b/app/graphql/types/ci/project_variable_connection_type.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ # rubocop: disable Graphql/AuthorizeTypes
+ class ProjectVariableConnectionType < GraphQL::Types::Relay::BaseConnection
+ field :limit, GraphQL::Types::Int,
+ null: false,
+ description: 'Maximum amount of project CI/CD variables.'
+
+ def limit
+ ::Plan.default.actual_limits.project_ci_variables
+ end
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/ci/project_variable_type.rb b/app/graphql/types/ci/project_variable_type.rb
index 625bb7fd4b1..2a5375045e5 100644
--- a/app/graphql/types/ci/project_variable_type.rb
+++ b/app/graphql/types/ci/project_variable_type.rb
@@ -7,19 +7,20 @@ module Types
graphql_name 'CiProjectVariable'
description 'CI/CD variables for a project.'
+ connection_type_class(Types::Ci::ProjectVariableConnectionType)
implements(VariableInterface)
field :environment_scope, GraphQL::Types::String,
- null: true,
- description: 'Scope defining the environments that can use the variable.'
+ null: true,
+ description: 'Scope defining the environments that can use the variable.'
field :protected, GraphQL::Types::Boolean,
- null: true,
- description: 'Indicates whether the variable is protected.'
+ null: true,
+ description: 'Indicates whether the variable is protected.'
field :masked, GraphQL::Types::Boolean,
- null: true,
- description: 'Indicates whether the variable is masked.'
+ null: true,
+ description: 'Indicates whether the variable is masked.'
end
end
end
diff --git a/app/graphql/types/ci/runner_membership_filter_enum.rb b/app/graphql/types/ci/runner_membership_filter_enum.rb
index 2e1051b2151..4fd7e0749b0 100644
--- a/app/graphql/types/ci/runner_membership_filter_enum.rb
+++ b/app/graphql/types/ci/runner_membership_filter_enum.rb
@@ -3,15 +3,17 @@
module Types
module Ci
class RunnerMembershipFilterEnum < BaseEnum
- graphql_name 'RunnerMembershipFilter'
- description 'Values for filtering runners in namespaces.'
+ graphql_name 'CiRunnerMembershipFilter'
+ description 'Values for filtering runners in namespaces. ' \
+ 'The previous type name `RunnerMembershipFilter` was deprecated in 15.4.'
value 'DIRECT',
description: "Include runners that have a direct relationship.",
value: :direct
value 'DESCENDANTS',
- description: "Include runners that have either a direct relationship or a relationship with descendants. These can be project runners or group runners (in the case where group is queried).",
+ description: "Include runners that have either a direct or inherited relationship. " \
+ "These runners can be specific to a project or a group.",
value: :descendants
end
end
diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb
index 0afb61d2b64..a9c76974850 100644
--- a/app/graphql/types/ci/runner_type.rb
+++ b/app/graphql/types/ci/runner_type.rb
@@ -52,7 +52,7 @@ module Types
field :job_count, GraphQL::Types::Int, null: true,
description: "Number of jobs processed by the runner (limited to #{JOB_COUNT_LIMIT}, plus one to indicate that more items exist)."
field :jobs, ::Types::Ci::JobType.connection_type, null: true,
- description: 'Jobs assigned to the runner.',
+ description: 'Jobs assigned to the runner. This field can only be resolved for one runner in any single request.',
authorize: :read_builds,
resolver: ::Resolvers::Ci::RunnerJobsResolver
field :locked, GraphQL::Types::Boolean, null: true,
@@ -63,8 +63,11 @@ module Types
description: 'Indicates the runner is paused and not available to run jobs.'
field :project_count, GraphQL::Types::Int, null: true,
description: 'Number of projects that the runner is associated with.'
- field :projects, ::Types::ProjectType.connection_type, null: true,
- description: 'Projects the runner is associated with. For project runners only.'
+ field :projects,
+ ::Types::ProjectType.connection_type,
+ null: true,
+ resolver: ::Resolvers::Ci::RunnerProjectsResolver,
+ description: 'Find projects the runner is associated with. For project runners only.'
field :revision, GraphQL::Types::String, null: true,
description: 'Revision of the runner.'
field :run_untagged, GraphQL::Types::Boolean, null: false,
@@ -131,12 +134,6 @@ module Types
batched_owners(::Ci::RunnerNamespace, Group, :runner_groups, :namespace_id)
end
- def projects
- return unless runner.project_type?
-
- batched_owners(::Ci::RunnerProject, Project, :runner_projects, :project_id)
- end
-
private
def can_admin_runners?
@@ -159,19 +156,12 @@ module Types
owner_ids = runner_owner_ids_by_runner_id.values.flatten.uniq
owners = assoc_type.where(id: owner_ids).index_by(&:id)
- # Preload projects namespaces to avoid N+1 queries when checking the `read_project` policy for each
- preload_projects_namespaces(owners.values) if assoc_type == Project
-
runner_ids.each do |runner_id|
loader.call(runner_id, runner_owner_ids_by_runner_id[runner_id]&.map { |owner_id| owners[owner_id] } || [])
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
-
- def preload_projects_namespaces(_projects)
- # overridden in EE
- end
end
end
end
diff --git a/app/graphql/types/ci/variable_interface.rb b/app/graphql/types/ci/variable_interface.rb
index 82c9ba7121c..ec68d3c987c 100644
--- a/app/graphql/types/ci/variable_interface.rb
+++ b/app/graphql/types/ci/variable_interface.rb
@@ -8,24 +8,24 @@ module Types
graphql_name 'CiVariable'
field :id, GraphQL::Types::ID,
- null: false,
- description: 'ID of the variable.'
+ null: false,
+ description: 'ID of the variable.'
field :key, GraphQL::Types::String,
- null: true,
- description: 'Name of the variable.'
+ null: true,
+ description: 'Name of the variable.'
+
+ field :raw, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates whether the variable is raw.'
field :value, GraphQL::Types::String,
- null: true,
- description: 'Value of the variable.'
+ null: true,
+ description: 'Value of the variable.'
field :variable_type, ::Types::Ci::VariableTypeEnum,
- null: true,
- description: 'Type of the variable.'
-
- field :raw, GraphQL::Types::Boolean,
- null: true,
- description: 'Indicates whether the variable is raw.'
+ null: true,
+ description: 'Type of the variable.'
end
end
end
diff --git a/app/graphql/types/clusters/agent_type.rb b/app/graphql/types/clusters/agent_type.rb
index 546252b2285..5d7b8815cde 100644
--- a/app/graphql/types/clusters/agent_type.rb
+++ b/app/graphql/types/clusters/agent_type.rb
@@ -71,3 +71,5 @@ module Types
end
end
end
+
+Types::Clusters::AgentType.prepend_mod
diff --git a/app/graphql/types/customer_relations/contact_sort_enum.rb b/app/graphql/types/customer_relations/contact_sort_enum.rb
index 221dedacb6a..bb11d741368 100644
--- a/app/graphql/types/customer_relations/contact_sort_enum.rb
+++ b/app/graphql/types/customer_relations/contact_sort_enum.rb
@@ -11,10 +11,10 @@ module Types
sortable_fields.each do |field|
value "#{field.upcase.tr(' ', '_')}_ASC",
value: { field: field.downcase.tr(' ', '_'), direction: :asc },
- description: "#{field} by ascending order."
+ description: "#{field} in ascending order."
value "#{field.upcase.tr(' ', '_')}_DESC",
value: { field: field.downcase.tr(' ', '_'), direction: :desc },
- description: "#{field} by descending order."
+ description: "#{field} in descending order."
end
end
end
diff --git a/app/graphql/types/customer_relations/organization_sort_enum.rb b/app/graphql/types/customer_relations/organization_sort_enum.rb
new file mode 100644
index 00000000000..742a5a4fa99
--- /dev/null
+++ b/app/graphql/types/customer_relations/organization_sort_enum.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Types
+ module CustomerRelations
+ class OrganizationSortEnum < SortEnum
+ graphql_name 'OrganizationSort'
+ description 'Values for sorting organizations'
+
+ sortable_fields = ['Name', 'Description', 'Default Rate']
+
+ sortable_fields.each do |field|
+ value "#{field.upcase.tr(' ', '_')}_ASC",
+ value: { field: field.downcase.tr(' ', '_'), direction: :asc },
+ description: "#{field} in ascending order."
+ value "#{field.upcase.tr(' ', '_')}_DESC",
+ value: { field: field.downcase.tr(' ', '_'), direction: :desc },
+ description: "#{field} in descending order."
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/customer_relations/organization_state_counts_type.rb b/app/graphql/types/customer_relations/organization_state_counts_type.rb
new file mode 100644
index 00000000000..7d813209a8e
--- /dev/null
+++ b/app/graphql/types/customer_relations/organization_state_counts_type.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Types
+ module CustomerRelations
+ # `object` is a hash. Authorization is performed by OrganizationStateCountsResolver
+ class OrganizationStateCountsType < Types::BaseObject # rubocop:disable Graphql/AuthorizeTypes
+ graphql_name 'OrganizationStateCounts'
+ description 'Represents the total number of organizations for the represented states.'
+
+ AVAILABLE_STATES = ::CustomerRelations::Organization.states.keys.push('all').freeze
+
+ AVAILABLE_STATES.each do |state|
+ field state,
+ GraphQL::Types::Int,
+ null: true,
+ description: "Number of organizations with state `#{state.upcase}`"
+ end
+
+ def all
+ object.values.sum
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/customer_relations/organization_state_enum.rb b/app/graphql/types/customer_relations/organization_state_enum.rb
index ecdd7d092ad..84bbbbc90fc 100644
--- a/app/graphql/types/customer_relations/organization_state_enum.rb
+++ b/app/graphql/types/customer_relations/organization_state_enum.rb
@@ -5,12 +5,16 @@ module Types
class OrganizationStateEnum < BaseEnum
graphql_name 'CustomerRelationsOrganizationState'
+ value 'all',
+ description: "All available organizations.",
+ value: :all
+
value 'active',
- description: "Active organization.",
+ description: "Active organizations.",
value: :active
value 'inactive',
- description: "Inactive organization.",
+ description: "Inactive organizations.",
value: :inactive
end
end
diff --git a/app/graphql/types/deployment_details_type.rb b/app/graphql/types/deployment_details_type.rb
new file mode 100644
index 00000000000..f8ba0cb1b24
--- /dev/null
+++ b/app/graphql/types/deployment_details_type.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Types
+ class DeploymentDetailsType < DeploymentType
+ graphql_name 'DeploymentDetails'
+ description 'The details of the deployment'
+ authorize :read_deployment
+ present_using Deployments::DeploymentPresenter
+
+ field :tags,
+ [Types::DeploymentTagType],
+ description: 'Git tags that contain this deployment.',
+ calls_gitaly: true
+ end
+end
diff --git a/app/graphql/types/deployment_status_enum.rb b/app/graphql/types/deployment_status_enum.rb
new file mode 100644
index 00000000000..7ef69d3f1c1
--- /dev/null
+++ b/app/graphql/types/deployment_status_enum.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ class DeploymentStatusEnum < BaseEnum
+ graphql_name 'DeploymentStatus'
+ description 'All deployment statuses.'
+
+ ::Deployment.statuses.each_key do |status|
+ value status.upcase,
+ description: "A deployment that is #{status.tr('_', ' ')}.",
+ value: status
+ end
+ end
+end
diff --git a/app/graphql/types/deployment_tag_type.rb b/app/graphql/types/deployment_tag_type.rb
new file mode 100644
index 00000000000..bc3597404a2
--- /dev/null
+++ b/app/graphql/types/deployment_tag_type.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Types
+ # DeploymentTagType is a hash, authorized by the deployment
+ # rubocop:disable Graphql/AuthorizeTypes
+ class DeploymentTagType < BaseObject
+ graphql_name 'DeploymentTag'
+ description 'Tags for a given deployment'
+
+ field :name,
+ GraphQL::Types::String,
+ description: 'Name of this git tag.'
+
+ field :path,
+ GraphQL::Types::String,
+ description: 'Path for this tag.',
+ hash_key: :path
+ end
+ # rubocop:enable Graphql/AuthorizeTypes
+end
diff --git a/app/graphql/types/deployment_type.rb b/app/graphql/types/deployment_type.rb
new file mode 100644
index 00000000000..70a3a4cb574
--- /dev/null
+++ b/app/graphql/types/deployment_type.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module Types
+ # If you're considering to add a new field in DeploymentType, please follow this guideline:
+ # - If the field is preloadable in batch, define it in DeploymentType.
+ # In this case, you should extend DeploymentsResolver logic to preload the field. Also, add a new test that
+ # fetching the specific field for multiple deployments doesn't cause N+1 query problem.
+ # - If the field is NOT preloadable in batch, define it in DeploymentDetailsType.
+ # This type can be only fetched for a single deployment, so you don't need to take care of the preloading.
+ class DeploymentType < BaseObject
+ graphql_name 'Deployment'
+ description 'The deployment of an environment'
+
+ present_using Deployments::DeploymentPresenter
+
+ authorize :read_deployment
+
+ field :id,
+ GraphQL::Types::ID,
+ description: 'Global ID of the deployment.'
+
+ field :iid,
+ GraphQL::Types::ID,
+ description: 'Project-level internal ID of the deployment.'
+
+ field :ref,
+ GraphQL::Types::String,
+ description: 'Git-Ref that the deployment ran on.'
+
+ field :tag,
+ GraphQL::Types::Boolean,
+ description: 'True or false if the deployment ran on a Git-tag.'
+
+ field :sha,
+ GraphQL::Types::String,
+ description: 'Git-SHA that the deployment ran on.'
+
+ field :created_at,
+ Types::TimeType,
+ description: 'When the deployment record was created.'
+
+ field :updated_at,
+ Types::TimeType,
+ description: 'When the deployment record was updated.'
+
+ field :finished_at,
+ Types::TimeType,
+ description: 'When the deployment finished.'
+
+ field :status,
+ Types::DeploymentStatusEnum,
+ description: 'Status of the deployment.'
+
+ field :commit,
+ Types::CommitType,
+ description: 'Commit details of the deployment.',
+ calls_gitaly: true
+
+ field :job,
+ Types::Ci::JobType,
+ description: 'Pipeline job of the deployment.',
+ method: :build
+
+ field :triggerer,
+ Types::UserType,
+ description: 'User who executed the deployment.',
+ method: :deployed_by
+ end
+end
diff --git a/app/graphql/types/deployments_order_by_input_type.rb b/app/graphql/types/deployments_order_by_input_type.rb
new file mode 100644
index 00000000000..a87fef9fe8a
--- /dev/null
+++ b/app/graphql/types/deployments_order_by_input_type.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Types
+ class DeploymentsOrderByInputType < BaseInputObject
+ graphql_name 'DeploymentsOrderByInput'
+ description 'Values for ordering deployments by a specific field'
+
+ argument :created_at,
+ Types::SortDirectionEnum,
+ required: false,
+ description: 'Order by Created time.'
+
+ argument :finished_at,
+ Types::SortDirectionEnum,
+ required: false,
+ description: 'Order by Finished time.'
+
+ def prepare
+ raise GraphQL::ExecutionError, 'orderBy parameter must contain one key-value pair.' unless to_h.size == 1
+
+ super
+ end
+ end
+end
diff --git a/app/graphql/types/environment_type.rb b/app/graphql/types/environment_type.rb
index 2a7076cc3c9..eb4e7b1dabf 100644
--- a/app/graphql/types/environment_type.rb
+++ b/app/graphql/types/environment_type.rb
@@ -21,6 +21,30 @@ module Types
field :path, GraphQL::Types::String, null: false,
description: 'Path to the environment.'
+ field :slug, GraphQL::Types::String,
+ description: 'Slug of the environment.'
+
+ field :external_url, GraphQL::Types::String, null: true,
+ description: 'External URL of the environment.'
+
+ field :created_at, Types::TimeType,
+ description: 'When the environment was created.'
+
+ field :updated_at, Types::TimeType,
+ description: 'When the environment was updated.'
+
+ field :auto_stop_at, Types::TimeType,
+ description: 'When the environment is going to be stopped automatically.'
+
+ field :auto_delete_at, Types::TimeType,
+ description: 'When the environment is going to be deleted automatically.'
+
+ field :tier, Types::DeploymentTierEnum,
+ description: 'Deployment tier of the environment.'
+
+ field :environment_type, GraphQL::Types::String,
+ description: 'Folder name of the environment.'
+
field :metrics_dashboard, Types::Metrics::DashboardType, null: true,
description: 'Metrics dashboard schema for the environment.',
resolver: Resolvers::Metrics::DashboardResolver
@@ -29,5 +53,22 @@ module Types
Types::AlertManagement::AlertType,
null: true,
description: 'Most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned.'
+
+ field :deployments,
+ Types::DeploymentType.connection_type,
+ null: true,
+ description: 'Deployments of the environment. This field can only be resolved for one project in any single request.',
+ resolver: Resolvers::DeploymentsResolver do
+ extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
+ end
+
+ field :last_deployment,
+ Types::DeploymentType,
+ description: 'Last deployment of the environment.',
+ resolver: Resolvers::Environments::LastDeploymentResolver
+
+ def tier
+ object.tier.to_sym
+ end
end
end
diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb
index 235a2bc2a34..45357de5502 100644
--- a/app/graphql/types/group_type.rb
+++ b/app/graphql/types/group_type.rb
@@ -22,7 +22,7 @@ module Types
type: Types::CustomEmojiType.connection_type,
null: true,
description: 'Custom emoji within this namespace.',
- _deprecated_feature_flag: :custom_emoji
+ alpha: { milestone: '13.6' }
field :share_with_group_lock,
type: GraphQL::Types::Boolean,
@@ -134,7 +134,7 @@ module Types
description: 'Number of container repositories in the group.'
field :packages,
- description: 'Packages of the group.',
+ description: 'Packages of the group. This field can only be resolved for one group in any single request.',
resolver: Resolvers::GroupPackagesResolver
field :dependency_proxy_setting,
@@ -212,6 +212,12 @@ module Types
description: "Find organizations of this group.",
resolver: Resolvers::Crm::OrganizationsResolver
+ field :organization_state_counts,
+ Types::CustomerRelations::OrganizationStateCountsType,
+ null: true,
+ description: 'Counts of organizations by status for the group.',
+ resolver: Resolvers::Crm::OrganizationStateCountsResolver
+
field :contacts, Types::CustomerRelations::ContactType.connection_type,
null: true,
description: "Find contacts of this group.",
@@ -272,6 +278,10 @@ module Types
group.dependency_proxy_setting || group.create_dependency_proxy_setting
end
+ def custom_emoji
+ object.custom_emoji if Feature.enabled?(:custom_emoji)
+ end
+
private
def group
diff --git a/app/graphql/types/member_sort_enum.rb b/app/graphql/types/member_sort_enum.rb
new file mode 100644
index 00000000000..f3291dda13b
--- /dev/null
+++ b/app/graphql/types/member_sort_enum.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Types
+ class MemberSortEnum < SortEnum
+ graphql_name 'MemberSort'
+ description 'Values for sorting members'
+
+ value 'ACCESS_LEVEL_ASC', 'Access level ascending order.', value: :access_level_asc
+ value 'ACCESS_LEVEL_DESC', 'Access level descending order.', value: :access_level_desc
+ value 'USER_FULL_NAME_ASC', "User's full name ascending order.", value: :name_asc
+ value 'USER_FULL_NAME_DESC', "User's full name descending order.", value: :name_desc
+ end
+end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index d88653f2f8c..399dcc8e03d 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -94,9 +94,10 @@ module Types
method: :public_merge_status, null: true,
description: 'Merge status of the merge request.'
- field :detailed_merge_status, ::Types::MergeRequests::DetailedMergeStatusEnum, method: :detailed_merge_status, null: true,
+ field :detailed_merge_status, ::Types::MergeRequests::DetailedMergeStatusEnum, null: true,
calls_gitaly: true,
- description: 'Detailed merge status of the merge request.', alpha: { milestone: '15.3' }
+ description: 'Detailed merge status of the merge request.',
+ alpha: { milestone: '15.3' }
field :mergeable_discussions_state, GraphQL::Types::Boolean, null: true,
calls_gitaly: true,
@@ -280,6 +281,10 @@ module Types
def merge_user
object.metrics&.merged_by || object.merge_user
end
+
+ def detailed_merge_status
+ ::MergeRequests::Mergeability::DetailedMergeStatusService.new(merge_request: object).execute
+ end
end
end
diff --git a/app/graphql/types/merge_requests/detailed_merge_status_enum.rb b/app/graphql/types/merge_requests/detailed_merge_status_enum.rb
index 58104159303..3de6296154d 100644
--- a/app/graphql/types/merge_requests/detailed_merge_status_enum.rb
+++ b/app/graphql/types/merge_requests/detailed_merge_status_enum.rb
@@ -21,6 +21,9 @@ module Types
value 'CI_MUST_PASS',
value: :ci_must_pass,
description: 'Pipeline must succeed before merging.'
+ value 'CI_STILL_RUNNING',
+ value: :ci_still_running,
+ description: 'Pipeline is still running.'
value 'DISCUSSIONS_NOT_RESOLVED',
value: :discussions_not_resolved,
description: 'Discussions must be resolved before merging.'
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 499c2e786bf..ea833b35085 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -37,8 +37,8 @@ module Types
mount_mutation Mutations::Clusters::AgentTokens::Create
mount_mutation Mutations::Clusters::AgentTokens::Revoke
mount_mutation Mutations::Commits::Create, calls_gitaly: true
- mount_mutation Mutations::CustomEmoji::Create, _deprecated_feature_flag: :custom_emoji
- mount_mutation Mutations::CustomEmoji::Destroy, _deprecated_feature_flag: :custom_emoji
+ mount_mutation Mutations::CustomEmoji::Create, alpha: { milestone: '13.6' }
+ mount_mutation Mutations::CustomEmoji::Destroy, alpha: { milestone: '13.6' }
mount_mutation Mutations::CustomerRelations::Contacts::Create
mount_mutation Mutations::CustomerRelations::Contacts::Update
mount_mutation Mutations::CustomerRelations::Organizations::Create
@@ -120,10 +120,12 @@ module Types
milestone: '15.0'
}
mount_mutation Mutations::Ci::ProjectCiCdSettingsUpdate
+ mount_mutation Mutations::Ci::Job::ArtifactsDestroy
mount_mutation Mutations::Ci::Job::Play
mount_mutation Mutations::Ci::Job::Retry
mount_mutation Mutations::Ci::Job::Cancel
mount_mutation Mutations::Ci::Job::Unschedule
+ mount_mutation Mutations::Ci::JobArtifact::Destroy
mount_mutation Mutations::Ci::JobTokenScope::AddProject
mount_mutation Mutations::Ci::JobTokenScope::RemoveProject
mount_mutation Mutations::Ci::Runner::Update
diff --git a/app/graphql/types/packages/package_details_type.rb b/app/graphql/types/packages/package_details_type.rb
index 0413177ef14..6c0d955ed77 100644
--- a/app/graphql/types/packages/package_details_type.rb
+++ b/app/graphql/types/packages/package_details_type.rb
@@ -26,6 +26,8 @@ module Types
field :pypi_setup_url, GraphQL::Types::String, null: true, description: 'Url of the PyPi project setup endpoint.'
field :pypi_url, GraphQL::Types::String, null: true, description: 'Url of the PyPi project endpoint.'
+ field :last_downloaded_at, Types::TimeType, null: true, description: 'Last time that a file of this package was downloaded.'
+
def versions
object.versions
end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index ecc6c9d7811..f43f5c27dac 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -10,119 +10,204 @@ module Types
expose_permissions Types::PermissionTypes::Project
- field :id, GraphQL::Types::ID, null: false,
- description: 'ID of the project.'
-
- field :ci_config_path_or_default, GraphQL::Types::String, null: false,
- description: 'Path of the CI configuration file.'
- field :full_path, GraphQL::Types::ID, null: false,
- description: 'Full path of the project.'
- field :path, GraphQL::Types::String, null: false,
- description: 'Path of the project.'
-
- field :sast_ci_configuration, Types::CiConfiguration::Sast::Type, null: true,
- calls_gitaly: true,
- description: 'SAST CI configuration for the project.'
-
- field :name, GraphQL::Types::String, null: false,
- description: 'Name of the project (without namespace).'
- field :name_with_namespace, GraphQL::Types::String, null: false,
- description: 'Full name of the project with its namespace.'
-
- field :description, GraphQL::Types::String, null: true,
- description: 'Short description of the project.'
-
- field :tag_list, GraphQL::Types::String, null: true,
- deprecated: { reason: 'Use `topics`', milestone: '13.12' },
- description: 'List of project topics (not Git tags).', method: :topic_list
-
- field :topics, [GraphQL::Types::String], null: true,
- description: 'List of project topics.', method: :topic_list
-
- field :http_url_to_repo, GraphQL::Types::String, null: true,
- description: 'URL to connect to the project via HTTPS.'
- field :ssh_url_to_repo, GraphQL::Types::String, null: true,
- description: 'URL to connect to the project via SSH.'
- field :web_url, GraphQL::Types::String, null: true,
- description: 'Web URL of the project.'
-
- field :forks_count, GraphQL::Types::Int, null: false, calls_gitaly: true, # 4 times
- description: 'Number of times the project has been forked.'
- field :star_count, GraphQL::Types::Int, null: false,
- description: 'Number of times the project has been starred.'
-
- field :created_at, Types::TimeType, null: true,
- description: 'Timestamp of the project creation.'
- field :last_activity_at, Types::TimeType, null: true,
- description: 'Timestamp of the project last activity.'
-
- field :archived, GraphQL::Types::Boolean, null: true,
- description: 'Indicates the archived status of the project.'
-
- field :visibility, GraphQL::Types::String, null: true,
- description: 'Visibility of the project.'
-
- field :lfs_enabled, GraphQL::Types::Boolean, null: true,
- description: 'Indicates if the project has Large File Storage (LFS) enabled.'
- field :merge_requests_ff_only_enabled, GraphQL::Types::Boolean, null: true,
- description: 'Indicates if no merge commits should be created and all merges should instead be fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded.'
- field :shared_runners_enabled, GraphQL::Types::Boolean, null: true,
- description: 'Indicates if shared runners are enabled for the project.'
-
- field :service_desk_enabled, GraphQL::Types::Boolean, null: true,
- description: 'Indicates if the project has Service Desk enabled.'
-
- field :service_desk_address, GraphQL::Types::String, null: true,
- description: 'E-mail address of the Service Desk.'
-
- field :avatar_url, GraphQL::Types::String, null: true, calls_gitaly: true,
- description: 'URL to avatar image file of the project.'
-
- field :jobs_enabled, GraphQL::Types::Boolean, null: true,
- description: 'Indicates if CI/CD pipeline jobs are enabled for the current user.'
-
- field :public_jobs, GraphQL::Types::Boolean, method: :public_builds, null: true,
- description: 'Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts.'
-
- field :open_issues_count, GraphQL::Types::Int, null: true,
- description: 'Number of open issues for the project.'
-
- field :allow_merge_on_skipped_pipeline, GraphQL::Types::Boolean, null: true,
- description: 'If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of the project can also be merged with skipped jobs.'
- field :autoclose_referenced_issues, GraphQL::Types::Boolean, null: true,
- description: 'Indicates if issues referenced by merge requests and commits within the default branch are closed automatically.'
- field :import_status, GraphQL::Types::String, null: true,
- description: 'Status of import background job of the project.'
- field :jira_import_status, GraphQL::Types::String, null: true,
- description: 'Status of Jira import background job of the project.'
- field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::Types::Boolean, null: true,
- description: 'Indicates if merge requests of the project can only be merged when all the discussions are resolved.'
- field :only_allow_merge_if_pipeline_succeeds, GraphQL::Types::Boolean, null: true,
- description: 'Indicates if merge requests of the project can only be merged with successful jobs.'
- field :printing_merge_request_link_enabled, GraphQL::Types::Boolean, null: true,
- description: 'Indicates if a link to create or view a merge request should display after a push to Git repositories of the project from the command line.'
- field :remove_source_branch_after_merge, GraphQL::Types::Boolean, null: true,
- description: 'Indicates if `Delete source branch` option should be enabled by default for all new merge requests of the project.'
- field :request_access_enabled, GraphQL::Types::Boolean, null: true,
- description: 'Indicates if users can request member access to the project.'
- field :squash_read_only, GraphQL::Types::Boolean, null: false, method: :squash_readonly?,
- description: 'Indicates if `squashReadOnly` is enabled.'
- field :suggestion_commit_message, GraphQL::Types::String, null: true,
- description: 'Commit message used to apply merge request suggestions.'
+ field :id, GraphQL::Types::ID,
+ null: false,
+ description: 'ID of the project.'
+
+ field :ci_config_path_or_default, GraphQL::Types::String,
+ null: false,
+ description: 'Path of the CI configuration file.'
+
+ field :ci_config_variables, [Types::Ci::ConfigVariableType],
+ null: true,
+ calls_gitaly: true,
+ authorize: :create_pipeline,
+ alpha: { milestone: '15.3' },
+ description: 'CI/CD config variable.' do
+ argument :sha, GraphQL::Types::String,
+ required: true,
+ description: 'Sha.'
+ end
+
+ field :full_path, GraphQL::Types::ID,
+ null: false,
+ description: 'Full path of the project.'
+
+ field :path, GraphQL::Types::String,
+ null: false,
+ description: 'Path of the project.'
+
+ field :sast_ci_configuration, Types::CiConfiguration::Sast::Type,
+ null: true,
+ calls_gitaly: true,
+ description: 'SAST CI configuration for the project.'
+
+ field :name, GraphQL::Types::String,
+ null: false,
+ description: 'Name of the project (without namespace).'
+
+ field :name_with_namespace, GraphQL::Types::String,
+ null: false,
+ description: 'Full name of the project with its namespace.'
+
+ field :description, GraphQL::Types::String,
+ null: true,
+ description: 'Short description of the project.'
+
+ field :tag_list, GraphQL::Types::String,
+ null: true,
+ deprecated: { reason: 'Use `topics`', milestone: '13.12' },
+ description: 'List of project topics (not Git tags).',
+ method: :topic_list
+
+ field :topics, [GraphQL::Types::String],
+ null: true,
+ description: 'List of project topics.',
+ method: :topic_list
+
+ field :http_url_to_repo, GraphQL::Types::String,
+ null: true,
+ description: 'URL to connect to the project via HTTPS.'
+
+ field :ssh_url_to_repo, GraphQL::Types::String,
+ null: true,
+ description: 'URL to connect to the project via SSH.'
+
+ field :web_url, GraphQL::Types::String,
+ null: true,
+ description: 'Web URL of the project.'
+
+ field :forks_count, GraphQL::Types::Int,
+ null: false,
+ calls_gitaly: true, # 4 times
+ description: 'Number of times the project has been forked.'
+
+ field :star_count, GraphQL::Types::Int,
+ null: false,
+ description: 'Number of times the project has been starred.'
+
+ field :created_at, Types::TimeType,
+ null: true,
+ description: 'Timestamp of the project creation.'
+
+ field :last_activity_at, Types::TimeType,
+ null: true,
+ description: 'Timestamp of the project last activity.'
+
+ field :archived, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates the archived status of the project.'
+
+ field :visibility, GraphQL::Types::String,
+ null: true,
+ description: 'Visibility of the project.'
+
+ field :lfs_enabled, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates if the project has Large File Storage (LFS) enabled.'
+
+ field :merge_requests_ff_only_enabled, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates if no merge commits should be created and all merges should instead be ' \
+ 'fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded.'
+
+ field :shared_runners_enabled, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates if shared runners are enabled for the project.'
+
+ field :service_desk_enabled, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates if the project has Service Desk enabled.'
+
+ field :service_desk_address, GraphQL::Types::String,
+ null: true,
+ description: 'E-mail address of the Service Desk.'
+
+ field :avatar_url, GraphQL::Types::String,
+ null: true,
+ calls_gitaly: true,
+ description: 'URL to avatar image file of the project.'
+
+ field :jobs_enabled, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates if CI/CD pipeline jobs are enabled for the current user.'
+
+ field :public_jobs, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates if there is public access to pipelines and job details of the project, ' \
+ 'including output logs and artifacts.',
+ method: :public_builds
+
+ field :open_issues_count, GraphQL::Types::Int,
+ null: true,
+ description: 'Number of open issues for the project.'
+
+ field :allow_merge_on_skipped_pipeline, GraphQL::Types::Boolean,
+ null: true,
+ description: 'If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of ' \
+ 'the project can also be merged with skipped jobs.'
+
+ field :autoclose_referenced_issues, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates if issues referenced by merge requests and commits within the default branch ' \
+ 'are closed automatically.'
+
+ field :import_status, GraphQL::Types::String,
+ null: true,
+ description: 'Status of import background job of the project.'
+
+ field :jira_import_status, GraphQL::Types::String,
+ null: true,
+ description: 'Status of Jira import background job of the project.'
+
+ field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates if merge requests of the project can only be merged when all the discussions are resolved.'
+
+ field :only_allow_merge_if_pipeline_succeeds, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates if merge requests of the project can only be merged with successful jobs.'
+
+ field :printing_merge_request_link_enabled, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates if a link to create or view a merge request should display after a push to Git ' \
+ 'repositories of the project from the command line.'
+
+ field :remove_source_branch_after_merge, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates if `Delete source branch` option should be enabled by default for all ' \
+ 'new merge requests of the project.'
+
+ field :request_access_enabled, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates if users can request member access to the project.'
+
+ field :squash_read_only, GraphQL::Types::Boolean,
+ null: false,
+ description: 'Indicates if `squashReadOnly` is enabled.',
+ method: :squash_readonly?
+
+ field :suggestion_commit_message, GraphQL::Types::String,
+ null: true,
+ description: 'Commit message used to apply merge request suggestions.'
# No, the quotes are not a typo. Used to get around circular dependencies.
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27536#note_871009675
- field :group, 'Types::GroupType', null: true,
- description: 'Group of the project.'
- field :namespace, Types::NamespaceType, null: true,
- description: 'Namespace of the project.'
+ field :group, 'Types::GroupType',
+ null: true,
+ description: 'Group of the project.'
+
+ field :namespace, Types::NamespaceType,
+ null: true,
+ description: 'Namespace of the project.'
field :statistics, Types::ProjectStatisticsType,
null: true,
description: 'Statistics of the project.'
- field :repository, Types::RepositoryType, null: true,
- description: 'Git repository of the project.'
+ field :repository, Types::RepositoryType,
+ null: true,
+ description: 'Git repository of the project.'
field :merge_requests,
Types::MergeRequestType.connection_type,
@@ -159,9 +244,10 @@ module Types
extras: [:lookahead],
resolver: Resolvers::IssueStatusCountsResolver
- field :milestones, Types::MilestoneType.connection_type, null: true,
- description: 'Milestones of the project.',
- resolver: Resolvers::ProjectMilestonesResolver
+ field :milestones, Types::MilestoneType.connection_type,
+ null: true,
+ description: 'Milestones of the project.',
+ resolver: Resolvers::ProjectMilestonesResolver
field :project_members,
description: 'Members of the project.',
@@ -179,6 +265,12 @@ module Types
description: 'A single environment of the project.',
resolver: Resolvers::EnvironmentsResolver.single
+ field :deployment,
+ Types::DeploymentDetailsType,
+ null: true,
+ description: 'Details of the deployment of the project.',
+ resolver: Resolvers::DeploymentResolver.single
+
field :issue,
Types::IssueType,
null: true,
@@ -201,164 +293,150 @@ module Types
description: 'Jobs of a project. This field can only be resolved for one project in any single request.',
resolver: Resolvers::ProjectJobsResolver
+ field :job,
+ type: Types::Ci::JobType,
+ null: true,
+ authorize: :read_build,
+ description: 'One job belonging to the project, selected by ID.' do
+ argument :id, Types::GlobalIDType[::CommitStatus],
+ required: true,
+ description: 'ID of the job.'
+ end
+
field :pipelines,
null: true,
description: 'Build pipelines of the project.',
extras: [:lookahead],
resolver: Resolvers::ProjectPipelinesResolver
- field :pipeline,
- Types::Ci::PipelineType,
+ field :pipeline, Types::Ci::PipelineType,
null: true,
description: 'Build pipeline of the project.',
extras: [:lookahead],
resolver: Resolvers::ProjectPipelineResolver
- field :pipeline_counts,
- Types::Ci::PipelineCountsType,
+ field :pipeline_counts, Types::Ci::PipelineCountsType,
null: true,
description: 'Build pipeline counts of the project.',
resolver: Resolvers::Ci::ProjectPipelineCountsResolver
- field :ci_variables,
- Types::Ci::ProjectVariableType.connection_type,
+ field :ci_variables, Types::Ci::ProjectVariableType.connection_type,
null: true,
description: "List of the project's CI/CD variables.",
authorize: :admin_build,
method: :variables
- field :ci_cd_settings,
- Types::Ci::CiCdSettingType,
+ field :ci_cd_settings, Types::Ci::CiCdSettingType,
null: true,
description: 'CI/CD settings for the project.'
- field :sentry_detailed_error,
- Types::ErrorTracking::SentryDetailedErrorType,
+ field :sentry_detailed_error, Types::ErrorTracking::SentryDetailedErrorType,
null: true,
description: 'Detailed version of a Sentry error on the project.',
resolver: Resolvers::ErrorTracking::SentryDetailedErrorResolver
- field :grafana_integration,
- Types::GrafanaIntegrationType,
+ field :grafana_integration, Types::GrafanaIntegrationType,
null: true,
description: 'Grafana integration details for the project.',
resolver: Resolvers::Projects::GrafanaIntegrationResolver
- field :snippets,
- Types::SnippetType.connection_type,
+ field :snippets, Types::SnippetType.connection_type,
null: true,
description: 'Snippets of the project.',
resolver: Resolvers::Projects::SnippetsResolver
- field :sentry_errors,
- Types::ErrorTracking::SentryErrorCollectionType,
+ field :sentry_errors, Types::ErrorTracking::SentryErrorCollectionType,
null: true,
description: 'Paginated collection of Sentry errors on the project.',
resolver: Resolvers::ErrorTracking::SentryErrorCollectionResolver
- field :boards,
- Types::BoardType.connection_type,
+ field :boards, Types::BoardType.connection_type,
null: true,
description: 'Boards of the project.',
max_page_size: 2000,
resolver: Resolvers::BoardsResolver
- field :recent_issue_boards,
- Types::BoardType.connection_type,
+ field :recent_issue_boards, Types::BoardType.connection_type,
null: true,
description: 'List of recently visited boards of the project. Maximum size is 4.',
resolver: Resolvers::RecentBoardsResolver
- field :board,
- Types::BoardType,
+ field :board, Types::BoardType,
null: true,
description: 'A single board of the project.',
resolver: Resolvers::BoardResolver
- field :jira_imports,
- Types::JiraImportType.connection_type,
+ field :jira_imports, Types::JiraImportType.connection_type,
null: true,
description: 'Jira imports into the project.'
- field :services,
- Types::Projects::ServiceType.connection_type,
+ field :services, Types::Projects::ServiceType.connection_type,
null: true,
description: 'Project services.',
resolver: Resolvers::Projects::ServicesResolver
- field :alert_management_alerts,
- Types::AlertManagement::AlertType.connection_type,
+ field :alert_management_alerts, Types::AlertManagement::AlertType.connection_type,
null: true,
description: 'Alert Management alerts of the project.',
extras: [:lookahead],
resolver: Resolvers::AlertManagement::AlertResolver
- field :alert_management_alert,
- Types::AlertManagement::AlertType,
+ field :alert_management_alert, Types::AlertManagement::AlertType,
null: true,
description: 'A single Alert Management alert of the project.',
resolver: Resolvers::AlertManagement::AlertResolver.single
- field :alert_management_alert_status_counts,
- Types::AlertManagement::AlertStatusCountsType,
+ field :alert_management_alert_status_counts, Types::AlertManagement::AlertStatusCountsType,
null: true,
description: 'Counts of alerts by status for the project.',
resolver: Resolvers::AlertManagement::AlertStatusCountsResolver
- field :alert_management_integrations,
- Types::AlertManagement::IntegrationType.connection_type,
+ 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 :alert_management_http_integrations,
- Types::AlertManagement::HttpIntegrationType.connection_type,
+ field :alert_management_http_integrations, Types::AlertManagement::HttpIntegrationType.connection_type,
null: true,
description: 'HTTP Integrations which can receive alerts for the project.',
resolver: Resolvers::AlertManagement::HttpIntegrationsResolver
- field :incident_management_timeline_events,
- Types::IncidentManagement::TimelineEventType.connection_type,
+ field :incident_management_timeline_events, Types::IncidentManagement::TimelineEventType.connection_type,
null: true,
description: 'Incident Management Timeline events associated with the incident.',
extras: [:lookahead],
resolver: Resolvers::IncidentManagement::TimelineEventsResolver
- field :incident_management_timeline_event,
- Types::IncidentManagement::TimelineEventType,
+ field :incident_management_timeline_event, Types::IncidentManagement::TimelineEventType,
null: true,
description: 'Incident Management Timeline event associated with the incident.',
resolver: Resolvers::IncidentManagement::TimelineEventsResolver.single
- field :releases,
- Types::ReleaseType.connection_type,
+ field :releases, Types::ReleaseType.connection_type,
null: true,
description: 'Releases of the project.',
resolver: Resolvers::ReleasesResolver
- field :release,
- Types::ReleaseType,
+ field :release, Types::ReleaseType,
null: true,
description: 'A single release of the project.',
resolver: Resolvers::ReleasesResolver.single,
authorize: :read_release
- field :container_expiration_policy,
- Types::ContainerExpirationPolicyType,
+ field :container_expiration_policy, Types::ContainerExpirationPolicyType,
null: true,
description: 'Container expiration policy of the project.'
- field :container_repositories,
- Types::ContainerRepositoryType.connection_type,
+ field :container_repositories, Types::ContainerRepositoryType.connection_type,
null: true,
description: 'Container repositories of the project.',
resolver: Resolvers::ContainerRepositoriesResolver
- field :container_repositories_count, GraphQL::Types::Int, null: false,
- description: 'Number of container repositories in the project.'
+ field :container_repositories_count, GraphQL::Types::Int,
+ null: false,
+ description: 'Number of container repositories in the project.'
- field :label,
- Types::LabelType,
+ field :label, Types::LabelType,
null: true,
description: 'Label available on this project.' do
argument :title, GraphQL::Types::String,
@@ -366,68 +444,63 @@ module Types
description: 'Title of the label.'
end
- field :terraform_state,
- Types::Terraform::StateType,
+ field :terraform_state, Types::Terraform::StateType,
null: true,
description: 'Find a single Terraform state by name.',
resolver: Resolvers::Terraform::StatesResolver.single
- field :terraform_states,
- Types::Terraform::StateType.connection_type,
+ field :terraform_states, Types::Terraform::StateType.connection_type,
null: true,
description: 'Terraform states associated with the project.',
resolver: Resolvers::Terraform::StatesResolver
- field :pipeline_analytics, Types::Ci::AnalyticsType, null: true,
- description: 'Pipeline analytics.',
- resolver: Resolvers::ProjectPipelineStatisticsResolver
+ field :pipeline_analytics, Types::Ci::AnalyticsType,
+ null: true,
+ description: 'Pipeline analytics.',
+ resolver: Resolvers::ProjectPipelineStatisticsResolver
- field :ci_template, Types::Ci::TemplateType, null: true,
- description: 'Find a single CI/CD template by name.',
- resolver: Resolvers::Ci::TemplateResolver
+ field :ci_template, Types::Ci::TemplateType,
+ null: true,
+ description: 'Find a single CI/CD template by name.',
+ resolver: Resolvers::Ci::TemplateResolver
- field :ci_job_token_scope, Types::Ci::JobTokenScopeType, null: true,
- description: 'The CI Job Tokens scope of access.',
- resolver: Resolvers::Ci::JobTokenScopeResolver
+ field :ci_job_token_scope, Types::Ci::JobTokenScopeType,
+ null: true,
+ description: 'The CI Job Tokens scope of access.',
+ resolver: Resolvers::Ci::JobTokenScopeResolver
- field :timelogs,
- Types::TimelogType.connection_type, null: true,
- description: 'Time logged on issues and merge requests in the project.',
- extras: [:lookahead],
- complexity: 5,
- resolver: ::Resolvers::TimelogResolver
+ field :timelogs, Types::TimelogType.connection_type,
+ null: true,
+ description: 'Time logged on issues and merge requests in the project.',
+ extras: [:lookahead],
+ complexity: 5,
+ resolver: ::Resolvers::TimelogResolver
- field :agent_configurations,
- ::Types::Kas::AgentConfigurationType.connection_type,
+ field :agent_configurations, ::Types::Kas::AgentConfigurationType.connection_type,
null: true,
description: 'Agent configurations defined by the project',
resolver: ::Resolvers::Kas::AgentConfigurationsResolver
- field :cluster_agent,
- ::Types::Clusters::AgentType,
+ field :cluster_agent, ::Types::Clusters::AgentType,
null: true,
description: 'Find a single cluster agent by name.',
resolver: ::Resolvers::Clusters::AgentsResolver.single
- field :cluster_agents,
- ::Types::Clusters::AgentType.connection_type,
+ field :cluster_agents, ::Types::Clusters::AgentType.connection_type,
extras: [:lookahead],
null: true,
description: 'Cluster agents associated with the project.',
resolver: ::Resolvers::Clusters::AgentsResolver
- field :merge_commit_template,
- GraphQL::Types::String,
+ field :merge_commit_template, GraphQL::Types::String,
null: true,
description: 'Template used to create merge commit message in merge requests.'
- field :squash_commit_template,
- GraphQL::Types::String,
+ field :squash_commit_template, GraphQL::Types::String,
null: true,
description: 'Template used to create squash commit message in merge requests.'
- field :labels,
- Types::LabelType.connection_type,
+ field :labels, Types::LabelType.connection_type,
null: true,
description: 'Labels available on this project.',
resolver: Resolvers::LabelsResolver
@@ -438,8 +511,7 @@ module Types
' Returns `null` if `work_items` feature flag is disabled.' \
' This flag is disabled by default, because the feature is experimental and is subject to change without notice.'
- field :timelog_categories,
- Types::TimeTracking::TimelogCategoryType.connection_type,
+ field :timelog_categories, Types::TimeTracking::TimelogCategoryType.connection_type,
null: true,
description: "Timelog categories for the project.",
alpha: { milestone: '15.3' }
@@ -448,6 +520,12 @@ module Types
resolver: Resolvers::Projects::ForkTargetsResolver,
description: 'Namespaces in which the current user can fork the project into.'
+ field :branch_rules,
+ Types::Projects::BranchRuleType.connection_type,
+ null: true,
+ description: "Branch rules configured for the project.",
+ resolver: Resolvers::Projects::BranchRulesResolver
+
def timelog_categories
object.project_namespace.timelog_categories if Feature.enabled?(:timelog_categories)
end
@@ -498,6 +576,21 @@ module Types
project.container_repositories.size
end
+ def ci_config_variables(sha:)
+ result = ::Ci::ListConfigVariablesService.new(object, context[:current_user]).execute(sha)
+
+ return if result.nil?
+
+ result.map do |var_key, var_config|
+ { key: var_key, **var_config }
+ end
+ end
+
+ def job(id:)
+ object.commit_statuses.find(id.model_id)
+ rescue ActiveRecord::RecordNotFound
+ end
+
def sast_ci_configuration
return unless Ability.allowed?(current_user, :download_code, object)
diff --git a/app/graphql/types/projects/branch_rule_type.rb b/app/graphql/types/projects/branch_rule_type.rb
new file mode 100644
index 00000000000..866cff0f439
--- /dev/null
+++ b/app/graphql/types/projects/branch_rule_type.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Types
+ module Projects
+ class BranchRuleType < BaseObject
+ graphql_name 'BranchRule'
+ description 'List of branch rules for a project, grouped by branch name.'
+ accepts ::ProtectedBranch
+ authorize :read_protected_branch
+
+ field :name,
+ type: GraphQL::Types::String,
+ null: false,
+ description: 'Branch name, with wildcards, for the branch rules.'
+
+ field :branch_protection,
+ type: Types::BranchRules::BranchProtectionType,
+ null: false,
+ description: 'Branch protections configured for this branch rule.',
+ method: :itself
+
+ field :created_at,
+ Types::TimeType,
+ null: false,
+ description: 'Timestamp of when the branch rule was created.'
+
+ field :updated_at,
+ Types::TimeType,
+ null: false,
+ description: 'Timestamp of when the branch rule was last updated.'
+ end
+ end
+end
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 84355390ea0..78463a1804a 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -67,7 +67,7 @@ module Types
end
field :package,
- description: 'Find a package.',
+ description: 'Find a package. This field can only be resolved for one query in any single request.',
resolver: Resolvers::PackageDetailsResolver
field :user, Types::UserType,
diff --git a/app/graphql/types/sort_direction_enum.rb b/app/graphql/types/sort_direction_enum.rb
new file mode 100644
index 00000000000..28dba1abfb6
--- /dev/null
+++ b/app/graphql/types/sort_direction_enum.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Types
+ class SortDirectionEnum < BaseEnum
+ graphql_name 'SortDirectionEnum'
+ description 'Values for sort direction'
+
+ value 'ASC', 'Ascending order.', value: 'asc'
+ value 'DESC', 'Descending order.', value: 'desc'
+ end
+end
diff --git a/app/graphql/types/subscription_type.rb b/app/graphql/types/subscription_type.rb
index 9b5f028a857..ef701bbfc10 100644
--- a/app/graphql/types/subscription_type.rb
+++ b/app/graphql/types/subscription_type.rb
@@ -18,5 +18,12 @@ module Types
field :issuable_dates_updated, subscription: Subscriptions::IssuableUpdated, null: true,
description: 'Triggered when the due date or start date of an issuable is updated.'
+
+ field :merge_request_reviewers_updated,
+ subscription: Subscriptions::IssuableUpdated,
+ null: true,
+ description: 'Triggered when the reviewers of a merge request are updated.'
end
end
+
+Types::SubscriptionType.prepend_mod
diff --git a/app/graphql/types/timelog_type.rb b/app/graphql/types/timelog_type.rb
index c3fb9b77927..3856e1aa3b3 100644
--- a/app/graphql/types/timelog_type.rb
+++ b/app/graphql/types/timelog_type.rb
@@ -4,7 +4,7 @@ module Types
class TimelogType < BaseObject
graphql_name 'Timelog'
- authorize :read_issue
+ authorize :read_issuable
expose_permissions Types::PermissionTypes::Timelog
diff --git a/app/graphql/types/work_items/widgets/description_type.rb b/app/graphql/types/work_items/widgets/description_type.rb
index 4c365a67bfd..4861f7f46d8 100644
--- a/app/graphql/types/work_items/widgets/description_type.rb
+++ b/app/graphql/types/work_items/widgets/description_type.rb
@@ -13,8 +13,18 @@ module Types
implements Types::WorkItems::WidgetInterface
field :description, GraphQL::Types::String,
- null: true,
- description: 'Description of the work item.'
+ null: true,
+ description: 'Description of the work item.'
+ field :edited, GraphQL::Types::Boolean,
+ null: false,
+ description: 'Whether the description has been edited since the work item was created.',
+ method: :edited?
+ field :last_edited_at, Types::TimeType,
+ null: true,
+ description: 'Timestamp of when the work item\'s description was last edited.'
+ field :last_edited_by, Types::UserType,
+ null: true,
+ description: 'User that made the last edit to the work item\'s description.'
markdown_field :description_html, null: true do |resolved_object|
resolved_object.work_item