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.rb8
-rw-r--r--app/graphql/mutations/alert_management/alerts/set_assignees.rb2
-rw-r--r--app/graphql/mutations/alert_management/alerts/todo/create.rb2
-rw-r--r--app/graphql/mutations/alert_management/base.rb18
-rw-r--r--app/graphql/mutations/alert_management/create_alert_issue.rb2
-rw-r--r--app/graphql/mutations/alert_management/update_alert_status.rb2
-rw-r--r--app/graphql/mutations/ci/pipeline_schedule/create.rb72
-rw-r--r--app/graphql/mutations/ci/pipeline_schedule/play.rb32
-rw-r--r--app/graphql/mutations/ci/pipeline_schedule/variable_input_type.rb19
-rw-r--r--app/graphql/mutations/ci/runner/update.rb2
-rw-r--r--app/graphql/mutations/clusters/agent_tokens/create.rb6
-rw-r--r--app/graphql/mutations/container_repositories/destroy.rb4
-rw-r--r--app/graphql/mutations/incident_management/timeline_event/update.rb4
-rw-r--r--app/graphql/mutations/issues/link_alerts.rb26
-rw-r--r--app/graphql/mutations/issues/unlink_alert.rb33
-rw-r--r--app/graphql/mutations/notes/create/diff_note.rb8
-rw-r--r--app/graphql/mutations/notes/create/image_diff_note.rb6
-rw-r--r--app/graphql/mutations/notes/create/note.rb6
-rw-r--r--app/graphql/mutations/timelogs/create.rb8
-rw-r--r--app/graphql/mutations/todos/restore_many.rb6
-rw-r--r--app/graphql/mutations/work_items/create.rb2
-rw-r--r--app/graphql/resolvers/base_resolver.rb9
-rw-r--r--app/graphql/resolvers/ci/project_runners_resolver.rb15
-rw-r--r--app/graphql/resolvers/ci/runner_groups_resolver.rb45
-rw-r--r--app/graphql/resolvers/ci/runner_jobs_resolver.rb12
-rw-r--r--app/graphql/resolvers/ci/runner_owner_project_resolver.rb26
-rw-r--r--app/graphql/resolvers/ci/runner_projects_resolver.rb14
-rw-r--r--app/graphql/resolvers/clusters/agent_tokens_resolver.rb13
-rw-r--r--app/graphql/resolvers/concerns/looks_ahead.rb16
-rw-r--r--app/graphql/resolvers/concerns/resolves_groups.rb1
-rw-r--r--app/graphql/resolvers/environments/nested_environments_resolver.rb18
-rw-r--r--app/graphql/resolvers/environments_resolver.rb4
-rw-r--r--app/graphql/resolvers/group_packages_resolver.rb6
-rw-r--r--app/graphql/resolvers/issues_resolver.rb5
-rw-r--r--app/graphql/resolvers/package_details_resolver.rb8
-rw-r--r--app/graphql/resolvers/package_pipelines_resolver.rb2
-rw-r--r--app/graphql/resolvers/paginated_tree_resolver.rb5
-rw-r--r--app/graphql/resolvers/project_jobs_resolver.rb14
-rw-r--r--app/graphql/resolvers/projects/fork_details_resolver.rb21
-rw-r--r--app/graphql/resolvers/work_items/work_item_discussions_resolver.rb68
-rw-r--r--app/graphql/resolvers/work_items_resolver.rb1
-rw-r--r--app/graphql/types/alert_management/alert_type.rb10
-rw-r--r--app/graphql/types/base_field.rb7
-rw-r--r--app/graphql/types/ci/config_variable_type.rb1
-rw-r--r--app/graphql/types/ci/freeze_period_status_enum.rb13
-rw-r--r--app/graphql/types/ci/freeze_period_type.rb41
-rw-r--r--app/graphql/types/ci/pipeline_schedule_type.rb46
-rw-r--r--app/graphql/types/ci/pipeline_schedule_variable_type.rb13
-rw-r--r--app/graphql/types/ci/pipeline_type.rb2
-rw-r--r--app/graphql/types/ci/runner_job_execution_status_enum.rb19
-rw-r--r--app/graphql/types/ci/runner_type.rb93
-rw-r--r--app/graphql/types/commit_signature_interface.rb5
-rw-r--r--app/graphql/types/commit_signatures/gpg_signature_type.rb1
-rw-r--r--app/graphql/types/commit_signatures/ssh_signature_type.rb23
-rw-r--r--app/graphql/types/commit_signatures/x509_signature_type.rb1
-rw-r--r--app/graphql/types/container_repository_type.rb2
-rw-r--r--app/graphql/types/dependency_proxy/manifest_type.rb2
-rw-r--r--app/graphql/types/deployment_details_type.rb17
-rw-r--r--app/graphql/types/deployment_type.rb18
-rw-r--r--app/graphql/types/environment_type.rb11
-rw-r--r--app/graphql/types/global_id_type.rb4
-rw-r--r--app/graphql/types/group_connection.rb22
-rw-r--r--app/graphql/types/issue_type_enum.rb4
-rw-r--r--app/graphql/types/key_type.rb17
-rw-r--r--app/graphql/types/merge_request_type.rb4
-rw-r--r--app/graphql/types/mutation_type.rb4
-rw-r--r--app/graphql/types/nested_environment_type.rb28
-rw-r--r--app/graphql/types/notes/note_type.rb8
-rw-r--r--app/graphql/types/packages/package_links_type.rb2
-rw-r--r--app/graphql/types/permission_types/base_permission_type.rb8
-rw-r--r--app/graphql/types/permission_types/deployment.rb14
-rw-r--r--app/graphql/types/permission_types/environment.rb11
-rw-r--r--app/graphql/types/permission_types/project.rb3
-rw-r--r--app/graphql/types/project_statistics_type.rb8
-rw-r--r--app/graphql/types/project_type.rb31
-rw-r--r--app/graphql/types/projects/fork_details_type.rb20
-rw-r--r--app/graphql/types/query_type.rb2
-rw-r--r--app/graphql/types/release_type.rb10
-rw-r--r--app/graphql/types/root_storage_statistics_type.rb2
-rw-r--r--app/graphql/types/subscription_type.rb5
-rw-r--r--app/graphql/types/todo_action_enum.rb3
-rw-r--r--app/graphql/types/todo_type.rb6
-rw-r--r--app/graphql/types/user_interface.rb5
-rw-r--r--app/graphql/types/work_items/notes_filter_type_enum.rb20
-rw-r--r--app/graphql/types/work_items/widget_interface.rb5
-rw-r--r--app/graphql/types/work_items/widgets/hierarchy_type.rb23
-rw-r--r--app/graphql/types/work_items/widgets/notes_type.rb26
87 files changed, 1007 insertions, 184 deletions
diff --git a/app/graphql/graphql_triggers.rb b/app/graphql/graphql_triggers.rb
index 710e7fe110c..7f83b62a2ff 100644
--- a/app/graphql/graphql_triggers.rb
+++ b/app/graphql/graphql_triggers.rb
@@ -44,6 +44,14 @@ module GraphqlTriggers
merge_request
)
end
+
+ def self.merge_request_approval_state_updated(merge_request)
+ GitlabSchema.subscriptions.trigger(
+ 'mergeRequestApprovalStateUpdated',
+ { issuable_id: merge_request.to_gid },
+ merge_request
+ )
+ end
end
GraphqlTriggers.prepend_mod
diff --git a/app/graphql/mutations/alert_management/alerts/set_assignees.rb b/app/graphql/mutations/alert_management/alerts/set_assignees.rb
index c986111d290..500e2b868b1 100644
--- a/app/graphql/mutations/alert_management/alerts/set_assignees.rb
+++ b/app/graphql/mutations/alert_management/alerts/set_assignees.rb
@@ -20,7 +20,7 @@ module Mutations
alert = authorized_find!(project_path: args[:project_path], iid: args[:iid])
result = set_assignees(alert, args[:assignee_usernames], args[:operation_mode])
- track_usage_event(:incident_management_alert_assigned, current_user.id)
+ track_alert_events('incident_management_alert_assigned', alert)
prepare_response(result)
end
diff --git a/app/graphql/mutations/alert_management/alerts/todo/create.rb b/app/graphql/mutations/alert_management/alerts/todo/create.rb
index 2a1056e8f64..999c0bec5af 100644
--- a/app/graphql/mutations/alert_management/alerts/todo/create.rb
+++ b/app/graphql/mutations/alert_management/alerts/todo/create.rb
@@ -11,7 +11,7 @@ module Mutations
alert = authorized_find!(project_path: args[:project_path], iid: args[:iid])
result = ::AlertManagement::Alerts::Todo::CreateService.new(alert, current_user).execute
- track_usage_event(:incident_management_alert_todo, current_user.id)
+ track_alert_events('incident_management_alert_todo', alert)
prepare_response(result)
end
diff --git a/app/graphql/mutations/alert_management/base.rb b/app/graphql/mutations/alert_management/base.rb
index d01f200107c..2eef6bb9db7 100644
--- a/app/graphql/mutations/alert_management/base.rb
+++ b/app/graphql/mutations/alert_management/base.rb
@@ -39,6 +39,24 @@ module Mutations
::AlertManagement::AlertsFinder.new(current_user, project, args).execute.first
end
+
+ def track_alert_events(event, alert)
+ project = alert.project
+ namespace = project.namespace
+ track_usage_event(event, current_user.id)
+
+ return unless Feature.enabled?(:route_hll_to_snowplow_phase2, namespace)
+
+ Gitlab::Tracking.event(
+ self.class.to_s,
+ event,
+ project: project,
+ namespace: namespace,
+ user: current_user,
+ label: 'redis_hll_counters.incident_management.incident_management_total_unique_counts_monthly',
+ context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event).to_context]
+ )
+ end
end
end
end
diff --git a/app/graphql/mutations/alert_management/create_alert_issue.rb b/app/graphql/mutations/alert_management/create_alert_issue.rb
index 77a7d7a4147..7c8de6365e7 100644
--- a/app/graphql/mutations/alert_management/create_alert_issue.rb
+++ b/app/graphql/mutations/alert_management/create_alert_issue.rb
@@ -9,7 +9,7 @@ module Mutations
alert = authorized_find!(project_path: args[:project_path], iid: args[:iid])
result = create_alert_issue(alert, current_user)
- track_usage_event(:incident_management_incident_created, current_user.id)
+ track_alert_events('incident_management_incident_created', alert)
track_usage_event(:incident_management_alert_create_incident, current_user.id)
prepare_response(alert, result)
diff --git a/app/graphql/mutations/alert_management/update_alert_status.rb b/app/graphql/mutations/alert_management/update_alert_status.rb
index 21566c7d66f..be271a7d795 100644
--- a/app/graphql/mutations/alert_management/update_alert_status.rb
+++ b/app/graphql/mutations/alert_management/update_alert_status.rb
@@ -13,7 +13,7 @@ module Mutations
alert = authorized_find!(project_path: project_path, iid: iid)
result = update_status(alert, status)
- track_usage_event(:incident_management_alert_status_changed, current_user.id)
+ track_alert_events('incident_management_alert_status_changed', alert)
prepare_response(result)
end
diff --git a/app/graphql/mutations/ci/pipeline_schedule/create.rb b/app/graphql/mutations/ci/pipeline_schedule/create.rb
new file mode 100644
index 00000000000..65b355cd80f
--- /dev/null
+++ b/app/graphql/mutations/ci/pipeline_schedule/create.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Ci
+ module PipelineSchedule
+ class Create < BaseMutation
+ graphql_name 'PipelineScheduleCreate'
+
+ include FindsProject
+
+ authorize :create_pipeline_schedule
+
+ argument :project_path, GraphQL::Types::ID,
+ required: true,
+ description: 'Full path of the project the pipeline schedule is associated with.'
+
+ argument :description, GraphQL::Types::String,
+ required: true,
+ description: 'Description of the pipeline schedule.'
+
+ argument :cron, GraphQL::Types::String,
+ required: true,
+ description: 'Cron expression of the pipeline schedule.'
+
+ argument :cron_timezone, GraphQL::Types::String,
+ required: false,
+ description:
+ <<-STR
+ Cron time zone supported by ActiveSupport::TimeZone.
+ For example: "Pacific Time (US & Canada)" (default: "UTC").
+ STR
+
+ argument :ref, GraphQL::Types::String,
+ required: true,
+ description: 'Ref of the pipeline schedule.'
+
+ argument :active, GraphQL::Types::Boolean,
+ required: false,
+ description: 'Indicates if the pipeline schedule should be active or not.'
+
+ argument :variables, [Mutations::Ci::PipelineSchedule::VariableInputType],
+ required: false,
+ description: 'Variables for the pipeline schedule.'
+
+ field :pipeline_schedule,
+ Types::Ci::PipelineScheduleType,
+ description: 'Created pipeline schedule.'
+
+ def resolve(project_path:, variables: [], **pipeline_schedule_attrs)
+ project = authorized_find!(project_path)
+
+ params = pipeline_schedule_attrs.merge(variables_attributes: variables.map(&:to_h))
+
+ schedule = ::Ci::CreatePipelineScheduleService
+ .new(project, current_user, params)
+ .execute
+
+ unless schedule.persisted?
+ return {
+ pipeline_schedule: nil, errors: schedule.errors.full_messages
+ }
+ end
+
+ {
+ pipeline_schedule: schedule,
+ errors: []
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/ci/pipeline_schedule/play.rb b/app/graphql/mutations/ci/pipeline_schedule/play.rb
new file mode 100644
index 00000000000..056890852c9
--- /dev/null
+++ b/app/graphql/mutations/ci/pipeline_schedule/play.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Ci
+ module PipelineSchedule
+ class Play < Base
+ graphql_name 'PipelineSchedulePlay'
+
+ authorize :play_pipeline_schedule
+
+ field :pipeline_schedule,
+ Types::Ci::PipelineScheduleType,
+ null: true,
+ description: 'Pipeline schedule after mutation.'
+
+ def resolve(id:)
+ schedule = authorized_find!(id: id)
+
+ job_id = ::Ci::PipelineScheduleService
+ .new(schedule.project, current_user)
+ .execute(schedule)
+
+ if job_id
+ { pipeline_schedule: schedule, errors: [] }
+ else
+ { pipeline_schedule: nil, errors: ['Unable to schedule a pipeline to run immediately.'] }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/ci/pipeline_schedule/variable_input_type.rb b/app/graphql/mutations/ci/pipeline_schedule/variable_input_type.rb
new file mode 100644
index 00000000000..54a6ad92448
--- /dev/null
+++ b/app/graphql/mutations/ci/pipeline_schedule/variable_input_type.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Ci
+ module PipelineSchedule
+ class VariableInputType < Types::BaseInputObject
+ graphql_name 'PipelineScheduleVariableInput'
+
+ description 'Attributes for the pipeline schedule variable.'
+
+ argument :key, GraphQL::Types::String, required: true, description: 'Name of the variable.'
+
+ argument :value, GraphQL::Types::String, required: true, description: 'Value of the variable.'
+
+ argument :variable_type, Types::Ci::VariableTypeEnum, required: true, description: 'Type of the variable.'
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/ci/runner/update.rb b/app/graphql/mutations/ci/runner/update.rb
index 3c99cde60a4..4f0bf19f09c 100644
--- a/app/graphql/mutations/ci/runner/update.rb
+++ b/app/graphql/mutations/ci/runner/update.rb
@@ -54,7 +54,7 @@ module Mutations
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 } }
+ prepare: ->(global_ids, ctx) { global_ids&.filter_map { |gid| gid.model_id.to_i } }
field :runner,
Types::Ci::RunnerType,
diff --git a/app/graphql/mutations/clusters/agent_tokens/create.rb b/app/graphql/mutations/clusters/agent_tokens/create.rb
index a99a54fa5ed..c10e1633350 100644
--- a/app/graphql/mutations/clusters/agent_tokens/create.rb
+++ b/app/graphql/mutations/clusters/agent_tokens/create.rb
@@ -49,9 +49,9 @@ module Mutations
payload = result.payload
{
- secret: payload[:secret],
- token: payload[:token],
- errors: Array.wrap(result.message)
+ secret: payload[:secret],
+ token: payload[:token],
+ errors: Array.wrap(result.message)
}
end
diff --git a/app/graphql/mutations/container_repositories/destroy.rb b/app/graphql/mutations/container_repositories/destroy.rb
index fe1c3fe4e61..c3bd7acf444 100644
--- a/app/graphql/mutations/container_repositories/destroy.rb
+++ b/app/graphql/mutations/container_repositories/destroy.rb
@@ -22,10 +22,6 @@ module Mutations
container_repository.delete_scheduled!
- unless Feature.enabled?(:container_registry_delete_repository_with_cron_worker)
- DeleteContainerRepositoryWorker.perform_async(current_user.id, container_repository.id) # rubocop:disable CodeReuse/Worker
- end
-
track_event(:delete_repository, :container)
{
diff --git a/app/graphql/mutations/incident_management/timeline_event/update.rb b/app/graphql/mutations/incident_management/timeline_event/update.rb
index 1f53bdc19cb..b35feed3082 100644
--- a/app/graphql/mutations/incident_management/timeline_event/update.rb
+++ b/app/graphql/mutations/incident_management/timeline_event/update.rb
@@ -18,6 +18,10 @@ module Mutations
required: false,
description: 'Timestamp when the event occurred.'
+ argument :timeline_event_tag_names, [GraphQL::Types::String],
+ required: false,
+ description: copy_field_description(Types::IncidentManagement::TimelineEventType, :timeline_event_tags)
+
def resolve(id:, **args)
timeline_event = authorized_find!(id: id)
diff --git a/app/graphql/mutations/issues/link_alerts.rb b/app/graphql/mutations/issues/link_alerts.rb
new file mode 100644
index 00000000000..c45e90c598f
--- /dev/null
+++ b/app/graphql/mutations/issues/link_alerts.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Issues
+ class LinkAlerts < Base
+ graphql_name 'IssueLinkAlerts'
+
+ argument :alert_references, [GraphQL::Types::String],
+ required: true,
+ description: 'Alerts references to be linked to the incident.'
+
+ authorize :admin_issue
+
+ def resolve(project_path:, iid:, alert_references:)
+ issue = authorized_find!(project_path: project_path, iid: iid)
+
+ ::IncidentManagement::LinkAlerts::CreateService.new(issue, current_user, alert_references).execute
+
+ {
+ issue: issue,
+ errors: errors_on_object(issue)
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/issues/unlink_alert.rb b/app/graphql/mutations/issues/unlink_alert.rb
new file mode 100644
index 00000000000..a11af4133cf
--- /dev/null
+++ b/app/graphql/mutations/issues/unlink_alert.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Issues
+ class UnlinkAlert < Base
+ graphql_name 'IssueUnlinkAlert'
+
+ argument :alert_id, ::Types::GlobalIDType[::AlertManagement::Alert],
+ required: true,
+ description: 'Global ID of the alert to unlink from the incident.'
+
+ authorize :admin_issue
+
+ def resolve(project_path:, iid:, alert_id:)
+ issue = authorized_find!(project_path: project_path, iid: iid)
+ alert = find_alert_by_gid(alert_id)
+
+ result = ::IncidentManagement::LinkAlerts::DestroyService.new(issue, current_user, alert).execute
+
+ {
+ issue: issue,
+ errors: result.errors
+ }
+ end
+
+ private
+
+ def find_alert_by_gid(alert_id)
+ ::Gitlab::Graphql::Lazy.force(GitlabSchema.object_from_id(alert_id, expected_type: ::AlertManagement::Alert))
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/create/diff_note.rb b/app/graphql/mutations/notes/create/diff_note.rb
index 7b8c06fd104..df2bd55106e 100644
--- a/app/graphql/mutations/notes/create/diff_note.rb
+++ b/app/graphql/mutations/notes/create/diff_note.rb
@@ -31,10 +31,10 @@ module Mutations
def create_note_params(noteable, args)
super(noteable, args).merge({
- type: 'DiffNote',
- position: position(noteable, args),
- merge_request_diff_head_sha: args[:position][:head_sha]
- })
+ type: 'DiffNote',
+ position: position(noteable, args),
+ merge_request_diff_head_sha: args[:position][:head_sha]
+ })
end
def position(noteable, args)
diff --git a/app/graphql/mutations/notes/create/image_diff_note.rb b/app/graphql/mutations/notes/create/image_diff_note.rb
index d94fd4d6ff8..3de93e4f5c1 100644
--- a/app/graphql/mutations/notes/create/image_diff_note.rb
+++ b/app/graphql/mutations/notes/create/image_diff_note.rb
@@ -15,9 +15,9 @@ module Mutations
def create_note_params(noteable, args)
super(noteable, args).merge({
- type: 'DiffNote',
- position: position(noteable, args)
- })
+ type: 'DiffNote',
+ position: position(noteable, args)
+ })
end
def position(noteable, args)
diff --git a/app/graphql/mutations/notes/create/note.rb b/app/graphql/mutations/notes/create/note.rb
index 4d6f056de09..9b105b7fe1c 100644
--- a/app/graphql/mutations/notes/create/note.rb
+++ b/app/graphql/mutations/notes/create/note.rb
@@ -31,9 +31,9 @@ module Mutations
end
super(noteable, args).merge({
- in_reply_to_discussion_id: discussion_id,
- merge_request_diff_head_sha: args[:merge_request_diff_head_sha]
- })
+ in_reply_to_discussion_id: discussion_id,
+ merge_request_diff_head_sha: args[:merge_request_diff_head_sha]
+ })
end
def authorize_discussion!(discussion)
diff --git a/app/graphql/mutations/timelogs/create.rb b/app/graphql/mutations/timelogs/create.rb
index bab7508454e..1be023eed8a 100644
--- a/app/graphql/mutations/timelogs/create.rb
+++ b/app/graphql/mutations/timelogs/create.rb
@@ -11,7 +11,7 @@ module Mutations
description: 'Amount of time spent.'
argument :spent_at,
- Types::DateType,
+ Types::TimeType,
required: true,
description: 'When the time was spent.'
@@ -28,8 +28,12 @@ module Mutations
authorize :create_timelog
def resolve(issuable_id:, time_spent:, spent_at:, summary:, **args)
- issuable = authorized_find!(id: issuable_id)
parsed_time_spent = Gitlab::TimeTrackingFormatter.parse(time_spent)
+ if parsed_time_spent.nil?
+ return { timelog: nil, errors: [_('Time spent must be formatted correctly. For example: 1h 30m.')] }
+ end
+
+ issuable = authorized_find!(id: issuable_id)
result = ::Timelogs::CreateService.new(
issuable, parsed_time_spent, spent_at, summary, current_user
diff --git a/app/graphql/mutations/todos/restore_many.rb b/app/graphql/mutations/todos/restore_many.rb
index 20913a9e7da..f2f944860c2 100644
--- a/app/graphql/mutations/todos/restore_many.rb
+++ b/app/graphql/mutations/todos/restore_many.rb
@@ -23,9 +23,9 @@ module Mutations
updated_ids = restore(todos)
{
- updated_ids: updated_ids,
- todos: Todo.id_in(updated_ids),
- errors: errors_on_objects(todos)
+ updated_ids: updated_ids,
+ todos: Todo.id_in(updated_ids),
+ errors: errors_on_objects(todos)
}
end
diff --git a/app/graphql/mutations/work_items/create.rb b/app/graphql/mutations/work_items/create.rb
index 793e5d3caf8..a4efffb69c1 100644
--- a/app/graphql/mutations/work_items/create.rb
+++ b/app/graphql/mutations/work_items/create.rb
@@ -73,3 +73,5 @@ module Mutations
end
end
end
+
+Mutations::WorkItems::Create.prepend_mod
diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb
index 2b54a3fdd55..6f847221f1b 100644
--- a/app/graphql/resolvers/base_resolver.rb
+++ b/app/graphql/resolvers/base_resolver.rb
@@ -15,6 +15,13 @@ module Resolvers
@calls_gitaly = true
end
+ # This is a flag to allow us to use `complexity_multiplier` to compute complexity for connection
+ # fields(see BaseField#connection_complexity_multiplier) in resolvers that do external connection pagination,
+ # thus disabling the default `connection` option(see self.field_options method above).
+ def self.calculate_ext_conn_complexity
+ false
+ end
+
def self.field_options
extra_options = {
requires_argument: @requires_argument,
@@ -116,7 +123,7 @@ module Resolvers
# When fetching many items, additional complexity is added to the field
# depending on how many items is fetched. For each item we add 1% of the
# original complexity - this means that loading 100 items (our default
- # maxp_age_size limit) doubles the original complexity.
+ # max_page_size limit) doubles the original complexity.
#
# Complexity is not increased when searching by specific ID(s), because
# complexity difference is minimal in this case.
diff --git a/app/graphql/resolvers/ci/project_runners_resolver.rb b/app/graphql/resolvers/ci/project_runners_resolver.rb
new file mode 100644
index 00000000000..378fa73c065
--- /dev/null
+++ b/app/graphql/resolvers/ci/project_runners_resolver.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Ci
+ class ProjectRunnersResolver < RunnersResolver
+ type Types::Ci::RunnerType.connection_type, null: true
+
+ def parent_param
+ raise 'Expected project missing' unless parent.is_a?(Project)
+
+ { project: parent }
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/ci/runner_groups_resolver.rb b/app/graphql/resolvers/ci/runner_groups_resolver.rb
new file mode 100644
index 00000000000..3360e820bd2
--- /dev/null
+++ b/app/graphql/resolvers/ci/runner_groups_resolver.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Ci
+ class RunnerGroupsResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+ include ResolvesGroups
+
+ type Types::GroupConnection, null: true
+
+ authorize :read_runner
+ authorizes_object!
+
+ alias_method :runner, :object
+
+ def resolve_with_lookahead(**args)
+ return unless runner.group_type?
+
+ BatchLoader::GraphQL.for(runner.id).batch(key: :runner_namespaces) do |runner_ids, loader|
+ plucked_runner_and_namespace_ids =
+ ::Ci::RunnerNamespace
+ .for_runner(runner_ids)
+ .select(:runner_id, :namespace_id)
+ .pluck(:runner_id, :namespace_id) # rubocop: disable CodeReuse/ActiveRecord)
+
+ namespace_ids = plucked_runner_and_namespace_ids.collect(&:last).uniq
+ groups = apply_lookahead(::Group.id_in(namespace_ids))
+ Preloaders::GroupPolicyPreloader.new(groups, current_user).execute
+ groups_by_id = groups.index_by(&:id)
+
+ runner_group_ids_by_runner_id =
+ plucked_runner_and_namespace_ids
+ .group_by { |runner_id, _namespace_id| runner_id }
+ .transform_values { |values| values.filter_map { |_runner_id, namespace_id| groups_by_id[namespace_id] } }
+
+ runner_ids.each do |runner_id|
+ runner_namespaces = runner_group_ids_by_runner_id[runner_id] || []
+
+ loader.call(runner_id, runner_namespaces)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/ci/runner_jobs_resolver.rb b/app/graphql/resolvers/ci/runner_jobs_resolver.rb
index de00aadaea8..b818be3f018 100644
--- a/app/graphql/resolvers/ci/runner_jobs_resolver.rb
+++ b/app/graphql/resolvers/ci/runner_jobs_resolver.rb
@@ -27,9 +27,17 @@ module Resolvers
def preloads
{
- previous_stage_jobs_and_needs: [:needs, :pipeline],
+ previous_stage_jobs_or_needs: [:needs, :pipeline],
artifacts: [:job_artifacts],
- pipeline: [:user]
+ pipeline: [:user],
+ detailed_status: [
+ :metadata,
+ { pipeline: [:merge_request] },
+ { project: [:route, { namespace: :route }] }
+ ],
+ commit_path: [:pipeline, { project: [:route, { namespace: [:route] }] }],
+ short_sha: [:pipeline],
+ tags: [:tags]
}
end
end
diff --git a/app/graphql/resolvers/ci/runner_owner_project_resolver.rb b/app/graphql/resolvers/ci/runner_owner_project_resolver.rb
index da8fab93619..f4e044b81c9 100644
--- a/app/graphql/resolvers/ci/runner_owner_project_resolver.rb
+++ b/app/graphql/resolvers/ci/runner_owner_project_resolver.rb
@@ -13,20 +13,22 @@ module Resolvers
resolve_owner
end
- def preloads
- {
- full_path: [:route]
- }
- end
-
private
- def filtered_preloads
- selection = lookahead
+ def node_selection(selection = lookahead)
+ # There are no nodes or edges selections in RunnerOwnerProjectResolver, but rather a project directly
+ selection
+ end
+
+ def unconditional_includes
+ [:project_feature]
+ end
- preloads.each.flat_map do |name, requirements|
- selection&.selects?(name) ? requirements : []
- end
+ def preloads
+ {
+ full_path: [:route, { namespace: [:route] }],
+ web_url: [:route, { namespace: [:route] }]
+ }
end
def resolve_owner
@@ -48,7 +50,7 @@ module Resolvers
.transform_values { |runner_projects| runner_projects.first.project_id }
project_ids = owner_project_id_by_runner_id.values.uniq
- projects = Project.where(id: project_ids)
+ projects = apply_lookahead(Project.id_in(project_ids))
Preloaders::ProjectPolicyPreloader.new(projects, current_user).execute
projects_by_id = projects.index_by(&:id)
diff --git a/app/graphql/resolvers/ci/runner_projects_resolver.rb b/app/graphql/resolvers/ci/runner_projects_resolver.rb
index af9a67acfda..2a2d63f85de 100644
--- a/app/graphql/resolvers/ci/runner_projects_resolver.rb
+++ b/app/graphql/resolvers/ci/runner_projects_resolver.rb
@@ -40,6 +40,7 @@ module Resolvers
params: project_finder_params(args),
project_ids_relation: project_ids)
.execute
+ projects = apply_lookahead(projects)
Preloaders::ProjectPolicyPreloader.new(projects, current_user).execute
projects_by_id = projects.index_by(&:id)
@@ -58,6 +59,19 @@ module Resolvers
end
# rubocop:enable CodeReuse/ActiveRecord
end
+
+ private
+
+ def unconditional_includes
+ [:project_feature]
+ end
+
+ def preloads
+ super.merge({
+ full_path: [:route, { namespace: [:route] }],
+ web_url: [:route, { namespace: [:route] }]
+ })
+ end
end
end
end
diff --git a/app/graphql/resolvers/clusters/agent_tokens_resolver.rb b/app/graphql/resolvers/clusters/agent_tokens_resolver.rb
index 9740bc6bb6a..b7355a1752e 100644
--- a/app/graphql/resolvers/clusters/agent_tokens_resolver.rb
+++ b/app/graphql/resolvers/clusters/agent_tokens_resolver.rb
@@ -14,18 +14,7 @@ module Resolvers
description: 'Status of the token.'
def resolve(**args)
- return ::Clusters::AgentToken.none unless can_read_agent_tokens?
-
- tokens = agent.agent_tokens
- tokens = tokens.with_status(args[:status]) if args[:status].present?
-
- tokens
- end
-
- private
-
- def can_read_agent_tokens?
- current_user.can?(:read_cluster, project)
+ ::Clusters::AgentTokensFinder.new(agent, current_user, args).execute
end
end
end
diff --git a/app/graphql/resolvers/concerns/looks_ahead.rb b/app/graphql/resolvers/concerns/looks_ahead.rb
index 81099c04e9f..1d532eb2486 100644
--- a/app/graphql/resolvers/concerns/looks_ahead.rb
+++ b/app/graphql/resolvers/concerns/looks_ahead.rb
@@ -39,7 +39,7 @@ module LooksAhead
def filtered_preloads
nodes = node_selection
- return [] unless nodes
+ return [] unless nodes&.selected?
selected_fields = nodes.selections.map(&:name)
root_level_preloads = preloads_from_node_selection(selected_fields, preloads)
@@ -65,13 +65,13 @@ module LooksAhead
end.flatten
end
- def node_selection
- return unless lookahead
+ def node_selection(selection = lookahead)
+ return selection unless selection&.selected?
+ return selection.selection(:edges).selection(:node) if selection.selects?(:edges)
- if lookahead.selects?(:nodes)
- lookahead.selection(:nodes)
- elsif lookahead.selects?(:edges)
- lookahead.selection(:edges).selection(:node)
- end
+ # Will return a NullSelection object if :nodes is not a selection. This
+ # is better than returning nil as we can continue chaining selections on
+ # without raising errors.
+ selection.selection(:nodes)
end
end
diff --git a/app/graphql/resolvers/concerns/resolves_groups.rb b/app/graphql/resolvers/concerns/resolves_groups.rb
index 2a3dce80057..1268e74fd58 100644
--- a/app/graphql/resolvers/concerns/resolves_groups.rb
+++ b/app/graphql/resolvers/concerns/resolves_groups.rb
@@ -22,6 +22,7 @@ module ResolvesGroups
custom_emoji: [:custom_emoji],
full_path: [:route],
path: [:route],
+ web_url: [:route],
dependency_proxy_blob_count: [:dependency_proxy_blobs],
dependency_proxy_blobs: [:dependency_proxy_blobs],
dependency_proxy_image_count: [:dependency_proxy_manifests],
diff --git a/app/graphql/resolvers/environments/nested_environments_resolver.rb b/app/graphql/resolvers/environments/nested_environments_resolver.rb
new file mode 100644
index 00000000000..f043270beca
--- /dev/null
+++ b/app/graphql/resolvers/environments/nested_environments_resolver.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Environments
+ class NestedEnvironmentsResolver < EnvironmentsResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ type Types::NestedEnvironmentType, null: true
+
+ authorizes_object!
+ authorize :read_environment
+
+ def resolve(**args)
+ offset_pagination(super(**args).nested)
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/environments_resolver.rb b/app/graphql/resolvers/environments_resolver.rb
index f265e2183d0..aca1a36f0f5 100644
--- a/app/graphql/resolvers/environments_resolver.rb
+++ b/app/graphql/resolvers/environments_resolver.rb
@@ -14,6 +14,10 @@ module Resolvers
required: false,
description: 'States of environments that should be included in result.'
+ argument :type, GraphQL::Types::String,
+ required: false,
+ description: 'Search query for environment type.'
+
type Types::EnvironmentType, null: true
alias_method :project, :object
diff --git a/app/graphql/resolvers/group_packages_resolver.rb b/app/graphql/resolvers/group_packages_resolver.rb
index e6a6abb39dd..ae578390fd5 100644
--- a/app/graphql/resolvers/group_packages_resolver.rb
+++ b/app/graphql/resolvers/group_packages_resolver.rb
@@ -13,9 +13,9 @@ module Resolvers
default_value: :created_desc
GROUP_SORT_TO_PARAMS_MAP = SORT_TO_PARAMS_MAP.merge({
- project_path_desc: { order_by: 'project_path', sort: 'desc' },
- project_path_asc: { order_by: 'project_path', sort: 'asc' }
- }).freeze
+ project_path_desc: { order_by: 'project_path', sort: 'desc' },
+ project_path_asc: { order_by: 'project_path', sort: 'asc' }
+ }).freeze
def resolve(sort:, **filters)
return unless packages_available?
diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb
index e3102a7d32a..3e61ba755d8 100644
--- a/app/graphql/resolvers/issues_resolver.rb
+++ b/app/graphql/resolvers/issues_resolver.rb
@@ -12,6 +12,11 @@ module Resolvers
# see app/graphql/types/issue_connection.rb
type 'Types::IssueConnection', null: true
+ before_connection_authorization do |nodes, current_user|
+ projects = nodes.map(&:project)
+ ::Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects, current_user).execute
+ end
+
def resolve_with_lookahead(**args)
return unless Feature.enabled?(:root_level_issues_query)
diff --git a/app/graphql/resolvers/package_details_resolver.rb b/app/graphql/resolvers/package_details_resolver.rb
index b77c6b1112b..c565fcb70e3 100644
--- a/app/graphql/resolvers/package_details_resolver.rb
+++ b/app/graphql/resolvers/package_details_resolver.rb
@@ -11,6 +11,14 @@ module Resolvers
description: 'Global ID of the package.'
def resolve(id:)
+ Gitlab::Graphql::Lazy.with_value(find_object(id: id)) do |package|
+ package if package.default?
+ end
+ end
+
+ private
+
+ def find_object(id:)
GitlabSchema.find_by_gid(id)
end
end
diff --git a/app/graphql/resolvers/package_pipelines_resolver.rb b/app/graphql/resolvers/package_pipelines_resolver.rb
index 9ff77f02547..7f610915489 100644
--- a/app/graphql/resolvers/package_pipelines_resolver.rb
+++ b/app/graphql/resolvers/package_pipelines_resolver.rb
@@ -18,7 +18,7 @@ module Resolvers
# This returns a promise for a connection of promises for pipelines:
# Lazy[Connection[Lazy[Pipeline]]] structure
- def resolve(first: nil, last: nil, after: nil, before: nil, lookahead:)
+ def resolve(lookahead:, first: nil, last: nil, after: nil, before: nil)
default_value = default_value_for(first: first, last: last, after: after, before: before)
BatchLoader::GraphQL.for(package.id)
.batch(default_value: default_value) do |package_ids, loader|
diff --git a/app/graphql/resolvers/paginated_tree_resolver.rb b/app/graphql/resolvers/paginated_tree_resolver.rb
index c7e9e522c25..6c4e978125e 100644
--- a/app/graphql/resolvers/paginated_tree_resolver.rb
+++ b/app/graphql/resolvers/paginated_tree_resolver.rb
@@ -41,7 +41,10 @@ module Resolvers
next_cursor = tree.cursor&.next_cursor
Gitlab::Graphql::ExternallyPaginatedArray.new(cursor, next_cursor, *tree)
rescue Gitlab::Git::CommandError => e
- raise Gitlab::Graphql::Errors::ArgumentError, e
+ raise Gitlab::Graphql::Errors::BaseError.new(
+ e,
+ extensions: { code: e.code, gitaly_code: e.status, service: e.service }
+ )
end
def self.field_options
diff --git a/app/graphql/resolvers/project_jobs_resolver.rb b/app/graphql/resolvers/project_jobs_resolver.rb
index 4d13a4a3fae..2bf71679dbf 100644
--- a/app/graphql/resolvers/project_jobs_resolver.rb
+++ b/app/graphql/resolvers/project_jobs_resolver.rb
@@ -14,10 +14,18 @@ module Resolvers
required: false,
description: 'Filter jobs by status.'
+ argument :with_artifacts, ::GraphQL::Types::Boolean,
+ required: false,
+ description: 'Filter by artifacts presence.'
+
alias_method :project, :object
- def resolve_with_lookahead(statuses: nil)
- jobs = ::Ci::JobsFinder.new(current_user: current_user, project: project, params: { scope: statuses }).execute
+ def resolve_with_lookahead(statuses: nil, with_artifacts: nil)
+ jobs = ::Ci::JobsFinder.new(
+ current_user: current_user, project: project, params: {
+ scope: statuses, with_artifacts: with_artifacts
+ }
+ ).execute
apply_lookahead(jobs)
end
@@ -26,7 +34,7 @@ module Resolvers
def preloads
{
- previous_stage_jobs_and_needs: [:needs, :pipeline],
+ previous_stage_jobs_or_needs: [:needs, :pipeline],
artifacts: [:job_artifacts],
pipeline: [:user]
}
diff --git a/app/graphql/resolvers/projects/fork_details_resolver.rb b/app/graphql/resolvers/projects/fork_details_resolver.rb
new file mode 100644
index 00000000000..fcc13a1bc1e
--- /dev/null
+++ b/app/graphql/resolvers/projects/fork_details_resolver.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Projects
+ class ForkDetailsResolver < BaseResolver
+ type Types::Projects::ForkDetailsType, null: true
+
+ argument :ref, GraphQL::Types::String,
+ required: false,
+ description: 'Ref of the fork. Default value is HEAD.'
+
+ alias_method :project, :object
+
+ def resolve(**args)
+ return unless project.forked?
+
+ ::Projects::Forks::DivergenceCounts.new(project, args[:ref]).counts
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/work_items/work_item_discussions_resolver.rb b/app/graphql/resolvers/work_items/work_item_discussions_resolver.rb
new file mode 100644
index 00000000000..b40d85e8003
--- /dev/null
+++ b/app/graphql/resolvers/work_items/work_item_discussions_resolver.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module WorkItems
+ class WorkItemDiscussionsResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+ extension Gitlab::Graphql::Extensions::ForwardOnlyExternallyPaginatedArrayExtension
+
+ authorize :read_work_item
+ authorizes_object!
+
+ # this resolver may be calling gitaly as part of parsing notes that contain commit references
+ calls_gitaly!
+
+ alias_method :notes_widget, :object
+
+ argument :filter, Types::WorkItems::NotesFilterTypeEnum,
+ required: false,
+ default_value: Types::WorkItems::NotesFilterTypeEnum.default_value,
+ description: 'Type of notes collection: ALL_NOTES, ONLY_COMMENTS, ONLY_ACTIVITY.'
+
+ type Types::Notes::DiscussionType.connection_type, null: true
+
+ def resolve(**args)
+ finder = Issuable::DiscussionsListService.new(current_user, work_item, params(args))
+
+ Gitlab::Graphql::ExternallyPaginatedArray.new(
+ finder.paginator.cursor_for_previous_page,
+ finder.paginator.cursor_for_next_page,
+ *finder.execute
+ )
+ end
+
+ def self.field_options
+ # we manage the pagination manually through external array, so opt out of the connection field extension
+ super.merge(connection: false)
+ end
+
+ def self.calculate_ext_conn_complexity
+ true
+ end
+
+ def self.complexity_multiplier(args)
+ 0.05
+ end
+
+ private
+
+ def work_item
+ notes_widget.work_item
+ end
+ strong_memoize_attr :work_item
+
+ def params(args)
+ {
+ notes_filter: args[:filter],
+ cursor: args[:after],
+ per_page: self.class.nodes_limit(args, @field, context: context)
+ }
+ end
+
+ def self.nodes_limit(args, field, **kwargs)
+ page_size = field&.max_page_size || kwargs[:context]&.schema&.default_max_page_size
+ [args[:first], page_size].compact.min
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/work_items_resolver.rb b/app/graphql/resolvers/work_items_resolver.rb
index 42f4f99d4a9..a3de875c196 100644
--- a/app/graphql/resolvers/work_items_resolver.rb
+++ b/app/graphql/resolvers/work_items_resolver.rb
@@ -55,6 +55,7 @@ module Resolvers
last_edited_by: :last_edited_by,
assignees: :assignees,
parent: :work_item_parent,
+ children: { work_item_children: [:author, { project: :project_feature }] },
labels: :labels,
milestone: :milestone
}
diff --git a/app/graphql/types/alert_management/alert_type.rb b/app/graphql/types/alert_management/alert_type.rb
index a0d19229d3d..a13453f9194 100644
--- a/app/graphql/types/alert_management/alert_type.rb
+++ b/app/graphql/types/alert_management/alert_type.rb
@@ -13,6 +13,11 @@ module Types
authorize :read_alert_management_alert
+ field :id,
+ GraphQL::Types::ID,
+ null: false,
+ description: 'ID of the alert.'
+
field :iid,
GraphQL::Types::ID,
null: false,
@@ -116,7 +121,10 @@ module Types
null: true,
description: 'Runbook for the alert as defined in alert details.'
- field :todos, description: 'To-do items of the current user for the alert.', resolver: Resolvers::TodosResolver
+ field :todos,
+ Types::TodoType.connection_type,
+ description: 'To-do items of the current user for the alert.',
+ resolver: Resolvers::TodosResolver
field :details_url,
GraphQL::Types::String,
diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb
index 36ba3399754..615c143a0b9 100644
--- a/app/graphql/types/base_field.rb
+++ b/app/graphql/types/base_field.rb
@@ -135,15 +135,16 @@ module Types
:resolver_complexity, args, child_complexity: child_complexity
).to_i
complexity += 1 if calls_gitaly?
- complexity += complexity * connection_complexity_multiplier(ctx, args)
+ ext_conn = resolver&.try(:calculate_ext_conn_complexity)
+ complexity += complexity * connection_complexity_multiplier(ctx, args, calculate_ext_conn_complexity: ext_conn)
complexity.to_i
end
end
- def connection_complexity_multiplier(ctx, args)
+ def connection_complexity_multiplier(ctx, args, calculate_ext_conn_complexity:)
# Resolvers may add extra complexity depending on number of items being loaded.
- return 0 unless connection?
+ return 0 if !connection? && !calculate_ext_conn_complexity
page_size = max_page_size || ctx.schema.default_max_page_size
limit_value = [args[:first], args[:last], page_size].compact.min
diff --git a/app/graphql/types/ci/config_variable_type.rb b/app/graphql/types/ci/config_variable_type.rb
index 5b5890fd5a5..020af5b2444 100644
--- a/app/graphql/types/ci/config_variable_type.rb
+++ b/app/graphql/types/ci/config_variable_type.rb
@@ -19,6 +19,7 @@ module Types
description: 'Value of the variable.'
field :value_options, [GraphQL::Types::String],
+ hash_key: :options,
null: true,
description: 'Value options for the variable.'
end
diff --git a/app/graphql/types/ci/freeze_period_status_enum.rb b/app/graphql/types/ci/freeze_period_status_enum.rb
new file mode 100644
index 00000000000..aebd0f537e9
--- /dev/null
+++ b/app/graphql/types/ci/freeze_period_status_enum.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ class FreezePeriodStatusEnum < BaseEnum
+ graphql_name 'CiFreezePeriodStatus'
+ description 'Deploy freeze period status'
+
+ value 'ACTIVE', value: :active, description: 'Freeze period is active.'
+ value 'INACTIVE', value: :inactive, description: 'Freeze period is inactive.'
+ end
+ end
+end
diff --git a/app/graphql/types/ci/freeze_period_type.rb b/app/graphql/types/ci/freeze_period_type.rb
new file mode 100644
index 00000000000..6a3f2ed8fa4
--- /dev/null
+++ b/app/graphql/types/ci/freeze_period_type.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ class FreezePeriodType < BaseObject
+ graphql_name 'CiFreezePeriod'
+ description 'Represents a deployment freeze window of a project'
+
+ authorize :read_freeze_period
+
+ present_using ::Ci::FreezePeriodPresenter
+
+ field :status, Types::Ci::FreezePeriodStatusEnum,
+ description: 'Freeze period status.',
+ null: false
+
+ field :start_cron, GraphQL::Types::String,
+ description: 'Start of the freeze period in cron format.',
+ null: false,
+ method: :freeze_start
+
+ field :end_cron, GraphQL::Types::String,
+ description: 'End of the freeze period in cron format.',
+ null: false,
+ method: :freeze_end
+
+ field :cron_timezone, GraphQL::Types::String,
+ description: 'Time zone for the cron fields, defaults to UTC if not provided.',
+ null: true
+
+ field :start_time, Types::TimeType,
+ description: 'Timestamp (UTC) of when the current/next active period starts.',
+ null: true
+
+ field :end_time, Types::TimeType,
+ description: 'Timestamp (UTC) of when the current/next active period ends.',
+ null: true,
+ method: :time_end_from_now
+ end
+ end
+end
diff --git a/app/graphql/types/ci/pipeline_schedule_type.rb b/app/graphql/types/ci/pipeline_schedule_type.rb
index 04f9fc78a92..904fa3f1c72 100644
--- a/app/graphql/types/ci/pipeline_schedule_type.rb
+++ b/app/graphql/types/ci/pipeline_schedule_type.rb
@@ -5,6 +5,8 @@ module Types
class PipelineScheduleType < BaseObject
graphql_name 'PipelineSchedule'
+ description 'Represents a pipeline schedule'
+
connection_type_class(Types::CountableConnectionType)
expose_permissions Types::PermissionTypes::Ci::PipelineSchedules
@@ -17,7 +19,9 @@ module Types
field :owner, ::Types::UserType, null: false, description: 'Owner of the pipeline schedule.'
- field :active, GraphQL::Types::Boolean, null: false, description: 'Indicates if a pipeline schedule is active.'
+ field :active, GraphQL::Types::Boolean, null: false, description: 'Indicates if the pipeline schedule is active.'
+
+ field :project, ::Types::ProjectType, null: true, description: 'Project of the pipeline schedule.'
field :next_run_at, Types::TimeType, null: false, description: 'Time when the next pipeline will run.'
@@ -26,20 +30,50 @@ module Types
field :last_pipeline, PipelineType, null: true, description: 'Last pipeline object.'
field :ref_for_display, GraphQL::Types::String,
- null: true, description: 'Git ref for the pipeline schedule.', method: :ref_for_display
-
- field :ref_path, GraphQL::Types::String, null: true, description: 'Path to the ref that triggered the pipeline.'
+ null: true, description: 'Git ref for the pipeline schedule.'
field :for_tag, GraphQL::Types::Boolean,
null: false, description: 'Indicates if a pipelines schedule belongs to a tag.', method: :for_tag?
- field :cron, GraphQL::Types::String, null: false, description: 'Cron notation for the schedule.'
+ field :edit_path, GraphQL::Types::String,
+ null: true,
+ description: 'Edit path of the pipeline schedule.',
+ authorize: :update_pipeline_schedule
+
+ field :variables,
+ Types::Ci::PipelineScheduleVariableType.connection_type,
+ null: true,
+ description: 'Pipeline schedule variables.',
+ authorize: :read_pipeline_schedule_variables
+
+ field :ref, GraphQL::Types::String,
+ null: true, description: 'Ref of the pipeline schedule.', method: :ref_for_display
+
+ field :ref_path, GraphQL::Types::String,
+ null: true,
+ description: 'Path to the ref that triggered the pipeline.'
- field :cron_timezone, GraphQL::Types::String, null: false, description: 'Timezone for the pipeline schedule.'
+ field :cron, GraphQL::Types::String,
+ null: false,
+ description: 'Cron notation for the schedule.'
+
+ field :cron_timezone, GraphQL::Types::String,
+ null: false,
+ description: 'Timezone for the pipeline schedule.'
+
+ field :created_at, Types::TimeType,
+ null: false, description: 'Timestamp of when the pipeline schedule was created.'
+
+ field :updated_at, Types::TimeType,
+ null: false, description: 'Timestamp of when the pipeline schedule was last updated.'
def ref_path
::Gitlab::Routing.url_helpers.project_commits_path(object.project, object.ref_for_display)
end
+
+ def edit_path
+ ::Gitlab::Routing.url_helpers.edit_project_pipeline_schedule_path(object.project, object)
+ end
end
end
end
diff --git a/app/graphql/types/ci/pipeline_schedule_variable_type.rb b/app/graphql/types/ci/pipeline_schedule_variable_type.rb
new file mode 100644
index 00000000000..1cb407bc2e4
--- /dev/null
+++ b/app/graphql/types/ci/pipeline_schedule_variable_type.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ class PipelineScheduleVariableType < BaseObject
+ graphql_name 'PipelineScheduleVariable'
+
+ authorize :read_pipeline_schedule_variables
+
+ implements(VariableInterface)
+ end
+ end
+end
diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb
index 4a523f2edd9..cb561f48b3b 100644
--- a/app/graphql/types/ci/pipeline_type.rb
+++ b/app/graphql/types/ci/pipeline_type.rb
@@ -78,7 +78,7 @@ module Types
resolver: Resolvers::Ci::PipelineStagesResolver
field :user,
- type: Types::UserType,
+ type: 'Types::UserType',
null: true,
description: 'Pipeline user.'
diff --git a/app/graphql/types/ci/runner_job_execution_status_enum.rb b/app/graphql/types/ci/runner_job_execution_status_enum.rb
new file mode 100644
index 00000000000..686ea085199
--- /dev/null
+++ b/app/graphql/types/ci/runner_job_execution_status_enum.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ class RunnerJobExecutionStatusEnum < BaseEnum
+ graphql_name 'CiRunnerJobExecutionStatus'
+
+ value 'IDLE',
+ description: "Runner is idle.",
+ value: :idle,
+ deprecated: { milestone: '15.7', reason: :alpha }
+
+ value 'RUNNING',
+ description: 'Runner is executing jobs.',
+ value: :running,
+ deprecated: { milestone: '15.7', reason: :alpha }
+ end
+ end
+end
diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb
index a9c76974850..5d34906f7b8 100644
--- a/app/graphql/types/ci/runner_type.rb
+++ b/app/graphql/types/ci/runner_type.rb
@@ -23,6 +23,9 @@ module Types
deprecated: { reason: 'Use paused', milestone: '14.8' }
field :admin_url, GraphQL::Types::String, null: true,
description: 'Admin URL of the runner. Only available for administrators.'
+ field :architecture_name, GraphQL::Types::String, null: true,
+ description: 'Architecture provided by the the runner.',
+ method: :architecture
field :contacted_at, Types::TimeType, null: true,
description: 'Timestamp of last contact from this runner.',
method: :contacted_at
@@ -35,32 +38,39 @@ module Types
field :executor_name, GraphQL::Types::String, null: true,
description: 'Executor last advertised by the runner.',
method: :executor_name
- field :platform_name, GraphQL::Types::String, null: true,
- description: 'Platform provided by the runner.',
- method: :platform
- field :architecture_name, GraphQL::Types::String, null: true,
- description: 'Architecture provided by the the runner.',
- method: :architecture
- field :maintenance_note, GraphQL::Types::String, null: true,
- description: 'Runner\'s maintenance notes.'
- field :groups, ::Types::GroupType.connection_type, null: true,
- description: 'Groups the runner is associated with. For group runners only.'
+ field :groups, 'Types::GroupConnection',
+ null: true,
+ resolver: ::Resolvers::Ci::RunnerGroupsResolver,
+ description: 'Groups the runner is associated with. For group runners only.'
field :id, ::Types::GlobalIDType[::Ci::Runner], null: false,
description: 'ID of the runner.'
field :ip_address, GraphQL::Types::String, null: true,
description: 'IP address of the runner.'
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 :job_execution_status,
+ Types::Ci::RunnerJobExecutionStatusEnum,
+ null: true,
+ description: 'Job execution status of the runner.',
+ deprecated: { milestone: '15.7', reason: :alpha }
field :jobs, ::Types::Ci::JobType.connection_type, null: true,
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,
description: 'Indicates the runner is locked.'
+ field :maintenance_note, GraphQL::Types::String, null: true,
+ description: 'Runner\'s maintenance notes.'
field :maximum_timeout, GraphQL::Types::Int, null: true,
description: 'Maximum timeout (in seconds) for jobs processed by the runner.'
+ field :owner_project, ::Types::ProjectType, null: true,
+ description: 'Project that owns the runner. For project runners only.',
+ resolver: ::Resolvers::Ci::RunnerOwnerProjectResolver
field :paused, GraphQL::Types::Boolean, null: false,
description: 'Indicates the runner is paused and not available to run jobs.'
+ field :platform_name, GraphQL::Types::String, null: true,
+ description: 'Platform provided by the runner.',
+ method: :platform
field :project_count, GraphQL::Types::Int, null: true,
description: 'Number of projects that the runner is associated with.'
field :projects,
@@ -88,9 +98,6 @@ module Types
method: :token_expires_at
field :version, GraphQL::Types::String, null: true,
description: 'Version of the runner.'
- field :owner_project, ::Types::ProjectType, null: true,
- description: 'Project that owns the runner. For project runners only.',
- resolver: ::Resolvers::Ci::RunnerOwnerProjectResolver
markdown_field :maintenance_note_html, null: true
@@ -99,8 +106,25 @@ module Types
end
def job_count
- # We limit to 1 above the JOB_COUNT_LIMIT to indicate that more items exist after JOB_COUNT_LIMIT
- runner.builds.limit(JOB_COUNT_LIMIT + 1).count
+ BatchLoader::GraphQL.for(runner.id).batch(key: :job_count) do |runner_ids, loader, _args|
+ # rubocop: disable CodeReuse/ActiveRecord
+ # We limit to 1 above the JOB_COUNT_LIMIT to indicate that more items exist after JOB_COUNT_LIMIT
+ builds_tbl = ::Ci::Build.arel_table
+ runners_tbl = ::Ci::Runner.arel_table
+ lateral_query = ::Ci::Build.select(1)
+ .where(builds_tbl['runner_id'].eq(runners_tbl['id']))
+ .limit(JOB_COUNT_LIMIT + 1)
+ counts = ::Ci::Runner.joins("JOIN LATERAL (#{lateral_query.to_sql}) builds_with_limit ON true")
+ .id_in(runner_ids)
+ .select(:id, Arel.star.count.as('count'))
+ .group(:id)
+ .index_by(&:id)
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ runner_ids.each do |runner_id|
+ loader.call(runner_id, counts[runner_id]&.count || 0)
+ end
+ end
end
def admin_url
@@ -111,14 +135,13 @@ module Types
Gitlab::Routing.url_helpers.edit_admin_runner_url(runner) if can_admin_runners?
end
- # rubocop: disable CodeReuse/ActiveRecord
def project_count
BatchLoader::GraphQL.for(runner.id).batch(key: :runner_project_count) do |ids, loader, args|
counts = ::Ci::Runner.project_type
.select(:id, 'COUNT(ci_runner_projects.id) as count')
.left_outer_joins(:runner_projects)
- .where(id: ids)
- .group(:id)
+ .id_in(ids)
+ .group(:id) # rubocop: disable CodeReuse/ActiveRecord
.index_by(&:id)
ids.each do |id|
@@ -126,12 +149,15 @@ module Types
end
end
end
- # rubocop: enable CodeReuse/ActiveRecord
- def groups
- return unless runner.group_type?
+ def job_execution_status
+ BatchLoader::GraphQL.for(runner.id).batch(key: :running_builds_exist) do |runner_ids, loader|
+ statuses = ::Ci::Runner.id_in(runner_ids).with_running_builds.index_by(&:id)
- batched_owners(::Ci::RunnerNamespace, Group, :runner_groups, :namespace_id)
+ runner_ids.each do |runner_id|
+ loader.call(runner_id, statuses[runner_id] ? :running : :idle)
+ end
+ end
end
private
@@ -139,29 +165,6 @@ module Types
def can_admin_runners?
context[:current_user]&.can_admin_all_resources?
end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def batched_owners(runner_assoc_type, assoc_type, key, column_name)
- BatchLoader::GraphQL.for(runner.id).batch(key: key) do |runner_ids, loader|
- plucked_runner_and_owner_ids = runner_assoc_type
- .select(:runner_id, column_name)
- .where(runner_id: runner_ids)
- .pluck(:runner_id, column_name)
- # In plucked_runner_and_owner_ids, first() represents the runner ID, and second() the owner ID,
- # so let's group the owner IDs by runner ID
- runner_owner_ids_by_runner_id = plucked_runner_and_owner_ids
- .group_by(&:first)
- .transform_values { |runner_and_owner_id| runner_and_owner_id.map(&:second) }
-
- owner_ids = runner_owner_ids_by_runner_id.values.flatten.uniq
- owners = assoc_type.where(id: owner_ids).index_by(&:id)
-
- 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
end
end
end
diff --git a/app/graphql/types/commit_signature_interface.rb b/app/graphql/types/commit_signature_interface.rb
index 6b0c16e538a..0449a0634ef 100644
--- a/app/graphql/types/commit_signature_interface.rb
+++ b/app/graphql/types/commit_signature_interface.rb
@@ -21,7 +21,8 @@ module Types
description: 'Project of the associated commit.'
orphan_types Types::CommitSignatures::GpgSignatureType,
- Types::CommitSignatures::X509SignatureType
+ Types::CommitSignatures::X509SignatureType,
+ Types::CommitSignatures::SshSignatureType
def self.resolve_type(object, context)
case object
@@ -29,6 +30,8 @@ module Types
Types::CommitSignatures::GpgSignatureType
when ::CommitSignatures::X509CommitSignature
Types::CommitSignatures::X509SignatureType
+ when ::CommitSignatures::SshSignature
+ Types::CommitSignatures::SshSignatureType
else
raise 'Unsupported commit signature type'
end
diff --git a/app/graphql/types/commit_signatures/gpg_signature_type.rb b/app/graphql/types/commit_signatures/gpg_signature_type.rb
index 2a845fff3e2..3baf2d9d21d 100644
--- a/app/graphql/types/commit_signatures/gpg_signature_type.rb
+++ b/app/graphql/types/commit_signatures/gpg_signature_type.rb
@@ -11,6 +11,7 @@ module Types
authorize :download_code
field :user, Types::UserType, null: true,
+ method: :signed_by_user,
description: 'User associated with the key.'
field :gpg_key_user_name, GraphQL::Types::String,
diff --git a/app/graphql/types/commit_signatures/ssh_signature_type.rb b/app/graphql/types/commit_signatures/ssh_signature_type.rb
new file mode 100644
index 00000000000..92eb4f7949a
--- /dev/null
+++ b/app/graphql/types/commit_signatures/ssh_signature_type.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Types
+ module CommitSignatures
+ class SshSignatureType < Types::BaseObject
+ graphql_name 'SshSignature'
+ description 'SSH signature for a signed commit'
+
+ implements Types::CommitSignatureInterface
+
+ authorize :download_code
+
+ field :user, Types::UserType, null: true,
+ method: :signed_by_user,
+ calls_gitaly: true,
+ description: 'User associated with the key.'
+
+ field :key, Types::KeyType,
+ null: true,
+ description: 'SSH key used for the signature.'
+ end
+ end
+end
diff --git a/app/graphql/types/commit_signatures/x509_signature_type.rb b/app/graphql/types/commit_signatures/x509_signature_type.rb
index 9ac96dbc015..2d58c3d5b5d 100644
--- a/app/graphql/types/commit_signatures/x509_signature_type.rb
+++ b/app/graphql/types/commit_signatures/x509_signature_type.rb
@@ -11,6 +11,7 @@ module Types
authorize :download_code
field :user, Types::UserType, null: true,
+ method: :signed_by_user,
calls_gitaly: true,
description: 'User associated with the key.'
diff --git a/app/graphql/types/container_repository_type.rb b/app/graphql/types/container_repository_type.rb
index cb818ac5e92..dfa599e798c 100644
--- a/app/graphql/types/container_repository_type.rb
+++ b/app/graphql/types/container_repository_type.rb
@@ -13,6 +13,7 @@ module Types
field :expiration_policy_cleanup_status, Types::ContainerRepositoryCleanupStatusEnum, null: true, description: 'Tags cleanup status for the container repository.'
field :expiration_policy_started_at, Types::TimeType, null: true, description: 'Timestamp when the cleanup done by the expiration policy was started on the container repository.'
field :id, GraphQL::Types::ID, null: false, description: 'ID of the container repository.'
+ field :last_cleanup_deleted_tags_count, GraphQL::Types::Int, null: true, description: 'Number of deleted tags from the last cleanup.'
field :location, GraphQL::Types::String, null: false, description: 'URL of the container repository.'
field :migration_state, GraphQL::Types::String, null: false, description: 'Migration state of the container repository.'
field :name, GraphQL::Types::String, null: false, description: 'Name of the container repository.'
@@ -21,7 +22,6 @@ module Types
field :status, Types::ContainerRepositoryStatusEnum, null: true, description: 'Status of the container repository.'
field :tags_count, GraphQL::Types::Int, null: false, description: 'Number of tags associated with this image.'
field :updated_at, Types::TimeType, null: false, description: 'Timestamp when the container repository was updated.'
- field :last_cleanup_deleted_tags_count, GraphQL::Types::Int, null: true, description: 'Number of deleted tags from the last cleanup.'
def can_delete
Ability.allowed?(current_user, :update_container_image, object)
diff --git a/app/graphql/types/dependency_proxy/manifest_type.rb b/app/graphql/types/dependency_proxy/manifest_type.rb
index f7e751e30d3..53b7610e490 100644
--- a/app/graphql/types/dependency_proxy/manifest_type.rb
+++ b/app/graphql/types/dependency_proxy/manifest_type.rb
@@ -14,11 +14,11 @@ module Types
field :id, ::Types::GlobalIDType[::DependencyProxy::Manifest], null: false, description: 'ID of the manifest.'
field :image_name, GraphQL::Types::String, null: false, description: 'Name of the image.'
field :size, GraphQL::Types::String, null: false, description: 'Size of the manifest file.'
- field :updated_at, Types::TimeType, null: false, description: 'Date of most recent update.'
field :status,
Types::DependencyProxy::ManifestTypeEnum,
null: false,
description: "Status of the manifest (#{::DependencyProxy::Manifest.statuses.keys.join(', ')})"
+ field :updated_at, Types::TimeType, null: false, description: 'Date of most recent update.'
def image_name
object.file_name.chomp(File.extname(object.file_name))
diff --git a/app/graphql/types/deployment_details_type.rb b/app/graphql/types/deployment_details_type.rb
deleted file mode 100644
index bbb5cc8e3f1..00000000000
--- a/app/graphql/types/deployment_details_type.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# 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
-
-Types::DeploymentDetailsType.prepend_mod_with('Types::DeploymentDetailsType')
diff --git a/app/graphql/types/deployment_type.rb b/app/graphql/types/deployment_type.rb
index 59b59dc4e1d..1c23fd44ea1 100644
--- a/app/graphql/types/deployment_type.rb
+++ b/app/graphql/types/deployment_type.rb
@@ -1,12 +1,6 @@
# 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'
@@ -15,6 +9,8 @@ module Types
authorize :read_deployment
+ expose_permissions Types::PermissionTypes::Deployment
+
field :id,
GraphQL::Types::ID,
description: 'Global ID of the deployment.'
@@ -65,5 +61,15 @@ module Types
Types::UserType,
description: 'User who executed the deployment.',
method: :deployed_by
+
+ field :tags,
+ [Types::DeploymentTagType],
+ description: 'Git tags that contain this deployment. ' \
+ 'This field can only be resolved for one deployment in any single request.',
+ calls_gitaly: true do
+ extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
+ end
end
end
+
+Types::DeploymentType.prepend_mod_with('Types::DeploymentType')
diff --git a/app/graphql/types/environment_type.rb b/app/graphql/types/environment_type.rb
index dd2286d333d..5f58fc38540 100644
--- a/app/graphql/types/environment_type.rb
+++ b/app/graphql/types/environment_type.rb
@@ -9,6 +9,12 @@ module Types
authorize :read_environment
+ expose_permissions Types::PermissionTypes::Environment,
+ description: 'Permissions for the current user on the resource. '\
+ 'This field can only be resolved for one environment in any single request.' do
+ extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
+ end
+
field :name, GraphQL::Types::String, null: false,
description: 'Human-readable name of the environment.'
@@ -67,6 +73,11 @@ module Types
description: 'Last deployment of the environment.',
resolver: Resolvers::Environments::LastDeploymentResolver
+ field :deploy_freezes,
+ [Types::Ci::FreezePeriodType],
+ null: true,
+ description: 'Deployment freeze periods of the environment.'
+
def tier
object.tier.to_sym
end
diff --git a/app/graphql/types/global_id_type.rb b/app/graphql/types/global_id_type.rb
index a71c2fb0e6c..7ebd98ff2e7 100644
--- a/app/graphql/types/global_id_type.rb
+++ b/app/graphql/types/global_id_type.rb
@@ -49,9 +49,7 @@ module Types
An example `#{graphql_name}` is: `"#{::Gitlab::GlobalId.build(model_name: model_name, id: 1)}"`.
#{
if deprecation = Gitlab::GlobalId::Deprecations.deprecation_by(model_name)
- 'The older format `"' +
- ::Gitlab::GlobalId.build(model_name: deprecation.old_name, id: 1).to_s +
- '"` was deprecated in ' + deprecation.milestone + '.'
+ "The older format `\"#{::Gitlab::GlobalId.build(model_name: deprecation.old_name, id: 1)}\"` was deprecated in #{deprecation.milestone}."
end}
MD
diff --git a/app/graphql/types/group_connection.rb b/app/graphql/types/group_connection.rb
new file mode 100644
index 00000000000..e4332e24302
--- /dev/null
+++ b/app/graphql/types/group_connection.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+# Normally this wouldn't be needed and we could use
+#
+# type Types::GroupType.connection_type, null: true
+#
+# in a resolver. However we can end up with cyclic definitions.
+# Running the spec locally can result in errors like
+#
+# NameError: uninitialized constant Types::GroupType
+#
+# or other errors. To fix this, we created this file and use
+#
+# type "Types::GroupConnection", null: true
+#
+# which gives a delayed resolution, and the proper connection type.
+#
+# See gitlab/app/graphql/types/ci/runner_type.rb
+# Reference: https://github.com/rmosolgo/graphql-ruby/issues/3974#issuecomment-1084444214
+# and https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#testing-tips-and-tricks
+#
+Types::GroupConnection = Types::GroupType.connection_type
diff --git a/app/graphql/types/issue_type_enum.rb b/app/graphql/types/issue_type_enum.rb
index 78cd27f60c3..d7f587ff03d 100644
--- a/app/graphql/types/issue_type_enum.rb
+++ b/app/graphql/types/issue_type_enum.rb
@@ -16,5 +16,9 @@ module Types
value 'OBJECTIVE', value: 'objective',
description: 'Objective issue type. Available only when feature flag `okrs_mvc` is enabled.',
alpha: { milestone: '15.6' }
+
+ value 'KEY_RESULT', value: 'key_result',
+ description: 'Key Result issue type. Available only when feature flag `okrs_mvc` is enabled.',
+ alpha: { milestone: '15.7' }
end
end
diff --git a/app/graphql/types/key_type.rb b/app/graphql/types/key_type.rb
new file mode 100644
index 00000000000..30699793045
--- /dev/null
+++ b/app/graphql/types/key_type.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Types
+ class KeyType < Types::BaseObject # rubocop:disable Graphql/AuthorizeTypes
+ graphql_name 'Key'
+ description 'Represents an SSH key.'
+
+ field :created_at, Types::TimeType, null: false,
+ description: 'Timestamp of when the key was created.'
+ field :expires_at, Types::TimeType, null: false,
+ description: "Timestamp of when the key expires. It's null if it never expires."
+ field :id, GraphQL::Types::ID, null: false, description: 'ID of the key.'
+ field :key, GraphQL::Types::String, null: false, method: :publishable_key,
+ description: 'Public key of the key pair.'
+ field :title, GraphQL::Types::String, null: false, description: 'Title of the key.'
+ end
+end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 49bf7aa638c..abf7b3ad530 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -200,10 +200,10 @@ module Types
description: 'Array of available auto merge strategies.'
field :commits, Types::CommitType.connection_type, null: true,
calls_gitaly: true, description: 'Merge request commits.'
- field :committers, Types::UserType.connection_type, null: true, complexity: 5,
- calls_gitaly: true, description: 'Users who have added commits to the merge request.'
field :commits_without_merge_commits, Types::CommitType.connection_type, null: true,
calls_gitaly: true, description: 'Merge request commits excluding merge commits.'
+ field :committers, Types::UserType.connection_type, null: true, complexity: 5,
+ calls_gitaly: true, description: 'Users who have added commits to the merge request.'
field :has_ci, GraphQL::Types::Boolean, null: false, method: :has_ci?,
description: 'Indicates if the merge request has CI.'
field :merge_user, Types::UserType, null: true,
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 1cbb2ede544..b342e57804b 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -63,6 +63,8 @@ module Types
mount_mutation Mutations::Issues::SetEscalationStatus
mount_mutation Mutations::Issues::Update
mount_mutation Mutations::Issues::Move
+ mount_mutation Mutations::Issues::LinkAlerts
+ mount_mutation Mutations::Issues::UnlinkAlert
mount_mutation Mutations::Labels::Create
mount_mutation Mutations::MergeRequests::Accept
mount_mutation Mutations::MergeRequests::Create
@@ -117,6 +119,8 @@ module Types
mount_mutation Mutations::Ci::Pipeline::Retry
mount_mutation Mutations::Ci::PipelineSchedule::Delete
mount_mutation Mutations::Ci::PipelineSchedule::TakeOwnership
+ mount_mutation Mutations::Ci::PipelineSchedule::Play
+ mount_mutation Mutations::Ci::PipelineSchedule::Create
mount_mutation Mutations::Ci::CiCdSettingsUpdate, deprecated: {
reason: :renamed,
replacement: 'ProjectCiCdSettingsUpdate',
diff --git a/app/graphql/types/nested_environment_type.rb b/app/graphql/types/nested_environment_type.rb
new file mode 100644
index 00000000000..b835af2bf45
--- /dev/null
+++ b/app/graphql/types/nested_environment_type.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ class NestedEnvironmentType < BaseObject
+ graphql_name 'NestedEnvironment'
+ description 'Describes where code is deployed for a project organized by folder.'
+
+ field :name, GraphQL::Types::String,
+ null: false, description: 'Human-readable name of the environment.'
+
+ field :size, GraphQL::Types::Int,
+ null: false, description: 'Number of environments nested in the folder.'
+
+ field :environment,
+ Types::EnvironmentType,
+ null: true, description: 'Latest environment in the folder.'
+
+ def environment
+ BatchLoader::GraphQL.for(object.last_id).batch do |environment_ids, loader|
+ Environment.id_in(environment_ids).each do |environment|
+ loader.call(environment.id, environment)
+ end
+ end
+ end
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+end
diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb
index eef5ce40bde..05629ea9223 100644
--- a/app/graphql/types/notes/note_type.rb
+++ b/app/graphql/types/notes/note_type.rb
@@ -77,6 +77,14 @@ module Types
def author
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
end
+
+ # We now support also SyntheticNote notes as a NoteType, but SyntheticNote does not have a real note ID,
+ # as SyntheticNote is generated dynamically from a ResourceEvent instance.
+ def id
+ return super unless object.is_a?(SyntheticNote)
+
+ ::Gitlab::GlobalId.build(object, model_name: object.class.to_s, id: object.discussion_id)
+ end
end
end
end
diff --git a/app/graphql/types/packages/package_links_type.rb b/app/graphql/types/packages/package_links_type.rb
index f16937530b9..eb29fb655bd 100644
--- a/app/graphql/types/packages/package_links_type.rb
+++ b/app/graphql/types/packages/package_links_type.rb
@@ -12,6 +12,8 @@ module Types
field :web_path, GraphQL::Types::String, null: true, description: 'Path to the package details page.'
def web_path
+ return unless object.default?
+
package_path(object)
end
end
diff --git a/app/graphql/types/permission_types/base_permission_type.rb b/app/graphql/types/permission_types/base_permission_type.rb
index 07e6e7a55d6..0192af25d0f 100644
--- a/app/graphql/types/permission_types/base_permission_type.rb
+++ b/app/graphql/types/permission_types/base_permission_type.rb
@@ -11,20 +11,20 @@ module Types
abilities.each { |ability| ability_field(ability) }
end
- def self.ability_field(ability, **kword_args)
+ def self.ability_field(ability, **kword_args, &block)
define_field_resolver_method(ability) unless resolving_keywords?(kword_args)
- permission_field(ability, **kword_args)
+ permission_field(ability, **kword_args, &block)
end
- def self.permission_field(name, **kword_args)
+ def self.permission_field(name, **kword_args, &block)
kword_args = kword_args.reverse_merge(
name: name,
type: GraphQL::Types::Boolean,
description: "Indicates the user can perform `#{name}` on this resource",
null: false)
- field(**kword_args) # rubocop:disable Graphql/Descriptions
+ field(**kword_args, &block) # rubocop:disable Graphql/Descriptions
end
def self.define_field_resolver_method(ability)
diff --git a/app/graphql/types/permission_types/deployment.rb b/app/graphql/types/permission_types/deployment.rb
new file mode 100644
index 00000000000..fce376552b1
--- /dev/null
+++ b/app/graphql/types/permission_types/deployment.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ module PermissionTypes
+ class Deployment < BasePermissionType
+ graphql_name 'DeploymentPermissions'
+
+ abilities :destroy_deployment
+ ability_field :update_deployment, calls_gitaly: true
+ end
+ end
+end
+
+Types::PermissionTypes::Deployment.prepend_mod_with('Types::PermissionTypes::Deployment')
diff --git a/app/graphql/types/permission_types/environment.rb b/app/graphql/types/permission_types/environment.rb
new file mode 100644
index 00000000000..59c9fce64e5
--- /dev/null
+++ b/app/graphql/types/permission_types/environment.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Types
+ module PermissionTypes
+ class Environment < BasePermissionType
+ graphql_name 'EnvironmentPermissions'
+
+ abilities :update_environment, :destroy_environment, :stop_environment
+ end
+ end
+end
diff --git a/app/graphql/types/permission_types/project.rb b/app/graphql/types/permission_types/project.rb
index f6a5563d367..c833b512222 100644
--- a/app/graphql/types/permission_types/project.rb
+++ b/app/graphql/types/permission_types/project.rb
@@ -17,7 +17,8 @@ module Types
:admin_wiki, :admin_project, :update_pages,
:admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki,
:create_pages, :destroy_pages, :read_pages_content, :admin_operations,
- :read_merge_request, :read_design, :create_design, :destroy_design
+ :read_merge_request, :read_design, :create_design, :destroy_design,
+ :read_environment
permission_field :create_snippet
diff --git a/app/graphql/types/project_statistics_type.rb b/app/graphql/types/project_statistics_type.rb
index c43baf1280b..a1d721856a9 100644
--- a/app/graphql/types/project_statistics_type.rb
+++ b/app/graphql/types/project_statistics_type.rb
@@ -11,6 +11,10 @@ module Types
field :build_artifacts_size, GraphQL::Types::Float, null: false,
description: 'Build artifacts size of the project in bytes.'
+ field :container_registry_size,
+ GraphQL::Types::Float,
+ null: true,
+ description: 'Container Registry size of the project in bytes.'
field :lfs_objects_size,
GraphQL::Types::Float,
null: false,
@@ -29,9 +33,5 @@ module Types
description: 'Uploads size of the project in bytes.'
field :wiki_size, GraphQL::Types::Float, null: true,
description: 'Wiki size of the project in bytes.'
- field :container_registry_size,
- GraphQL::Types::Float,
- null: true,
- description: 'Container Registry size of the project in bytes.'
end
end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 771dad00fb3..fe13ee7ef3c 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -258,8 +258,11 @@ module Types
field :environments,
Types::EnvironmentType.connection_type,
null: true,
- description: 'Environments of the project.',
- resolver: Resolvers::EnvironmentsResolver
+ description: 'Environments of the project. ' \
+ 'This field can only be resolved for one project in any single request.',
+ resolver: Resolvers::EnvironmentsResolver do
+ extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
+ end
field :environment,
Types::EnvironmentType,
@@ -267,8 +270,18 @@ module Types
description: 'A single environment of the project.',
resolver: Resolvers::EnvironmentsResolver.single
+ field :nested_environments,
+ Types::NestedEnvironmentType.connection_type,
+ null: true,
+ calls_gitaly: true,
+ description: 'Environments for this project with nested folders, ' \
+ 'can only be resolved for one project in any single request',
+ resolver: Resolvers::Environments::NestedEnvironmentsResolver do
+ extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1
+ end
+
field :deployment,
- Types::DeploymentDetailsType,
+ Types::DeploymentType,
null: true,
description: 'Details of the deployment of the project.',
resolver: Resolvers::DeploymentResolver.single
@@ -526,6 +539,13 @@ module Types
resolver: Resolvers::Projects::ForkTargetsResolver,
description: 'Namespaces in which the current user can fork the project into.'
+ field :fork_details, Types::Projects::ForkDetailsType,
+ calls_gitaly: true,
+ alpha: { milestone: '15.7' },
+ authorize: :read_code,
+ resolver: Resolvers::Projects::ForkDetailsResolver,
+ description: 'Details of the fork project compared to its upstream project.'
+
field :branch_rules,
Types::Projects::BranchRuleType.connection_type,
null: true,
@@ -537,6 +557,11 @@ module Types
description: "Programming languages used in the project.",
calls_gitaly: true
+ field :runners, Types::Ci::RunnerType.connection_type,
+ null: true,
+ resolver: ::Resolvers::Ci::ProjectRunnersResolver,
+ description: "Find runners visible to the current user."
+
def timelog_categories
object.project_namespace.timelog_categories if Feature.enabled?(:timelog_categories)
end
diff --git a/app/graphql/types/projects/fork_details_type.rb b/app/graphql/types/projects/fork_details_type.rb
new file mode 100644
index 00000000000..88c17d89620
--- /dev/null
+++ b/app/graphql/types/projects/fork_details_type.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Types
+ module Projects
+ # rubocop: disable Graphql/AuthorizeTypes
+ class ForkDetailsType < BaseObject
+ graphql_name 'ForkDetails'
+ description 'Details of the fork project compared to its upstream project.'
+
+ field :ahead, GraphQL::Types::Int,
+ null: true,
+ description: 'Number of commits ahead of upstream.'
+
+ field :behind, GraphQL::Types::Int,
+ null: true,
+ description: 'Number of commits behind upstream.'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 21cb3f9e06c..7263f792bae 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. This field can only be resolved for one query in any single request.',
+ description: 'Find a package. This field can only be resolved for one query in any single request. Returns `null` if a package has no `default` status.',
resolver: Resolvers::PackageDetailsResolver
field :user, Types::UserType,
diff --git a/app/graphql/types/release_type.rb b/app/graphql/types/release_type.rb
index a20e53ad1bd..8516256b433 100644
--- a/app/graphql/types/release_type.rb
+++ b/app/graphql/types/release_type.rb
@@ -13,9 +13,6 @@ module Types
present_using ReleasePresenter
- field :id, ::Types::GlobalIDType[Release],
- null: false,
- description: 'Global ID of the release.'
field :assets, Types::ReleaseAssetsType, null: true, method: :itself,
description: 'Assets of the release.'
field :created_at, Types::TimeType, null: true,
@@ -26,6 +23,11 @@ module Types
description: 'Description (also known as "release notes") of the release.'
field :evidences, Types::EvidenceType.connection_type, null: true,
description: 'Evidence for the release.'
+ field :historical_release, GraphQL::Types::Boolean, null: true, method: :historical_release?,
+ description: 'Indicates the release is an historical release.'
+ field :id, ::Types::GlobalIDType[Release],
+ null: false,
+ description: 'Global ID of the release.'
field :links, Types::ReleaseLinksType, null: true, method: :itself,
description: 'Links of the release.'
field :milestones, Types::MilestoneType.connection_type, null: true,
@@ -42,8 +44,6 @@ module Types
authorize: :read_code
field :upcoming_release, GraphQL::Types::Boolean, null: true, method: :upcoming_release?,
description: 'Indicates the release is an upcoming release.'
- field :historical_release, GraphQL::Types::Boolean, null: true, method: :historical_release?,
- description: 'Indicates the release is an historical release.'
field :author, Types::UserType, null: true,
description: 'User that created the release.'
diff --git a/app/graphql/types/root_storage_statistics_type.rb b/app/graphql/types/root_storage_statistics_type.rb
index b1b712aab38..64aaf3e73a0 100644
--- a/app/graphql/types/root_storage_statistics_type.rb
+++ b/app/graphql/types/root_storage_statistics_type.rb
@@ -7,6 +7,7 @@ module Types
authorize :read_statistics
field :build_artifacts_size, GraphQL::Types::Float, null: false, description: 'CI artifacts size in bytes.'
+ field :container_registry_size, GraphQL::Types::Float, null: false, description: 'Container Registry size in bytes.'
field :dependency_proxy_size, GraphQL::Types::Float, null: false, description: 'Dependency Proxy sizes in bytes.'
field :lfs_objects_size, GraphQL::Types::Float, null: false, description: 'LFS objects size in bytes.'
field :packages_size, GraphQL::Types::Float, null: false, description: 'Packages size in bytes.'
@@ -16,6 +17,5 @@ module Types
field :storage_size, GraphQL::Types::Float, null: false, description: 'Total storage in bytes.'
field :uploads_size, GraphQL::Types::Float, null: false, description: 'Uploads size in bytes.'
field :wiki_size, GraphQL::Types::Float, null: false, description: 'Wiki size in bytes.'
- field :container_registry_size, GraphQL::Types::Float, null: false, description: 'Container Registry size in bytes.'
end
end
diff --git a/app/graphql/types/subscription_type.rb b/app/graphql/types/subscription_type.rb
index 9d5edec82b2..f7f26ba4c5a 100644
--- a/app/graphql/types/subscription_type.rb
+++ b/app/graphql/types/subscription_type.rb
@@ -34,6 +34,11 @@ module Types
subscription: Subscriptions::IssuableUpdated,
null: true,
description: 'Triggered when the merge status of a merge request is updated.'
+
+ field :merge_request_approval_state_updated,
+ subscription: Subscriptions::IssuableUpdated,
+ null: true,
+ description: 'Triggered when approval state of a merge request is updated.'
end
end
diff --git a/app/graphql/types/todo_action_enum.rb b/app/graphql/types/todo_action_enum.rb
index ef43b6eb464..33e1c4e98a4 100644
--- a/app/graphql/types/todo_action_enum.rb
+++ b/app/graphql/types/todo_action_enum.rb
@@ -5,11 +5,12 @@ module Types
value 'assigned', value: 1, description: 'User was assigned.'
value 'mentioned', value: 2, description: 'User was mentioned.'
value 'build_failed', value: 3, description: 'Build triggered by the user failed.'
- value 'marked', value: 4, description: 'User added a TODO.'
+ value 'marked', value: 4, description: 'User added a to-do item.'
value 'approval_required', value: 5, description: 'User was set as an approver.'
value 'unmergeable', value: 6, description: 'Merge request authored by the user could not be merged.'
value 'directly_addressed', value: 7, description: 'User was directly addressed.'
value 'merge_train_removed', value: 8, description: 'Merge request authored by the user was removed from the merge train.'
value 'review_requested', value: 9, description: 'Review was requested from the user.'
+ value 'member_access_requested', value: 10, description: 'Group access requested from the user.'
end
end
diff --git a/app/graphql/types/todo_type.rb b/app/graphql/types/todo_type.rb
index 0de6b1d6f8a..6e5ce35033b 100644
--- a/app/graphql/types/todo_type.rb
+++ b/app/graphql/types/todo_type.rb
@@ -15,13 +15,11 @@ module Types
field :project, Types::ProjectType,
description: 'Project this to-do item is associated with.',
- null: true,
- authorize: :read_project
+ null: true
field :group, 'Types::GroupType',
description: 'Group this to-do item is associated with.',
- null: true,
- authorize: :read_group
+ null: true
field :author, Types::UserType,
description: 'Author of this to-do item.',
diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb
index f49b3eee4f5..51046d09f90 100644
--- a/app/graphql/types/user_interface.rb
+++ b/app/graphql/types/user_interface.rb
@@ -88,7 +88,10 @@ module Types
null: true,
description: 'Personal namespace of the user.'
- field :todos, resolver: Resolvers::TodosResolver, description: 'To-do items of the user.'
+ field :todos,
+ Types::TodoType.connection_type,
+ description: 'To-do items of the user.',
+ resolver: Resolvers::TodosResolver
# Merge request field: MRs can be authored, assigned, or assigned-for-review:
field :authored_merge_requests,
diff --git a/app/graphql/types/work_items/notes_filter_type_enum.rb b/app/graphql/types/work_items/notes_filter_type_enum.rb
new file mode 100644
index 00000000000..93fb4689f0b
--- /dev/null
+++ b/app/graphql/types/work_items/notes_filter_type_enum.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ class NotesFilterTypeEnum < BaseEnum
+ graphql_name 'NotesFilterType'
+ description 'Work item notes collection type.'
+
+ ::UserPreference::NOTES_FILTERS.each_pair do |key, value|
+ value key.upcase,
+ value: value,
+ description: UserPreference.notes_filters.invert[::UserPreference::NOTES_FILTERS[key]]
+ end
+
+ def self.default_value
+ ::UserPreference::NOTES_FILTERS[:all_notes]
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/widget_interface.rb b/app/graphql/types/work_items/widget_interface.rb
index b85d0a23535..672a78f12e1 100644
--- a/app/graphql/types/work_items/widget_interface.rb
+++ b/app/graphql/types/work_items/widget_interface.rb
@@ -17,7 +17,8 @@ module Types
::Types::WorkItems::Widgets::LabelsType,
::Types::WorkItems::Widgets::AssigneesType,
::Types::WorkItems::Widgets::StartAndDueDateType,
- ::Types::WorkItems::Widgets::MilestoneType
+ ::Types::WorkItems::Widgets::MilestoneType,
+ ::Types::WorkItems::Widgets::NotesType
].freeze
def self.ce_orphan_types
@@ -41,6 +42,8 @@ module Types
::Types::WorkItems::Widgets::StartAndDueDateType
when ::WorkItems::Widgets::Milestone
::Types::WorkItems::Widgets::MilestoneType
+ when ::WorkItems::Widgets::Notes
+ ::Types::WorkItems::Widgets::NotesType
else
raise "Unknown GraphQL type for widget #{object}"
end
diff --git a/app/graphql/types/work_items/widgets/hierarchy_type.rb b/app/graphql/types/work_items/widgets/hierarchy_type.rb
index 0ccd8af7dc8..4ec8ec84779 100644
--- a/app/graphql/types/work_items/widgets/hierarchy_type.rb
+++ b/app/graphql/types/work_items/widgets/hierarchy_type.rb
@@ -20,8 +20,29 @@ module Types
null: true, complexity: 5,
description: 'Child work items.'
+ field :has_children, GraphQL::Types::Boolean,
+ null: false, description: 'Indicates if the work item has children.'
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def has_children?
+ BatchLoader::GraphQL.for(object.work_item.id).batch(default_value: false) do |ids, loader|
+ links_for_parents = ::WorkItems::ParentLink.for_parents(ids)
+ .select(:work_item_parent_id)
+ .group(:work_item_parent_id)
+ .reorder(nil)
+
+ links_for_parents.each { |link| loader.call(link.work_item_parent_id, true) }
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ alias_method :has_children, :has_children?
+
def children
- object.children.inc_relations_for_permission_check
+ relation = object.children
+ relation = relation.inc_relations_for_permission_check unless object.children.loaded?
+
+ relation
end
end
# rubocop:enable Graphql/AuthorizeTypes
diff --git a/app/graphql/types/work_items/widgets/notes_type.rb b/app/graphql/types/work_items/widgets/notes_type.rb
new file mode 100644
index 00000000000..7da2777beee
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/notes_type.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ module Widgets
+ # Disabling widget level authorization as it might be too granular
+ # and we already authorize the parent work item
+ # rubocop:disable Graphql/AuthorizeTypes
+ class NotesType < BaseObject
+ graphql_name 'WorkItemWidgetNotes'
+ description 'Represents a notes widget'
+
+ implements Types::WorkItems::WidgetInterface
+
+ # This field loads user comments, system notes and resource events as a discussion for an work item,
+ # raising the complexity considerably. In order to discourage fetching this field as part of fetching
+ # a list of issues we raise the complexity
+ field :discussions, Types::Notes::DiscussionType.connection_type,
+ null: true,
+ description: "Notes on this work item.",
+ resolver: Resolvers::WorkItems::WorkItemDiscussionsResolver
+ end
+ # rubocop:enable Graphql/AuthorizeTypes
+ end
+ end
+end