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:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-17 19:05:49 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-17 19:05:49 +0300
commit43a25d93ebdabea52f99b05e15b06250cd8f07d7 (patch)
treedceebdc68925362117480a5d672bcff122fb625b /app/graphql
parent20c84b99005abd1c82101dfeff264ac50d2df211 (diff)
Add latest changes from gitlab-org/gitlab@16-0-stable-eev16.0.0-rc42
Diffstat (limited to 'app/graphql')
-rw-r--r--app/graphql/batch_loaders/award_emoji_votes_batch_loader.rb22
-rw-r--r--app/graphql/gitlab_schema.rb2
-rw-r--r--app/graphql/graphql_triggers.rb28
-rw-r--r--app/graphql/mutations/achievements/award.rb38
-rw-r--r--app/graphql/mutations/achievements/delete.rb33
-rw-r--r--app/graphql/mutations/achievements/revoke.rb33
-rw-r--r--app/graphql/mutations/achievements/update.rb46
-rw-r--r--app/graphql/mutations/alert_management/base.rb2
-rw-r--r--app/graphql/mutations/award_emojis/base.rb2
-rw-r--r--app/graphql/mutations/base_mutation.rb6
-rw-r--r--app/graphql/mutations/boards/update.rb6
-rw-r--r--app/graphql/mutations/ci/ci_cd_settings_update.rb9
-rw-r--r--app/graphql/mutations/ci/job_artifact/bulk_destroy.rb69
-rw-r--r--app/graphql/mutations/ci/job_token_scope/add_project.rb16
-rw-r--r--app/graphql/mutations/ci/pipeline_schedule/take_ownership.rb2
-rw-r--r--app/graphql/mutations/ci/project_ci_cd_settings_update.rb12
-rw-r--r--app/graphql/mutations/ci/runner/common_mutation_arguments.rb45
-rw-r--r--app/graphql/mutations/ci/runner/create.rb107
-rw-r--r--app/graphql/mutations/ci/runner/delete.rb6
-rw-r--r--app/graphql/mutations/ci/runner/update.rb42
-rw-r--r--app/graphql/mutations/clusters/agent_tokens/create.rb10
-rw-r--r--app/graphql/mutations/clusters/agent_tokens/revoke.rb9
-rw-r--r--app/graphql/mutations/clusters/agents/delete.rb6
-rw-r--r--app/graphql/mutations/concerns/mutations/finds_by_gid.rb9
-rw-r--r--app/graphql/mutations/concerns/mutations/finds_namespace.rb11
-rw-r--r--app/graphql/mutations/concerns/mutations/spam_protection.rb2
-rw-r--r--app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb12
-rw-r--r--app/graphql/mutations/container_repositories/destroy_base.rb6
-rw-r--r--app/graphql/mutations/design_management/update.rb33
-rw-r--r--app/graphql/mutations/discussions/toggle_resolve.rb4
-rw-r--r--app/graphql/mutations/environments/canary_ingress/update.rb4
-rw-r--r--app/graphql/mutations/environments/stop.rb39
-rw-r--r--app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb2
-rw-r--r--app/graphql/mutations/issues/bulk_update.rb23
-rw-r--r--app/graphql/mutations/members/bulk_update_base.rb88
-rw-r--r--app/graphql/mutations/members/groups/bulk_update.rb77
-rw-r--r--app/graphql/mutations/members/projects/bulk_update.rb29
-rw-r--r--app/graphql/mutations/metrics/dashboard/annotations/create.rb8
-rw-r--r--app/graphql/mutations/metrics/dashboard/annotations/delete.rb4
-rw-r--r--app/graphql/mutations/notes/base.rb6
-rw-r--r--app/graphql/mutations/notes/create/base.rb4
-rw-r--r--app/graphql/mutations/packages/destroy.rb6
-rw-r--r--app/graphql/mutations/packages/destroy_file.rb6
-rw-r--r--app/graphql/mutations/projects/sync_fork.rb70
-rw-r--r--app/graphql/mutations/release_asset_links/create.rb14
-rw-r--r--app/graphql/mutations/release_asset_links/delete.rb18
-rw-r--r--app/graphql/mutations/release_asset_links/update.rb18
-rw-r--r--app/graphql/mutations/terraform/state/base.rb6
-rw-r--r--app/graphql/mutations/todos/base.rb13
-rw-r--r--app/graphql/mutations/todos/create.rb10
-rw-r--r--app/graphql/mutations/todos/mark_all_done.rb2
-rw-r--r--app/graphql/mutations/todos/mark_done.rb2
-rw-r--r--app/graphql/mutations/todos/restore.rb2
-rw-r--r--app/graphql/mutations/todos/restore_many.rb2
-rw-r--r--app/graphql/mutations/user_preferences/update.rb3
-rw-r--r--app/graphql/mutations/work_items/convert.rb70
-rw-r--r--app/graphql/mutations/work_items/create.rb30
-rw-r--r--app/graphql/mutations/work_items/create_from_task.rb6
-rw-r--r--app/graphql/mutations/work_items/delete.rb6
-rw-r--r--app/graphql/mutations/work_items/delete_task.rb5
-rw-r--r--app/graphql/mutations/work_items/export.rb54
-rw-r--r--app/graphql/mutations/work_items/update.rb25
-rw-r--r--app/graphql/queries/repository/path_last_commit.query.graphql25
-rw-r--r--app/graphql/resolvers/achievements/achievements_resolver.rb35
-rw-r--r--app/graphql/resolvers/achievements/user_achievements_resolver.rb33
-rw-r--r--app/graphql/resolvers/analytics/cycle_analytics/base_count_resolver.rb38
-rw-r--r--app/graphql/resolvers/analytics/cycle_analytics/base_issue_resolver.rb50
-rw-r--r--app/graphql/resolvers/analytics/cycle_analytics/deployment_count_resolver.rb54
-rw-r--r--app/graphql/resolvers/analytics/cycle_analytics/issue_count_resolver.rb37
-rw-r--r--app/graphql/resolvers/award_emoji/base_votes_count_resolver.rb21
-rw-r--r--app/graphql/resolvers/blobs_resolver.rb2
-rw-r--r--app/graphql/resolvers/ci/all_jobs_resolver.rb27
-rw-r--r--app/graphql/resolvers/ci/inherited_variables_resolver.rb13
-rw-r--r--app/graphql/resolvers/ci/jobs_resolver.rb7
-rw-r--r--app/graphql/resolvers/ci/pipeline_job_artifacts_resolver.rb2
-rw-r--r--app/graphql/resolvers/ci/runner_jobs_resolver.rb6
-rw-r--r--app/graphql/resolvers/ci/runner_projects_resolver.rb27
-rw-r--r--app/graphql/resolvers/ci/runner_resolver.rb8
-rw-r--r--app/graphql/resolvers/ci/runner_status_resolver.rb11
-rw-r--r--app/graphql/resolvers/ci/runners_resolver.rb29
-rw-r--r--app/graphql/resolvers/clusters/agent_tokens_resolver.rb8
-rw-r--r--app/graphql/resolvers/clusters/agents/authorizations/ci_access_resolver.rb19
-rw-r--r--app/graphql/resolvers/clusters/agents/authorizations/user_access_resolver.rb19
-rw-r--r--app/graphql/resolvers/clusters/agents_resolver.rb2
-rw-r--r--app/graphql/resolvers/concerns/resolves_merge_requests.rb1
-rw-r--r--app/graphql/resolvers/concerns/time_frame_arguments.rb42
-rw-r--r--app/graphql/resolvers/concerns/work_items/shared_filter_arguments.rb28
-rw-r--r--app/graphql/resolvers/data_transfer/data_transfer_arguments.rb19
-rw-r--r--app/graphql/resolvers/data_transfer/group_data_transfer_resolver.rb34
-rw-r--r--app/graphql/resolvers/data_transfer/project_data_transfer_resolver.rb34
-rw-r--r--app/graphql/resolvers/data_transfer_resolver.rb57
-rw-r--r--app/graphql/resolvers/design_management/version_resolver.rb4
-rw-r--r--app/graphql/resolvers/down_votes_count_resolver.rb7
-rw-r--r--app/graphql/resolvers/group_labels_resolver.rb4
-rw-r--r--app/graphql/resolvers/issues_resolver.rb18
-rw-r--r--app/graphql/resolvers/kas/agent_configurations_resolver.rb2
-rw-r--r--app/graphql/resolvers/labels_resolver.rb9
-rw-r--r--app/graphql/resolvers/merge_requests_resolver.rb7
-rw-r--r--app/graphql/resolvers/metrics/dashboard_resolver.rb1
-rw-r--r--app/graphql/resolvers/metrics/dashboards/annotation_resolver.rb1
-rw-r--r--app/graphql/resolvers/milestones_resolver.rb2
-rw-r--r--app/graphql/resolvers/notes/synthetic_note_resolver.rb4
-rw-r--r--app/graphql/resolvers/paginated_tree_resolver.rb2
-rw-r--r--app/graphql/resolvers/project_merge_requests_resolver.rb8
-rw-r--r--app/graphql/resolvers/projects/commit_references_resolver.rb29
-rw-r--r--app/graphql/resolvers/projects/fork_details_resolver.rb9
-rw-r--r--app/graphql/resolvers/timelog_resolver.rb2
-rw-r--r--app/graphql/resolvers/up_votes_count_resolver.rb7
-rw-r--r--app/graphql/resolvers/user_resolver.rb2
-rw-r--r--app/graphql/resolvers/work_item_resolver.rb6
-rw-r--r--app/graphql/resolvers/work_items_resolver.rb39
-rw-r--r--app/graphql/subscriptions/base_subscription.rb12
-rw-r--r--app/graphql/subscriptions/issuable_updated.rb4
-rw-r--r--app/graphql/subscriptions/notes/base.rb4
-rw-r--r--app/graphql/types/achievements/achievement_type.rb9
-rw-r--r--app/graphql/types/achievements/user_achievement_type.rb51
-rw-r--r--app/graphql/types/alert_management/alert_type.rb10
-rw-r--r--app/graphql/types/analytics/cycle_analytics/flow_metrics.rb30
-rw-r--r--app/graphql/types/analytics/cycle_analytics/link_type.rb33
-rw-r--r--app/graphql/types/analytics/cycle_analytics/metric_type.rb39
-rw-r--r--app/graphql/types/board_list_type.rb2
-rw-r--r--app/graphql/types/branch_protections/base_access_level_type.rb2
-rw-r--r--app/graphql/types/ci/catalog/resource_type.rb27
-rw-r--r--app/graphql/types/ci/ci_cd_setting_type.rb6
-rw-r--r--app/graphql/types/ci/config/include_type_enum.rb1
-rw-r--r--app/graphql/types/ci/inherited_ci_variable_type.rb48
-rw-r--r--app/graphql/types/ci/job_trace_type.rb19
-rw-r--r--app/graphql/types/ci/job_type.rb60
-rw-r--r--app/graphql/types/ci/runner_manager_type.rb49
-rw-r--r--app/graphql/types/ci/runner_type.rb44
-rw-r--r--app/graphql/types/clusters/agent_activity_event_type.rb2
-rw-r--r--app/graphql/types/clusters/agent_token_type.rb2
-rw-r--r--app/graphql/types/clusters/agent_type.rb2
-rw-r--r--app/graphql/types/clusters/agents/authorizations/ci_access_type.rb21
-rw-r--r--app/graphql/types/clusters/agents/authorizations/user_access_type.rb21
-rw-r--r--app/graphql/types/commit_references_type.rb67
-rw-r--r--app/graphql/types/commit_signatures/ssh_signature_type.rb17
-rw-r--r--app/graphql/types/data_transfer/base_type.rb2
-rw-r--r--app/graphql/types/data_transfer/egress_node_type.rb6
-rw-r--r--app/graphql/types/data_transfer/project_data_transfer_type.rb10
-rw-r--r--app/graphql/types/design_management/design_type.rb7
-rw-r--r--app/graphql/types/environment_type.rb3
-rw-r--r--app/graphql/types/group_type.rb13
-rw-r--r--app/graphql/types/issuable_subscription_event_enum.rb11
-rw-r--r--app/graphql/types/issue_type.rb9
-rw-r--r--app/graphql/types/merge_request_type.rb14
-rw-r--r--app/graphql/types/merge_requests/detailed_merge_status_enum.rb3
-rw-r--r--app/graphql/types/mutation_type.rb24
-rw-r--r--app/graphql/types/namespace_type.rb8
-rw-r--r--app/graphql/types/packages/package_base_type.rb2
-rw-r--r--app/graphql/types/packages/package_details_type.rb4
-rw-r--r--app/graphql/types/packages/package_file_type.rb2
-rw-r--r--app/graphql/types/permission_types/ci/pipeline_schedules.rb9
-rw-r--r--app/graphql/types/permission_types/group_enum.rb3
-rw-r--r--app/graphql/types/permission_types/issue.rb2
-rw-r--r--app/graphql/types/permission_types/work_item.rb3
-rw-r--r--app/graphql/types/project_statistics_redirect_type.rb27
-rw-r--r--app/graphql/types/project_type.rb81
-rw-r--r--app/graphql/types/projects/commit_parent_names_type.rb13
-rw-r--r--app/graphql/types/projects/fork_details_type.rb26
-rw-r--r--app/graphql/types/projects/namespace_project_sort_enum.rb5
-rw-r--r--app/graphql/types/query_type.rb16
-rw-r--r--app/graphql/types/relative_position_type_enum.rb11
-rw-r--r--app/graphql/types/release_asset_link_type.rb6
-rw-r--r--app/graphql/types/repository_type.rb2
-rw-r--r--app/graphql/types/root_storage_statistics_type.rb1
-rw-r--r--app/graphql/types/time_tracking/timelog_connection_type.rb2
-rw-r--r--app/graphql/types/timelog_type.rb4
-rw-r--r--app/graphql/types/user_interface.rb13
-rw-r--r--app/graphql/types/user_preferences_type.rb4
-rw-r--r--app/graphql/types/visibility_pipeline_id_type_enum.rb12
-rw-r--r--app/graphql/types/work_item_type.rb21
-rw-r--r--app/graphql/types/work_items/available_export_fields_enum.rb18
-rw-r--r--app/graphql/types/work_items/award_emoji_update_action_enum.rb13
-rw-r--r--app/graphql/types/work_items/todo_update_action_enum.rb13
-rw-r--r--app/graphql/types/work_items/widget_interface.rb11
-rw-r--r--app/graphql/types/work_items/widgets/award_emoji_type.rb41
-rw-r--r--app/graphql/types/work_items/widgets/award_emoji_update_input_type.rb20
-rw-r--r--app/graphql/types/work_items/widgets/current_user_todos_input_type.rb21
-rw-r--r--app/graphql/types/work_items/widgets/current_user_todos_type.rb26
-rw-r--r--app/graphql/types/work_items/widgets/hierarchy_update_input_type.rb15
-rw-r--r--app/graphql/types/work_items/widgets/notifications_type.rb26
-rw-r--r--app/graphql/types/work_items/widgets/notifications_update_input_type.rb16
183 files changed, 2691 insertions, 621 deletions
diff --git a/app/graphql/batch_loaders/award_emoji_votes_batch_loader.rb b/app/graphql/batch_loaders/award_emoji_votes_batch_loader.rb
index c1b35d3eaf7..4ce9baff8cb 100644
--- a/app/graphql/batch_loaders/award_emoji_votes_batch_loader.rb
+++ b/app/graphql/batch_loaders/award_emoji_votes_batch_loader.rb
@@ -1,21 +1,25 @@
# frozen_string_literal: true
module BatchLoaders
- module AwardEmojiVotesBatchLoader
- private
+ class AwardEmojiVotesBatchLoader
+ def self.load_upvotes(object, awardable_class: nil)
+ load_votes_for(object, AwardEmoji::UPVOTE_NAME, awardable_class: awardable_class)
+ end
+
+ def self.load_downvotes(object, awardable_class: nil)
+ load_votes_for(object, AwardEmoji::DOWNVOTE_NAME, awardable_class: awardable_class)
+ end
- def load_votes(object, vote_type)
- BatchLoader::GraphQL.for(object.id).batch(key: "#{object.issuing_parent_id}-#{vote_type}") do |ids, loader, args|
- counts = AwardEmoji.votes_for_collection(ids, object.class.name).named(vote_type).index_by(&:awardable_id)
+ def self.load_votes_for(object, vote_type, awardable_class: nil)
+ awardable_class ||= object.class.name
+
+ BatchLoader::GraphQL.for(object.id).batch(key: "#{object.issuing_parent_id}-#{vote_type}") do |ids, loader, _args|
+ counts = AwardEmoji.votes_for_collection(ids, awardable_class).named(vote_type).index_by(&:awardable_id)
ids.each do |id|
loader.call(id, counts[id]&.count || 0)
end
end
end
-
- def authorized_resource?(object)
- Ability.allowed?(current_user, "read_#{object.to_ability_name}".to_sym, object)
- end
end
end
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index 37adf4c2d3b..eed7959a2f1 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -20,7 +20,7 @@ class GitlabSchema < GraphQL::Schema
use Gitlab::Graphql::GenericTracing
use Gitlab::Graphql::Tracers::TimerTracer
- use GraphQL::Subscriptions::ActionCableSubscriptions
+ use Gitlab::Graphql::Subscriptions::ActionCableWithLoadBalancing
use BatchLoader::GraphQL
use Gitlab::Graphql::Pagination::Connections
use Gitlab::Graphql::Timeout, max_seconds: Gitlab.config.gitlab.graphql_timeout
diff --git a/app/graphql/graphql_triggers.rb b/app/graphql/graphql_triggers.rb
index 89656f1e018..d1798d2ade7 100644
--- a/app/graphql/graphql_triggers.rb
+++ b/app/graphql/graphql_triggers.rb
@@ -2,60 +2,62 @@
module GraphqlTriggers
def self.issuable_assignees_updated(issuable)
- GitlabSchema.subscriptions.trigger('issuableAssigneesUpdated', { issuable_id: issuable.to_gid }, issuable)
+ GitlabSchema.subscriptions.trigger(:issuable_assignees_updated, { issuable_id: issuable.to_gid }, issuable)
end
def self.issue_crm_contacts_updated(issue)
- GitlabSchema.subscriptions.trigger('issueCrmContactsUpdated', { issuable_id: issue.to_gid }, issue)
+ GitlabSchema.subscriptions.trigger(:issue_crm_contacts_updated, { issuable_id: issue.to_gid }, issue)
end
def self.issuable_title_updated(issuable)
- GitlabSchema.subscriptions.trigger('issuableTitleUpdated', { issuable_id: issuable.to_gid }, issuable)
+ GitlabSchema.subscriptions.trigger(:issuable_title_updated, { issuable_id: issuable.to_gid }, issuable)
end
def self.issuable_description_updated(issuable)
- GitlabSchema.subscriptions.trigger('issuableDescriptionUpdated', { issuable_id: issuable.to_gid }, issuable)
+ GitlabSchema.subscriptions.trigger(:issuable_description_updated, { issuable_id: issuable.to_gid }, issuable)
end
def self.issuable_labels_updated(issuable)
- GitlabSchema.subscriptions.trigger('issuableLabelsUpdated', { issuable_id: issuable.to_gid }, issuable)
+ GitlabSchema.subscriptions.trigger(:issuable_labels_updated, { issuable_id: issuable.to_gid }, issuable)
end
def self.issuable_dates_updated(issuable)
- GitlabSchema.subscriptions.trigger('issuableDatesUpdated', { issuable_id: issuable.to_gid }, issuable)
+ GitlabSchema.subscriptions.trigger(:issuable_dates_updated, { issuable_id: issuable.to_gid }, issuable)
end
def self.issuable_milestone_updated(issuable)
- GitlabSchema.subscriptions.trigger('issuableMilestoneUpdated', { issuable_id: issuable.to_gid }, issuable)
+ GitlabSchema.subscriptions.trigger(:issuable_milestone_updated, { issuable_id: issuable.to_gid }, issuable)
end
def self.work_item_note_created(work_item_gid, note_data)
- GitlabSchema.subscriptions.trigger('workItemNoteCreated', { noteable_id: work_item_gid }, note_data)
+ GitlabSchema.subscriptions.trigger(:work_item_note_created, { noteable_id: work_item_gid }, note_data)
end
def self.work_item_note_deleted(work_item_gid, note_data)
- GitlabSchema.subscriptions.trigger('workItemNoteDeleted', { noteable_id: work_item_gid }, note_data)
+ GitlabSchema.subscriptions.trigger(:work_item_note_deleted, { noteable_id: work_item_gid }, note_data)
end
def self.work_item_note_updated(work_item_gid, note_data)
- GitlabSchema.subscriptions.trigger('workItemNoteUpdated', { noteable_id: work_item_gid }, note_data)
+ GitlabSchema.subscriptions.trigger(:work_item_note_updated, { noteable_id: work_item_gid }, note_data)
end
def self.merge_request_reviewers_updated(merge_request)
GitlabSchema.subscriptions.trigger(
- 'mergeRequestReviewersUpdated', { issuable_id: merge_request.to_gid }, merge_request
+ :merge_request_reviewers_updated, { issuable_id: merge_request.to_gid }, merge_request
)
end
def self.merge_request_merge_status_updated(merge_request)
+ return unless Feature.enabled?(:realtime_mr_status_change, merge_request.project)
+
GitlabSchema.subscriptions.trigger(
- 'mergeRequestMergeStatusUpdated', { issuable_id: merge_request.to_gid }, merge_request
+ :merge_request_merge_status_updated, { issuable_id: merge_request.to_gid }, merge_request
)
end
def self.merge_request_approval_state_updated(merge_request)
GitlabSchema.subscriptions.trigger(
- 'mergeRequestApprovalStateUpdated', { issuable_id: merge_request.to_gid }, merge_request
+ :merge_request_approval_state_updated, { issuable_id: merge_request.to_gid }, merge_request
)
end
end
diff --git a/app/graphql/mutations/achievements/award.rb b/app/graphql/mutations/achievements/award.rb
new file mode 100644
index 00000000000..b486049594d
--- /dev/null
+++ b/app/graphql/mutations/achievements/award.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Achievements
+ class Award < BaseMutation
+ graphql_name 'AchievementsAward'
+
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ field :user_achievement,
+ ::Types::Achievements::UserAchievementType,
+ null: true,
+ description: 'Achievement award.'
+
+ argument :achievement_id, ::Types::GlobalIDType[::Achievements::Achievement],
+ required: true,
+ description: 'Global ID of the achievement being awarded.'
+
+ argument :user_id, ::Types::GlobalIDType[::User],
+ required: true,
+ description: 'Global ID of the user being awarded the achievement.'
+
+ authorize :award_achievement
+
+ def resolve(args)
+ achievement = authorized_find!(id: args[:achievement_id])
+
+ recipient_id = args[:user_id].model_id
+ result = ::Achievements::AwardService.new(current_user, achievement.id, recipient_id).execute
+ { user_achievement: result.payload, errors: result.errors }
+ end
+
+ def find_object(id:)
+ GitlabSchema.object_from_id(id, expected_type: ::Achievements::Achievement)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/achievements/delete.rb b/app/graphql/mutations/achievements/delete.rb
new file mode 100644
index 00000000000..0b510b44b4e
--- /dev/null
+++ b/app/graphql/mutations/achievements/delete.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Achievements
+ class Delete < BaseMutation
+ graphql_name 'AchievementsDelete'
+
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ field :achievement,
+ ::Types::Achievements::AchievementType,
+ null: true,
+ description: 'Achievement.'
+
+ argument :achievement_id, ::Types::GlobalIDType[::Achievements::Achievement],
+ required: true,
+ description: 'Global ID of the achievement being deleted.'
+
+ authorize :admin_achievement
+
+ def resolve(args)
+ achievement = authorized_find!(id: args[:achievement_id])
+
+ result = ::Achievements::DestroyService.new(current_user, achievement).execute
+ { achievement: result.payload, errors: result.errors }
+ end
+
+ def find_object(id:)
+ GitlabSchema.object_from_id(id, expected_type: ::Achievements::Achievement)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/achievements/revoke.rb b/app/graphql/mutations/achievements/revoke.rb
new file mode 100644
index 00000000000..9d21b1c3741
--- /dev/null
+++ b/app/graphql/mutations/achievements/revoke.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Achievements
+ class Revoke < BaseMutation
+ graphql_name 'AchievementsRevoke'
+
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ field :user_achievement,
+ ::Types::Achievements::UserAchievementType,
+ null: true,
+ description: 'Achievement award.'
+
+ argument :user_achievement_id, ::Types::GlobalIDType[::Achievements::UserAchievement],
+ required: true,
+ description: 'Global ID of the user achievement being revoked.'
+
+ authorize :award_achievement
+
+ def resolve(args)
+ user_achievement = authorized_find!(id: args[:user_achievement_id])
+
+ result = ::Achievements::RevokeService.new(current_user, user_achievement).execute
+ { user_achievement: result.payload, errors: result.errors }
+ end
+
+ def find_object(id:)
+ GitlabSchema.object_from_id(id, expected_type: ::Achievements::UserAchievement)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/achievements/update.rb b/app/graphql/mutations/achievements/update.rb
new file mode 100644
index 00000000000..2a9e6580629
--- /dev/null
+++ b/app/graphql/mutations/achievements/update.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Achievements
+ class Update < BaseMutation
+ graphql_name 'AchievementsUpdate'
+
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ field :achievement,
+ ::Types::Achievements::AchievementType,
+ null: true,
+ description: 'Achievement.'
+
+ argument :achievement_id, ::Types::GlobalIDType[::Achievements::Achievement],
+ required: true,
+ description: 'Global ID of the achievement being updated.'
+
+ argument :name, GraphQL::Types::String,
+ required: false,
+ description: 'Name for the achievement.'
+
+ argument :avatar, ApolloUploadServer::Upload,
+ required: false,
+ description: 'Avatar for the achievement.'
+
+ argument :description, GraphQL::Types::String,
+ required: false,
+ description: 'Description of or notes for the achievement.'
+
+ authorize :admin_achievement
+
+ def resolve(args)
+ achievement = authorized_find!(id: args[:achievement_id])
+
+ args.delete(:achievement_id)
+ result = ::Achievements::UpdateService.new(current_user, achievement, args).execute
+ { achievement: result.payload, errors: result.errors }
+ end
+
+ def find_object(id:)
+ GitlabSchema.object_from_id(id, expected_type: ::Achievements::Achievement)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/alert_management/base.rb b/app/graphql/mutations/alert_management/base.rb
index 2eef6bb9db7..771ace5510f 100644
--- a/app/graphql/mutations/alert_management/base.rb
+++ b/app/graphql/mutations/alert_management/base.rb
@@ -45,8 +45,6 @@ module Mutations
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,
diff --git a/app/graphql/mutations/award_emojis/base.rb b/app/graphql/mutations/award_emojis/base.rb
index dc2d46269e6..65065de0de4 100644
--- a/app/graphql/mutations/award_emojis/base.rb
+++ b/app/graphql/mutations/award_emojis/base.rb
@@ -3,8 +3,6 @@
module Mutations
module AwardEmojis
class Base < BaseMutation
- include ::Mutations::FindsByGid
-
NOT_EMOJI_AWARDABLE = 'You cannot award emoji to this resource.'
authorize :award_emoji
diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb
index 5f98b222099..994668b5f8f 100644
--- a/app/graphql/mutations/base_mutation.rb
+++ b/app/graphql/mutations/base_mutation.rb
@@ -28,7 +28,7 @@ module Mutations
end
def ready?(**args)
- raise_resource_not_available_error! ERROR_MESSAGE if Gitlab::Database.read_only?
+ raise_resource_not_available_error!(ERROR_MESSAGE) if read_only?
missing_args = self.class.arguments.values
.reject { |arg| arg.accepts?(args.fetch(arg.keyword, :not_given)) }
@@ -39,6 +39,10 @@ module Mutations
true
end
+ def read_only?
+ Gitlab::Database.read_only?
+ end
+
def load_application_object(argument, id, context)
::Gitlab::Graphql::Lazy.new { super }
end
diff --git a/app/graphql/mutations/boards/update.rb b/app/graphql/mutations/boards/update.rb
index 7cfce9d2d91..f611608d1b6 100644
--- a/app/graphql/mutations/boards/update.rb
+++ b/app/graphql/mutations/boards/update.rb
@@ -29,12 +29,6 @@ module Mutations
errors: errors_on_object(board)
}
end
-
- private
-
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
end
end
end
diff --git a/app/graphql/mutations/ci/ci_cd_settings_update.rb b/app/graphql/mutations/ci/ci_cd_settings_update.rb
index 98b8e9567e7..a7d99d2a496 100644
--- a/app/graphql/mutations/ci/ci_cd_settings_update.rb
+++ b/app/graphql/mutations/ci/ci_cd_settings_update.rb
@@ -2,9 +2,16 @@
module Mutations
module Ci
- # TODO: Remove in 16.0, see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87002
+ # TODO: Remove after 16.0, see https://gitlab.com/gitlab-org/gitlab/-/issues/361801#note_1373963840
class CiCdSettingsUpdate < ProjectCiCdSettingsUpdate
graphql_name 'CiCdSettingsUpdate'
+
+ def ready?(**args)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable, '`remove_cicd_settings_update` feature flag is enabled.' \
+ if Feature.enabled?(:remove_cicd_settings_update)
+
+ super
+ end
end
end
end
diff --git a/app/graphql/mutations/ci/job_artifact/bulk_destroy.rb b/app/graphql/mutations/ci/job_artifact/bulk_destroy.rb
new file mode 100644
index 00000000000..53036496de4
--- /dev/null
+++ b/app/graphql/mutations/ci/job_artifact/bulk_destroy.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Ci
+ module JobArtifact
+ class BulkDestroy < BaseMutation
+ graphql_name 'BulkDestroyJobArtifacts'
+
+ authorize :destroy_artifacts
+
+ ArtifactId = ::Types::GlobalIDType[::Ci::JobArtifact]
+ ProjectId = ::Types::GlobalIDType[::Project]
+
+ argument :ids, [ArtifactId],
+ required: true,
+ description: 'Global IDs of the job artifacts to destroy.',
+ prepare: ->(global_ids, _ctx) { GitlabSchema.parse_gids(global_ids, expected_type: ::Ci::JobArtifact) }
+
+ argument :project_id, ProjectId,
+ required: true,
+ description: 'Global Project ID of the job artifacts to destroy. Incompatible with projectPath.'
+
+ field :destroyed_count, ::GraphQL::Types::Int,
+ null: true,
+ description: 'Number of job artifacts deleted.'
+
+ field :destroyed_ids, [ArtifactId],
+ null: true,
+ description: 'IDs of job artifacts that were deleted.'
+
+ def find_object(id:)
+ GlobalID::Locator.locate(id)
+ end
+
+ def resolve(**args)
+ ids = args[:ids]
+ project_id = args[:project_id]
+
+ project = authorized_find!(id: project_id)
+
+ if Feature.disabled?(:ci_job_artifact_bulk_destroy, project)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ '`ci_job_artifact_bulk_destroy` feature flag is disabled.'
+ end
+
+ raise Gitlab::Graphql::Errors::ArgumentError, 'IDs array of job artifacts can not be empty' if ids.empty?
+
+ result = ::Ci::JobArtifacts::BulkDeleteByProjectService.new(
+ job_artifact_ids: model_ids_of(ids),
+ current_user: current_user,
+ project: project
+ ).execute
+
+ if result.success?
+ result.payload.slice(:destroyed_count, :destroyed_ids).merge(errors: [])
+ else
+ { errors: result.errors }
+ end
+ end
+
+ private
+
+ def model_ids_of(global_ids)
+ global_ids.filter_map { |gid| gid.model_id.to_i }
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/ci/job_token_scope/add_project.rb b/app/graphql/mutations/ci/job_token_scope/add_project.rb
index 6f0f87b47a1..6071d6750c2 100644
--- a/app/graphql/mutations/ci/job_token_scope/add_project.rb
+++ b/app/graphql/mutations/ci/job_token_scope/add_project.rb
@@ -21,16 +21,28 @@ module Mutations
argument :direction,
::Types::Ci::JobTokenScope::DirectionEnum,
required: false,
- description: 'Direction of access, which defaults to outbound.'
+ deprecated: {
+ reason: 'Outbound job token scope is being removed. This field can now only be set to INBOUND',
+ milestone: '16.0'
+ },
+ description: 'Direction of access, which defaults to INBOUND.'
field :ci_job_token_scope,
Types::Ci::JobTokenScopeType,
null: true,
description: "CI job token's access scope."
- def resolve(project_path:, target_project_path:, direction: :outbound)
+ def resolve(project_path:, target_project_path:, direction: nil)
project = authorized_find!(project_path)
target_project = Project.find_by_full_path(target_project_path)
+ frozen_outbound = project.frozen_outbound_job_token_scopes?
+
+ if direction == :outbound && frozen_outbound
+ raise Gitlab::Graphql::Errors::ArgumentError, 'direction: OUTBOUND scope entries can only be removed. ' \
+ 'Only INBOUND scope can be expanded.'
+ end
+
+ direction ||= frozen_outbound ? :inbound : :outbound
result = ::Ci::JobTokenScope::AddProjectService
.new(project, current_user)
diff --git a/app/graphql/mutations/ci/pipeline_schedule/take_ownership.rb b/app/graphql/mutations/ci/pipeline_schedule/take_ownership.rb
index 2e4312f0045..d71ef738cab 100644
--- a/app/graphql/mutations/ci/pipeline_schedule/take_ownership.rb
+++ b/app/graphql/mutations/ci/pipeline_schedule/take_ownership.rb
@@ -6,7 +6,7 @@ module Mutations
class TakeOwnership < Base
graphql_name 'PipelineScheduleTakeOwnership'
- authorize :take_ownership_pipeline_schedule
+ authorize :admin_pipeline_schedule
field :pipeline_schedule,
Types::Ci::PipelineScheduleType,
diff --git a/app/graphql/mutations/ci/project_ci_cd_settings_update.rb b/app/graphql/mutations/ci/project_ci_cd_settings_update.rb
index d214aa46cfc..d4e55fd1792 100644
--- a/app/graphql/mutations/ci/project_ci_cd_settings_update.rb
+++ b/app/graphql/mutations/ci/project_ci_cd_settings_update.rb
@@ -19,6 +19,10 @@ module Mutations
argument :job_token_scope_enabled, GraphQL::Types::Boolean,
required: false,
+ deprecated: {
+ reason: 'Outbound job token scope is being removed. This field can now only be set to false',
+ milestone: '16.0'
+ },
description: 'Indicates CI/CD job tokens generated in this project ' \
'have restricted access to other projects.'
@@ -27,10 +31,6 @@ module Mutations
description: 'Indicates CI/CD job tokens generated in other projects ' \
'have restricted access to this project.'
- argument :opt_in_jwt, GraphQL::Types::Boolean,
- required: false,
- description: 'When disabled, the JSON Web Token is always available in all jobs in the pipeline.'
-
field :ci_cd_settings,
Types::Ci::CiCdSettingType,
null: false,
@@ -39,7 +39,9 @@ module Mutations
def resolve(full_path:, **args)
project = authorized_find!(full_path)
- args.delete(:inbound_job_token_scope_enabled) unless Feature.enabled?(:ci_inbound_job_token_scope, project)
+ if args[:job_token_scope_enabled] && project.frozen_outbound_job_token_scopes?
+ raise Gitlab::Graphql::Errors::ArgumentError, 'job_token_scope_enabled can only be set to false'
+ end
settings = project.ci_cd_settings
settings.update(args)
diff --git a/app/graphql/mutations/ci/runner/common_mutation_arguments.rb b/app/graphql/mutations/ci/runner/common_mutation_arguments.rb
new file mode 100644
index 00000000000..f4fbd0a38c7
--- /dev/null
+++ b/app/graphql/mutations/ci/runner/common_mutation_arguments.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Ci
+ module Runner
+ module CommonMutationArguments
+ extend ActiveSupport::Concern
+
+ included do
+ argument :description, GraphQL::Types::String,
+ required: false,
+ description: 'Description of the runner.'
+
+ argument :maintenance_note, GraphQL::Types::String,
+ required: false,
+ description: 'Runner\'s maintenance notes.'
+
+ argument :maximum_timeout, GraphQL::Types::Int,
+ required: false,
+ description: 'Maximum timeout (in seconds) for jobs processed by the runner.'
+
+ argument :access_level, ::Types::Ci::RunnerAccessLevelEnum,
+ required: false,
+ description: 'Access level of the runner.'
+
+ argument :paused, GraphQL::Types::Boolean,
+ required: false,
+ description: 'Indicates the runner is not allowed to receive jobs.'
+
+ argument :locked, GraphQL::Types::Boolean,
+ required: false,
+ description: 'Indicates the runner is locked.'
+
+ argument :run_untagged, GraphQL::Types::Boolean,
+ required: false,
+ description: 'Indicates the runner is able to run untagged jobs.'
+
+ argument :tag_list, [GraphQL::Types::String],
+ required: false,
+ description: 'Tags associated with the runner.'
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/ci/runner/create.rb b/app/graphql/mutations/ci/runner/create.rb
new file mode 100644
index 00000000000..7eca6c27d10
--- /dev/null
+++ b/app/graphql/mutations/ci/runner/create.rb
@@ -0,0 +1,107 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Ci
+ module Runner
+ class Create < BaseMutation
+ graphql_name 'RunnerCreate'
+
+ authorize :create_runner
+
+ include Mutations::Ci::Runner::CommonMutationArguments
+
+ argument :runner_type, ::Types::Ci::RunnerTypeEnum,
+ required: true,
+ description: 'Type of the runner to create.'
+
+ argument :group_id, ::Types::GlobalIDType[Group],
+ required: false,
+ description: 'Global ID of the group that the runner is created in (valid only for group runner).'
+
+ argument :project_id, ::Types::GlobalIDType[Project],
+ required: false,
+ description: 'Global ID of the project that the runner is created in (valid only for project runner).'
+
+ field :runner,
+ Types::Ci::RunnerType,
+ null: true,
+ description: 'Runner after mutation.'
+
+ def ready?(**args)
+ case args[:runner_type]
+ when 'group_type'
+ raise Gitlab::Graphql::Errors::ArgumentError, '`group_id` is missing' unless args[:group_id].present?
+ when 'project_type'
+ raise Gitlab::Graphql::Errors::ArgumentError, '`project_id` is missing' unless args[:project_id].present?
+ end
+
+ parse_gid(**args)
+
+ check_feature_flag(**args)
+
+ super
+ end
+
+ def resolve(**args)
+ case args[:runner_type]
+ when 'group_type', 'project_type'
+ args[:scope] = authorized_find!(**args)
+ args.except!(:group_id, :project_id)
+ else
+ raise_resource_not_available_error! unless current_user.can?(:create_instance_runner)
+ end
+
+ response = { runner: nil, errors: [] }
+ result = ::Ci::Runners::CreateRunnerService.new(user: current_user, params: args).execute
+
+ if result.success?
+ response[:runner] = result.payload[:runner]
+ else
+ response[:errors] = result.errors
+ end
+
+ response
+ end
+
+ private
+
+ def find_object(**args)
+ obj = parse_gid(**args)
+
+ GitlabSchema.find_by_gid(obj) if obj
+ end
+
+ def parse_gid(runner_type:, **args)
+ case runner_type
+ when 'group_type'
+ GitlabSchema.parse_gid(args[:group_id], expected_type: ::Group)
+ when 'project_type'
+ GitlabSchema.parse_gid(args[:project_id], expected_type: ::Project)
+ end
+ end
+
+ def check_feature_flag(**args)
+ case args[:runner_type]
+ when 'instance_type'
+ if Feature.disabled?(:create_runner_workflow_for_admin, current_user)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ '`create_runner_workflow_for_admin` feature flag is disabled.'
+ end
+ when 'group_type'
+ namespace = find_object(**args).sync
+ if Feature.disabled?(:create_runner_workflow_for_namespace, namespace)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ '`create_runner_workflow_for_namespace` feature flag is disabled.'
+ end
+ when 'project_type'
+ project = find_object(**args).sync
+ if project && Feature.disabled?(:create_runner_workflow_for_namespace, project.namespace)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ '`create_runner_workflow_for_namespace` feature flag is disabled.'
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/ci/runner/delete.rb b/app/graphql/mutations/ci/runner/delete.rb
index db68914a4eb..ba309ca754d 100644
--- a/app/graphql/mutations/ci/runner/delete.rb
+++ b/app/graphql/mutations/ci/runner/delete.rb
@@ -15,16 +15,12 @@ module Mutations
description: 'ID of the runner to delete.'
def resolve(id:, **runner_attrs)
- runner = authorized_find!(id)
+ runner = authorized_find!(id: id)
::Ci::Runners::UnregisterRunnerService.new(runner, current_user).execute
{ errors: runner.errors.full_messages }
end
-
- def find_object(id)
- GitlabSchema.find_by_gid(id)
- end
end
end
end
diff --git a/app/graphql/mutations/ci/runner/update.rb b/app/graphql/mutations/ci/runner/update.rb
index 4f0bf19f09c..da28397bb71 100644
--- a/app/graphql/mutations/ci/runner/update.rb
+++ b/app/graphql/mutations/ci/runner/update.rb
@@ -8,53 +8,23 @@ module Mutations
authorize :update_runner
+ include Mutations::Ci::Runner::CommonMutationArguments
+
RunnerID = ::Types::GlobalIDType[::Ci::Runner]
argument :id, RunnerID,
required: true,
description: 'ID of the runner to update.'
- argument :description, GraphQL::Types::String,
- required: false,
- description: 'Description of the runner.'
-
- argument :maintenance_note, GraphQL::Types::String,
- required: false,
- description: 'Runner\'s maintenance notes.'
-
- argument :maximum_timeout, GraphQL::Types::Int,
- required: false,
- description: 'Maximum timeout (in seconds) for jobs processed by the runner.'
-
- argument :access_level, ::Types::Ci::RunnerAccessLevelEnum,
- required: false,
- description: 'Access level of the runner.'
-
argument :active, GraphQL::Types::Boolean,
required: false,
description: 'Indicates the runner is allowed to receive jobs.',
deprecated: { reason: :renamed, replacement: 'paused', milestone: '14.8' }
- argument :paused, GraphQL::Types::Boolean,
- required: false,
- description: 'Indicates the runner is not allowed to receive jobs.'
-
- argument :locked, GraphQL::Types::Boolean,
- required: false,
- description: 'Indicates the runner is locked.'
-
- argument :run_untagged, GraphQL::Types::Boolean,
- required: false,
- description: 'Indicates the runner is able to run untagged jobs.'
-
- argument :tag_list, [GraphQL::Types::String],
- 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 } }
+ prepare: ->(global_ids, _ctx) { global_ids&.filter_map { |gid| gid.model_id.to_i } }
field :runner,
Types::Ci::RunnerType,
@@ -62,7 +32,7 @@ module Mutations
description: 'Runner after mutation.'
def resolve(id:, **runner_attrs)
- runner = authorized_find!(id)
+ runner = authorized_find!(id: id)
associated_projects_ids = runner_attrs.delete(:associated_projects)
@@ -75,10 +45,6 @@ module Mutations
response
end
- def find_object(id)
- GitlabSchema.find_by_gid(id)
- end
-
private
def associate_runner_projects(response, runner, associated_project_ids)
diff --git a/app/graphql/mutations/clusters/agent_tokens/create.rb b/app/graphql/mutations/clusters/agent_tokens/create.rb
index c10e1633350..e717ff4d798 100644
--- a/app/graphql/mutations/clusters/agent_tokens/create.rb
+++ b/app/graphql/mutations/clusters/agent_tokens/create.rb
@@ -40,9 +40,9 @@ module Mutations
result = ::Clusters::AgentTokens::CreateService
.new(
- container: cluster_agent.project,
+ agent: cluster_agent,
current_user: current_user,
- params: args.merge(agent_id: cluster_agent.id)
+ params: args
)
.execute
@@ -54,12 +54,6 @@ module Mutations
errors: Array.wrap(result.message)
}
end
-
- private
-
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
end
end
end
diff --git a/app/graphql/mutations/clusters/agent_tokens/revoke.rb b/app/graphql/mutations/clusters/agent_tokens/revoke.rb
index 974db976f1d..c4187746464 100644
--- a/app/graphql/mutations/clusters/agent_tokens/revoke.rb
+++ b/app/graphql/mutations/clusters/agent_tokens/revoke.rb
@@ -16,15 +16,10 @@ module Mutations
def resolve(id:)
token = authorized_find!(id: id)
- token.update(status: token.class.statuses[:revoked])
- { errors: errors_on_object(token) }
- end
+ ::Clusters::AgentTokens::RevokeService.new(token: token, current_user: current_user).execute
- private
-
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
+ { errors: errors_on_object(token) }
end
end
end
diff --git a/app/graphql/mutations/clusters/agents/delete.rb b/app/graphql/mutations/clusters/agents/delete.rb
index fb482e02794..ddb4e36a68e 100644
--- a/app/graphql/mutations/clusters/agents/delete.rb
+++ b/app/graphql/mutations/clusters/agents/delete.rb
@@ -24,12 +24,6 @@ module Mutations
errors: Array.wrap(result.message)
}
end
-
- private
-
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
end
end
end
diff --git a/app/graphql/mutations/concerns/mutations/finds_by_gid.rb b/app/graphql/mutations/concerns/mutations/finds_by_gid.rb
deleted file mode 100644
index 157f87a413d..00000000000
--- a/app/graphql/mutations/concerns/mutations/finds_by_gid.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-module Mutations
- module FindsByGid
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
- end
-end
diff --git a/app/graphql/mutations/concerns/mutations/finds_namespace.rb b/app/graphql/mutations/concerns/mutations/finds_namespace.rb
new file mode 100644
index 00000000000..bc9dfbcffe5
--- /dev/null
+++ b/app/graphql/mutations/concerns/mutations/finds_namespace.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Mutations
+ module FindsNamespace
+ private
+
+ def find_object(full_path)
+ Routable.find_by_full_path(full_path)
+ end
+ end
+end
diff --git a/app/graphql/mutations/concerns/mutations/spam_protection.rb b/app/graphql/mutations/concerns/mutations/spam_protection.rb
index e61f66c02a5..4c1ce0c8c5e 100644
--- a/app/graphql/mutations/concerns/mutations/spam_protection.rb
+++ b/app/graphql/mutations/concerns/mutations/spam_protection.rb
@@ -26,8 +26,6 @@ module Mutations
elsif fields[:needs_captcha_response]
fields.delete :spam
raise NeedsCaptchaResponseError.new(NEEDS_CAPTCHA_RESPONSE_MESSAGE, extensions: fields)
- else
- nil
end
end
end
diff --git a/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb b/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb
index 6738f268e92..f009abdba70 100644
--- a/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb
+++ b/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb
@@ -36,6 +36,18 @@ module Mutations
argument :milestone_widget, ::Types::WorkItems::Widgets::MilestoneInputType,
required: false,
description: 'Input for milestone widget.'
+ argument :notifications_widget,
+ ::Types::WorkItems::Widgets::NotificationsUpdateInputType,
+ required: false,
+ description: 'Input for notifications widget.'
+ argument :current_user_todos_widget,
+ ::Types::WorkItems::Widgets::CurrentUserTodosInputType,
+ required: false,
+ description: 'Input for to-dos widget.'
+ argument :award_emoji_widget,
+ ::Types::WorkItems::Widgets::AwardEmojiUpdateInputType,
+ required: false,
+ description: 'Input for award emoji widget.'
end
end
end
diff --git a/app/graphql/mutations/container_repositories/destroy_base.rb b/app/graphql/mutations/container_repositories/destroy_base.rb
index 1c2c4d87a5f..46851c15702 100644
--- a/app/graphql/mutations/container_repositories/destroy_base.rb
+++ b/app/graphql/mutations/container_repositories/destroy_base.rb
@@ -4,12 +4,6 @@ module Mutations
module ContainerRepositories
class DestroyBase < Mutations::BaseMutation
include ::Mutations::PackageEventable
-
- private
-
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
end
end
end
diff --git a/app/graphql/mutations/design_management/update.rb b/app/graphql/mutations/design_management/update.rb
new file mode 100644
index 00000000000..67732b70f29
--- /dev/null
+++ b/app/graphql/mutations/design_management/update.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Mutations
+ module DesignManagement
+ class Update < ::Mutations::BaseMutation
+ graphql_name "DesignManagementUpdate"
+
+ authorize :update_design
+
+ argument :id, ::Types::GlobalIDType[::DesignManagement::Design],
+ required: true,
+ description: "ID of the design to update."
+
+ argument :description, GraphQL::Types::String,
+ required: false,
+ description: copy_field_description(Types::DesignManagement::DesignType, :description)
+
+ field :design, Types::DesignManagement::DesignType,
+ null: false,
+ description: "Updated design."
+
+ def resolve(id:, description:)
+ design = authorized_find!(id: id)
+ design.update(description: description)
+
+ {
+ design: design.reset,
+ errors: errors_on_object(design)
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/discussions/toggle_resolve.rb b/app/graphql/mutations/discussions/toggle_resolve.rb
index fce6e4f416f..dc5731add3a 100644
--- a/app/graphql/mutations/discussions/toggle_resolve.rb
+++ b/app/graphql/mutations/discussions/toggle_resolve.rb
@@ -53,10 +53,6 @@ module Mutations
end
end
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
-
def resolve!(discussion)
::Discussions::ResolveService.new(
discussion.project,
diff --git a/app/graphql/mutations/environments/canary_ingress/update.rb b/app/graphql/mutations/environments/canary_ingress/update.rb
index 1cddfdd815b..43e9b6c0881 100644
--- a/app/graphql/mutations/environments/canary_ingress/update.rb
+++ b/app/graphql/mutations/environments/canary_ingress/update.rb
@@ -35,10 +35,6 @@ module Mutations
{ errors: Array.wrap(result[:message]) }
end
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
-
private
def certificate_based_clusters_enabled?
diff --git a/app/graphql/mutations/environments/stop.rb b/app/graphql/mutations/environments/stop.rb
new file mode 100644
index 00000000000..b0b87b9b534
--- /dev/null
+++ b/app/graphql/mutations/environments/stop.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Environments
+ class Stop < ::Mutations::BaseMutation
+ graphql_name 'EnvironmentStop'
+ description 'Stop an environment.'
+
+ authorize :stop_environment
+
+ argument :id,
+ ::Types::GlobalIDType[::Environment],
+ required: true,
+ description: 'Global ID of the environment to stop.'
+
+ argument :force,
+ GraphQL::Types::Boolean,
+ required: false,
+ description: 'Force environment to stop without executing on_stop actions.'
+
+ field :environment,
+ Types::EnvironmentType,
+ null: true,
+ description: 'Environment after attempt to stop.'
+
+ def resolve(id:, **kwargs)
+ environment = authorized_find!(id: id)
+
+ response = ::Environments::StopService.new(environment.project, current_user, kwargs).execute(environment)
+
+ if response.success?
+ { environment: response.payload[:environment], errors: [] }
+ else
+ { environment: response.payload[:environment], errors: response.errors }
+ end
+ end
+ end
+ end
+end
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 bb1da9278ff..c29dc98c872 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
@@ -35,7 +35,7 @@ module Mutations
end
def authorize!(object)
- raise_noteable_not_incident! if object && !object.try(:incident?)
+ raise_noteable_not_incident! if object && !object.try(:incident_type_issue?)
super
end
diff --git a/app/graphql/mutations/issues/bulk_update.rb b/app/graphql/mutations/issues/bulk_update.rb
index 3d80f119079..9c9dd3cf2fc 100644
--- a/app/graphql/mutations/issues/bulk_update.rb
+++ b/app/graphql/mutations/issues/bulk_update.rb
@@ -14,7 +14,8 @@ module Mutations
argument :parent_id, ::Types::GlobalIDType[::IssueParent],
required: true,
- description: 'Global ID of the parent that the bulk update will be scoped to . ' \
+ description: 'Global ID of the parent to which the bulk update will be scoped. ' \
+ 'The parent can be a project **(FREE)** or a group **(PREMIUM)**. ' \
'Example `IssueParentID` are `"gid://gitlab/Project/1"` and `"gid://gitlab/Group/1"`.'
argument :ids, [::Types::GlobalIDType[::Issue]],
@@ -31,6 +32,22 @@ module Mutations
required: false,
description: 'Global ID of the milestone that will be assigned to the issues.'
+ argument :state_event, Types::IssueStateEventEnum,
+ description: 'Close or reopen an issue.',
+ required: false
+
+ argument :add_label_ids, [::Types::GlobalIDType[::Label]],
+ description: 'Global ID array of the labels that will be added to the issues. ',
+ required: false
+
+ argument :remove_label_ids, [::Types::GlobalIDType[::Label]],
+ description: 'Global ID array of the labels that will be removed from the issues. ',
+ required: false
+
+ argument :subscription_event, Types::IssuableSubscriptionEventEnum,
+ description: 'Subscribe to or unsubscribe from issue notifications.',
+ required: false
+
field :updated_issue_count, GraphQL::Types::Int,
null: true,
description: 'Number of issues that were successfully updated.'
@@ -74,7 +91,7 @@ module Mutations
end
def prepared_params(attributes, ids)
- prepared = { issuable_ids: model_ids_from(ids).uniq }
+ prepared = attributes.except(*global_id_arguments).merge(issuable_ids: model_ids_from(ids).uniq)
global_id_arguments.each do |argument|
next unless attributes.key?(argument)
@@ -92,7 +109,7 @@ module Mutations
end
def global_id_arguments
- %i[assignee_ids milestone_id]
+ %i[assignee_ids milestone_id add_label_ids remove_label_ids]
end
def model_ids_from(attributes)
diff --git a/app/graphql/mutations/members/bulk_update_base.rb b/app/graphql/mutations/members/bulk_update_base.rb
new file mode 100644
index 00000000000..1e0208e864d
--- /dev/null
+++ b/app/graphql/mutations/members/bulk_update_base.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Members
+ class BulkUpdateBase < BaseMutation
+ include ::API::Helpers::MembersHelpers
+
+ argument :user_ids,
+ [::Types::GlobalIDType[::User]],
+ required: true,
+ description: 'Global IDs of the members.'
+
+ argument :access_level,
+ ::Types::MemberAccessLevelEnum,
+ required: true,
+ description: 'Access level to update the members to.'
+
+ argument :expires_at,
+ Types::TimeType,
+ required: false,
+ description: 'Date and time the membership expires.'
+
+ MAX_MEMBERS_UPDATE_LIMIT = 50
+ MAX_MEMBERS_UPDATE_ERROR = "Count of members to be updated should be less than #{MAX_MEMBERS_UPDATE_LIMIT}."
+ .freeze
+ INVALID_MEMBERS_ERROR = 'Only access level of direct members can be updated.'
+
+ def resolve(**args)
+ result = ::Members::UpdateService
+ .new(current_user, args.except(:user_ids, source_id_param_name))
+ .execute(@updatable_members)
+
+ {
+ source_members_key => result[:members],
+ errors: Array.wrap(result[:message])
+ }
+ rescue Gitlab::Access::AccessDeniedError
+ {
+ errors: ["Unable to update members, please check user permissions."]
+ }
+ end
+
+ private
+
+ def ready?(**args)
+ source = authorized_find!(source_id: args[source_id_param_name])
+ user_ids = args.fetch(:user_ids, {}).map(&:model_id)
+ @updatable_members = only_direct_members(source, user_ids)
+
+ if @updatable_members.size > MAX_MEMBERS_UPDATE_LIMIT
+ raise Gitlab::Graphql::Errors::InvalidMemberCountError, MAX_MEMBERS_UPDATE_ERROR
+ end
+
+ if @updatable_members.size != user_ids.size
+ raise Gitlab::Graphql::Errors::InvalidMembersError, INVALID_MEMBERS_ERROR
+ end
+
+ super
+ end
+
+ def find_object(source_id:)
+ GitlabSchema.object_from_id(source_id, expected_type: source_type)
+ end
+
+ def only_direct_members(source, user_ids)
+ source_members(source)
+ .with_user(user_ids)
+ .to_a
+ end
+
+ def source_id_param_name
+ "#{source_name}_id".to_sym
+ end
+
+ def source_members_key
+ "#{source_name}_members".to_sym
+ end
+
+ def source_name
+ source_type.name.downcase
+ end
+
+ def source_type
+ raise NotImplementedError
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/members/groups/bulk_update.rb b/app/graphql/mutations/members/groups/bulk_update.rb
index d0b19bd9634..fe3c7521c20 100644
--- a/app/graphql/mutations/members/groups/bulk_update.rb
+++ b/app/graphql/mutations/members/groups/bulk_update.rb
@@ -3,81 +3,22 @@
module Mutations
module Members
module Groups
- class BulkUpdate < ::Mutations::BaseMutation
+ class BulkUpdate < BulkUpdateBase
graphql_name 'GroupMemberBulkUpdate'
-
- include Gitlab::Utils::StrongMemoize
-
authorize :admin_group_member
field :group_members,
- [Types::GroupMemberType],
- null: true,
- description: 'Group members after mutation.'
+ [Types::GroupMemberType],
+ null: true,
+ description: 'Group members after mutation.'
argument :group_id,
- ::Types::GlobalIDType[::Group],
- required: true,
- description: 'Global ID of the group.'
-
- argument :user_ids,
- [::Types::GlobalIDType[::User]],
- required: true,
- description: 'Global IDs of the group members.'
-
- argument :access_level,
- ::Types::MemberAccessLevelEnum,
- required: true,
- description: 'Access level to update the members to.'
-
- argument :expires_at,
- Types::TimeType,
- required: false,
- description: 'Date and time the membership expires.'
-
- MAX_MEMBERS_UPDATE_LIMIT = 50
- MAX_MEMBERS_UPDATE_ERROR = "Count of members to be updated should be less than #{MAX_MEMBERS_UPDATE_LIMIT}."
- INVALID_MEMBERS_ERROR = 'Only access level of direct members can be updated.'
-
- def resolve(group_id:, **args)
- result = ::Members::UpdateService.new(current_user, args.except(:user_ids)).execute(@updatable_group_members)
-
- {
- group_members: result[:members],
- errors: Array.wrap(result[:message])
- }
- rescue Gitlab::Access::AccessDeniedError
- {
- errors: ["Unable to update members, please check user permissions."]
- }
- end
-
- private
-
- def ready?(**args)
- group = authorized_find!(group_id: args[:group_id])
- user_ids = args.fetch(:user_ids, {}).map(&:model_id)
- @updatable_group_members = only_direct_group_members(group, user_ids)
-
- if @updatable_group_members.size > MAX_MEMBERS_UPDATE_LIMIT
- raise Gitlab::Graphql::Errors::InvalidMemberCountError, MAX_MEMBERS_UPDATE_ERROR
- end
-
- if @updatable_group_members.size != user_ids.size
- raise Gitlab::Graphql::Errors::InvalidMembersError, INVALID_MEMBERS_ERROR
- end
-
- super
- end
-
- def find_object(group_id:)
- GitlabSchema.object_from_id(group_id, expected_type: ::Group)
- end
+ ::Types::GlobalIDType[::Group],
+ required: true,
+ description: 'Global ID of the group.'
- def only_direct_group_members(group, user_ids)
- group
- .members
- .with_user(user_ids).to_a
+ def source_type
+ ::Group
end
end
end
diff --git a/app/graphql/mutations/members/projects/bulk_update.rb b/app/graphql/mutations/members/projects/bulk_update.rb
new file mode 100644
index 00000000000..9bf7968670e
--- /dev/null
+++ b/app/graphql/mutations/members/projects/bulk_update.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Members
+ module Projects
+ class BulkUpdate < BulkUpdateBase
+ graphql_name 'ProjectMemberBulkUpdate'
+ description 'Updates multiple members of a project. ' \
+ 'To use this mutation, you must have at least the Maintainer role.'
+
+ authorize :admin_project_member
+
+ field :project_members,
+ [Types::ProjectMemberType],
+ null: true,
+ description: 'Project members after mutation.'
+
+ argument :project_id,
+ ::Types::GlobalIDType[::Project],
+ required: true,
+ description: 'Global ID of the project.'
+
+ def source_type
+ ::Project
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/metrics/dashboard/annotations/create.rb b/app/graphql/mutations/metrics/dashboard/annotations/create.rb
index 2e7c0c5a2f9..296efa19bb7 100644
--- a/app/graphql/mutations/metrics/dashboard/annotations/create.rb
+++ b/app/graphql/mutations/metrics/dashboard/annotations/create.rb
@@ -10,7 +10,7 @@ module Mutations
ANNOTATION_SOURCE_ARGUMENT_ERROR = 'Either a cluster or environment global id is required'
INVALID_ANNOTATION_SOURCE_ERROR = 'Invalid cluster or environment id'
- authorize :create_metrics_dashboard_annotation
+ authorize :admin_metrics_dashboard_annotation
field :annotation,
Types::Metrics::Dashboards::AnnotationType,
@@ -75,6 +75,8 @@ module Mutations
private
def ready?(**args)
+ raise_resource_not_available_error! if Feature.enabled?(:remove_monitor_metrics)
+
# Raise error if both cluster_id and environment_id are present or neither is present
unless args[:cluster_id].present? ^ args[:environment_id].present?
raise Gitlab::Graphql::Errors::ArgumentError, ANNOTATION_SOURCE_ARGUMENT_ERROR
@@ -83,10 +85,6 @@ module Mutations
super(**args)
end
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
-
def annotation_create_params(args)
annotation_source = AnnotationSource.new(object: annotation_source(args))
diff --git a/app/graphql/mutations/metrics/dashboard/annotations/delete.rb b/app/graphql/mutations/metrics/dashboard/annotations/delete.rb
index e0fadff13d4..32047cda213 100644
--- a/app/graphql/mutations/metrics/dashboard/annotations/delete.rb
+++ b/app/graphql/mutations/metrics/dashboard/annotations/delete.rb
@@ -7,13 +7,15 @@ module Mutations
class Delete < Base
graphql_name 'DeleteAnnotation'
- authorize :delete_metrics_dashboard_annotation
+ authorize :admin_metrics_dashboard_annotation
argument :id, ::Types::GlobalIDType[::Metrics::Dashboard::Annotation],
required: true,
description: 'Global ID of the annotation to delete.'
def resolve(id:)
+ raise_resource_not_available_error! if Feature.enabled?(:remove_monitor_metrics)
+
annotation = authorized_find!(id: id)
result = ::Metrics::Dashboard::Annotations::DeleteService.new(context[:current_user], annotation).execute
diff --git a/app/graphql/mutations/notes/base.rb b/app/graphql/mutations/notes/base.rb
index fb74805db17..d656835c335 100644
--- a/app/graphql/mutations/notes/base.rb
+++ b/app/graphql/mutations/notes/base.rb
@@ -13,12 +13,6 @@ module Mutations
Types::Notes::NoteType,
null: true,
description: 'Note after mutation.'
-
- private
-
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
end
end
end
diff --git a/app/graphql/mutations/notes/create/base.rb b/app/graphql/mutations/notes/create/base.rb
index f48e62af767..69cd1426218 100644
--- a/app/graphql/mutations/notes/create/base.rb
+++ b/app/graphql/mutations/notes/create/base.rb
@@ -47,10 +47,6 @@ module Mutations
private
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
-
def create_note_params(noteable, args)
{
noteable: noteable,
diff --git a/app/graphql/mutations/packages/destroy.rb b/app/graphql/mutations/packages/destroy.rb
index a398b1ff9dc..95832ec8b85 100644
--- a/app/graphql/mutations/packages/destroy.rb
+++ b/app/graphql/mutations/packages/destroy.rb
@@ -23,12 +23,6 @@ module Mutations
errors: errors
}
end
-
- private
-
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
end
end
end
diff --git a/app/graphql/mutations/packages/destroy_file.rb b/app/graphql/mutations/packages/destroy_file.rb
index f2a8f2b853a..c7dd2df704e 100644
--- a/app/graphql/mutations/packages/destroy_file.rb
+++ b/app/graphql/mutations/packages/destroy_file.rb
@@ -21,12 +21,6 @@ module Mutations
{ errors: package_file.errors.full_messages }
end
-
- private
-
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
end
end
end
diff --git a/app/graphql/mutations/projects/sync_fork.rb b/app/graphql/mutations/projects/sync_fork.rb
new file mode 100644
index 00000000000..4520f6388c5
--- /dev/null
+++ b/app/graphql/mutations/projects/sync_fork.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Projects
+ class SyncFork < BaseMutation
+ graphql_name 'ProjectSyncFork'
+
+ include FindsProject
+
+ argument :project_path, GraphQL::Types::ID,
+ required: true,
+ description: 'Full path of the project to initialize.'
+
+ argument :target_branch, GraphQL::Types::String,
+ required: true,
+ description: 'Ref of the fork to fetch into.'
+
+ field :details, Types::Projects::ForkDetailsType,
+ null: true,
+ description: 'Updated fork details.'
+
+ def resolve(project_path:, target_branch:)
+ project = authorized_find!(project_path, target_branch)
+
+ return respond(nil, ['Feature flag is disabled']) unless Feature.enabled?(:synchronize_fork,
+ project.fork_source)
+
+ return respond(nil, ['Target branch does not exist']) unless project.repository.branch_exists?(target_branch)
+
+ details_resolver = Resolvers::Projects::ForkDetailsResolver.new(object: project, context: context, field: nil)
+ details = details_resolver.resolve(ref: target_branch)
+
+ return respond(nil, ['This branch of this project cannot be updated from the upstream']) unless details
+
+ enqueue_sync_fork(project, target_branch, details)
+ end
+
+ def enqueue_sync_fork(project, target_branch, details)
+ return respond(details, []) if details.counts[:behind] == 0
+
+ if details.has_conflicts?
+ return respond(details, ['The synchronization cannot happen due to the merge conflict'])
+ end
+
+ return respond(details, ['This service has been called too many times.']) if rate_limit_throttled?(project)
+ return respond(details, ['Another fork sync is already in progress']) unless details.exclusive_lease.try_obtain
+
+ ::Projects::Forks::SyncWorker.perform_async(project.id, current_user.id, target_branch) # rubocop:disable CodeReuse/Worker
+
+ respond(details, [])
+ end
+
+ def rate_limit_throttled?(project)
+ Gitlab::ApplicationRateLimiter.throttled?(:project_fork_sync, scope: [project, current_user])
+ end
+
+ def respond(details, errors)
+ { details: details, errors: errors }
+ end
+
+ def authorized_find!(project_path, target_branch)
+ project = find_object(project_path)
+
+ return project if ::Gitlab::UserAccess.new(current_user, container: project).can_push_to_branch?(target_branch)
+
+ raise_resource_not_available_error!
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/release_asset_links/create.rb b/app/graphql/mutations/release_asset_links/create.rb
index f6445514ce9..bda998764b9 100644
--- a/app/graphql/mutations/release_asset_links/create.rb
+++ b/app/graphql/mutations/release_asset_links/create.rb
@@ -36,13 +36,15 @@ module Mutations
raise_resource_not_available_error!
end
- new_link = release.links.create(link_attrs)
-
- unless new_link.persisted?
- return { link: nil, errors: new_link.errors.full_messages }
+ result = ::Releases::Links::CreateService
+ .new(release, current_user, link_attrs)
+ .execute
+
+ if result.success?
+ { link: result.payload[:link], errors: [] }
+ else
+ { link: nil, errors: result.message }
end
-
- { link: new_link, errors: [] }
end
end
end
diff --git a/app/graphql/mutations/release_asset_links/delete.rb b/app/graphql/mutations/release_asset_links/delete.rb
index 91fa74859f6..891d8e5a4d8 100644
--- a/app/graphql/mutations/release_asset_links/delete.rb
+++ b/app/graphql/mutations/release_asset_links/delete.rb
@@ -19,17 +19,17 @@ module Mutations
description: 'Deleted release asset link.'
def resolve(id:)
- link = authorized_find!(id)
+ link = authorized_find!(id: id)
- unless link.destroy
- return { link: nil, errors: link.errors.full_messages }
- end
-
- { link: link, errors: [] }
- end
+ result = ::Releases::Links::DestroyService
+ .new(link.release, current_user)
+ .execute(link)
- def find_object(id)
- GitlabSchema.find_by_gid(id)
+ if result.success?
+ { link: result.payload[:link], errors: [] }
+ else
+ { link: nil, errors: result.message }
+ end
end
end
end
diff --git a/app/graphql/mutations/release_asset_links/update.rb b/app/graphql/mutations/release_asset_links/update.rb
index f9368927371..3df2d28b88c 100644
--- a/app/graphql/mutations/release_asset_links/update.rb
+++ b/app/graphql/mutations/release_asset_links/update.rb
@@ -44,17 +44,17 @@ module Mutations
end
def resolve(id:, **link_attrs)
- link = authorized_find!(id)
+ link = authorized_find!(id: id)
- unless link.update(link_attrs)
- return { link: nil, errors: link.errors.full_messages }
- end
-
- { link: link, errors: [] }
- end
+ result = ::Releases::Links::UpdateService
+ .new(link.release, current_user, link_attrs)
+ .execute(link)
- def find_object(id)
- GitlabSchema.find_by_gid(id)
+ if result.success?
+ { link: result.payload[:link], errors: [] }
+ else
+ { link: nil, errors: result.message }
+ end
end
end
end
diff --git a/app/graphql/mutations/terraform/state/base.rb b/app/graphql/mutations/terraform/state/base.rb
index 01f69934ea3..9a264836ef5 100644
--- a/app/graphql/mutations/terraform/state/base.rb
+++ b/app/graphql/mutations/terraform/state/base.rb
@@ -10,12 +10,6 @@ module Mutations
Types::GlobalIDType[::Terraform::State],
required: true,
description: 'Global ID of the Terraform state.'
-
- private
-
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
end
end
end
diff --git a/app/graphql/mutations/todos/base.rb b/app/graphql/mutations/todos/base.rb
deleted file mode 100644
index 9a94c5d1e6d..00000000000
--- a/app/graphql/mutations/todos/base.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module Mutations
- module Todos
- class Base < ::Mutations::BaseMutation
- private
-
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
- end
- end
-end
diff --git a/app/graphql/mutations/todos/create.rb b/app/graphql/mutations/todos/create.rb
index 489d2f490ff..8a0906da724 100644
--- a/app/graphql/mutations/todos/create.rb
+++ b/app/graphql/mutations/todos/create.rb
@@ -2,7 +2,7 @@
module Mutations
module Todos
- class Create < ::Mutations::Todos::Base
+ class Create < ::Mutations::BaseMutation
graphql_name 'TodoCreate'
authorize :create_todo
@@ -17,7 +17,7 @@ module Mutations
description: 'To-do item created.'
def resolve(target_id:)
- target = authorized_find!(target_id)
+ target = authorized_find!(id: target_id)
todo = TodoService.new.mark_todo(target, current_user)&.first
errors = errors_on_object(todo) if todo
@@ -27,12 +27,6 @@ module Mutations
errors: errors
}
end
-
- private
-
- def find_object(id)
- GitlabSchema.find_by_gid(id)
- end
end
end
end
diff --git a/app/graphql/mutations/todos/mark_all_done.rb b/app/graphql/mutations/todos/mark_all_done.rb
index fe4023515a4..7f8d15e033a 100644
--- a/app/graphql/mutations/todos/mark_all_done.rb
+++ b/app/graphql/mutations/todos/mark_all_done.rb
@@ -2,7 +2,7 @@
module Mutations
module Todos
- class MarkAllDone < ::Mutations::Todos::Base
+ class MarkAllDone < ::Mutations::BaseMutation
graphql_name 'TodosMarkAllDone'
authorize :update_user
diff --git a/app/graphql/mutations/todos/mark_done.rb b/app/graphql/mutations/todos/mark_done.rb
index 4fecba55242..05d69fbc969 100644
--- a/app/graphql/mutations/todos/mark_done.rb
+++ b/app/graphql/mutations/todos/mark_done.rb
@@ -2,7 +2,7 @@
module Mutations
module Todos
- class MarkDone < ::Mutations::Todos::Base
+ class MarkDone < ::Mutations::BaseMutation
graphql_name 'TodoMarkDone'
authorize :update_todo
diff --git a/app/graphql/mutations/todos/restore.rb b/app/graphql/mutations/todos/restore.rb
index def24cb71bc..a169ec58a9a 100644
--- a/app/graphql/mutations/todos/restore.rb
+++ b/app/graphql/mutations/todos/restore.rb
@@ -2,7 +2,7 @@
module Mutations
module Todos
- class Restore < ::Mutations::Todos::Base
+ class Restore < ::Mutations::BaseMutation
graphql_name 'TodoRestore'
authorize :update_todo
diff --git a/app/graphql/mutations/todos/restore_many.rb b/app/graphql/mutations/todos/restore_many.rb
index f2f944860c2..106ba18b852 100644
--- a/app/graphql/mutations/todos/restore_many.rb
+++ b/app/graphql/mutations/todos/restore_many.rb
@@ -2,7 +2,7 @@
module Mutations
module Todos
- class RestoreMany < ::Mutations::Todos::Base
+ class RestoreMany < ::Mutations::BaseMutation
graphql_name 'TodoRestoreMany'
MAX_UPDATE_AMOUNT = 50
diff --git a/app/graphql/mutations/user_preferences/update.rb b/app/graphql/mutations/user_preferences/update.rb
index c92c6d725b7..16c7b37532c 100644
--- a/app/graphql/mutations/user_preferences/update.rb
+++ b/app/graphql/mutations/user_preferences/update.rb
@@ -8,6 +8,9 @@ module Mutations
argument :issues_sort, Types::IssueSortEnum,
required: false,
description: 'Sort order for issue lists.'
+ argument :visibility_pipeline_id_type, Types::VisibilityPipelineIdTypeEnum,
+ required: false,
+ description: 'Determines whether the pipeline list shows ID or IID.'
field :user_preferences,
Types::UserPreferencesType,
diff --git a/app/graphql/mutations/work_items/convert.rb b/app/graphql/mutations/work_items/convert.rb
new file mode 100644
index 00000000000..83bca56d900
--- /dev/null
+++ b/app/graphql/mutations/work_items/convert.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module Mutations
+ module WorkItems
+ class Convert < BaseMutation
+ graphql_name 'WorkItemConvert'
+ description "Converts the work item to a new type"
+
+ include Mutations::SpamProtection
+
+ authorize :update_work_item
+
+ argument :id, ::Types::GlobalIDType[::WorkItem],
+ required: true,
+ description: 'Global ID of the work item.'
+ argument :work_item_type_id, ::Types::GlobalIDType[::WorkItems::Type],
+ required: true,
+ description: 'Global ID of the new work item type.'
+
+ field :work_item, Types::WorkItemType,
+ null: true,
+ description: 'Updated work item.'
+
+ def resolve(attributes)
+ work_item = authorized_find!(id: attributes[:id])
+
+ work_item_type = find_work_item_type!(attributes[:work_item_type_id])
+ authorize_work_item_type!(work_item, work_item_type)
+
+ spam_params = ::Spam::SpamParams.new_from_request(request: context[:request])
+
+ update_result = ::WorkItems::UpdateService.new(
+ container: work_item.project,
+ current_user: current_user,
+ params: { work_item_type: work_item_type, issue_type: work_item_type.base_type },
+ spam_params: spam_params
+ ).execute(work_item)
+
+ check_spam_action_response!(work_item)
+
+ {
+ work_item: (update_result[:work_item] if update_result[:status] == :success),
+ errors: Array.wrap(update_result[:message])
+ }
+ end
+
+ private
+
+ def find_work_item_type!(gid)
+ work_item_type = ::WorkItems::Type.find_by_id(gid.model_id)
+
+ return work_item_type if work_item_type.present?
+
+ message = format(_('Work Item type with id %{id} was not found'), id: gid.model_id)
+ raise_resource_not_available_error! message
+ end
+
+ def authorize_work_item_type!(work_item, work_item_type)
+ return if current_user.can?(:"create_#{work_item_type.base_type}", work_item)
+
+ message = format(_('You are not allowed to change the Work Item type to %{name}.'), name: work_item_type.name)
+ raise_resource_not_available_error! message
+ end
+
+ def find_object(id:)
+ GitlabSchema.find_by_gid(id)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/work_items/create.rb b/app/graphql/mutations/work_items/create.rb
index 9f124de7ab2..dfd2d5d1f88 100644
--- a/app/graphql/mutations/work_items/create.rb
+++ b/app/graphql/mutations/work_items/create.rb
@@ -6,13 +6,15 @@ module Mutations
graphql_name 'WorkItemCreate'
include Mutations::SpamProtection
- include FindsProject
+ include FindsNamespace
include Mutations::WorkItems::Widgetable
description "Creates a work item."
authorize :create_work_item
+ MUTUALLY_EXCLUSIVE_ARGUMENTS_ERROR = 'Please provide either projectPath or namespacePath argument, but not both.'
+
argument :confidential, GraphQL::Types::Boolean,
required: false,
description: 'Sets the work item confidentiality.'
@@ -25,9 +27,16 @@ module Mutations
argument :milestone_widget, ::Types::WorkItems::Widgets::MilestoneInputType,
required: false,
description: 'Input for milestone widget.'
+ argument :namespace_path, GraphQL::Types::ID,
+ required: false,
+ description: 'Full path of the namespace(project or group) the work item is created in.'
argument :project_path, GraphQL::Types::ID,
- required: true,
- description: 'Full path of the project the work item is associated with.'
+ required: false,
+ description: 'Full path of the project the work item is associated with.',
+ deprecated: {
+ reason: 'Please use namespace_path instead. That will cover for both projects and groups',
+ milestone: '15.10'
+ }
argument :title, GraphQL::Types::String,
required: true,
description: copy_field_description(Types::WorkItemType, :title)
@@ -39,8 +48,17 @@ module Mutations
null: true,
description: 'Created work item.'
- def resolve(project_path:, **attributes)
- project = authorized_find!(project_path)
+ def ready?(**args)
+ if args.slice(:project_path, :namespace_path)&.length != 1
+ raise Gitlab::Graphql::Errors::ArgumentError, MUTUALLY_EXCLUSIVE_ARGUMENTS_ERROR
+ end
+
+ super
+ end
+
+ def resolve(project_path: nil, namespace_path: nil, **attributes)
+ container_path = project_path || namespace_path
+ container = authorized_find!(container_path)
spam_params = ::Spam::SpamParams.new_from_request(request: context[:request])
params = global_id_compatibility_params(attributes).merge(author_id: current_user.id)
@@ -48,7 +66,7 @@ module Mutations
widget_params = extract_widget_params!(type, params)
create_result = ::WorkItems::CreateService.new(
- container: project,
+ container: container,
current_user: current_user,
params: params,
spam_params: spam_params,
diff --git a/app/graphql/mutations/work_items/create_from_task.rb b/app/graphql/mutations/work_items/create_from_task.rb
index 4ef8269a42f..23ae09b23fd 100644
--- a/app/graphql/mutations/work_items/create_from_task.rb
+++ b/app/graphql/mutations/work_items/create_from_task.rb
@@ -46,12 +46,6 @@ module Mutations
response
end
-
- private
-
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
end
end
end
diff --git a/app/graphql/mutations/work_items/delete.rb b/app/graphql/mutations/work_items/delete.rb
index ec0244fa65e..bce59448412 100644
--- a/app/graphql/mutations/work_items/delete.rb
+++ b/app/graphql/mutations/work_items/delete.rb
@@ -29,12 +29,6 @@ module Mutations
errors: result.errors
}
end
-
- private
-
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
end
end
end
diff --git a/app/graphql/mutations/work_items/delete_task.rb b/app/graphql/mutations/work_items/delete_task.rb
index 47ab3748ab4..b13d7e2e3bf 100644
--- a/app/graphql/mutations/work_items/delete_task.rb
+++ b/app/graphql/mutations/work_items/delete_task.rb
@@ -53,11 +53,6 @@ module Mutations
raise_resource_not_available_error!
end
end
-
- # method used by `authorized_find!(id: id)`
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
end
end
end
diff --git a/app/graphql/mutations/work_items/export.rb b/app/graphql/mutations/work_items/export.rb
new file mode 100644
index 00000000000..a3c7bae5571
--- /dev/null
+++ b/app/graphql/mutations/work_items/export.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Mutations
+ module WorkItems
+ class Export < BaseMutation
+ graphql_name 'WorkItemExport'
+
+ include FindsProject
+ include ::WorkItems::SharedFilterArguments
+ include ::SearchArguments
+
+ authorize :export_work_items
+
+ argument :project_path,
+ GraphQL::Types::ID,
+ required: true,
+ description: 'Full project path.'
+
+ argument :selected_fields,
+ [::Types::WorkItems::AvailableExportFieldsEnum],
+ required: false,
+ description: 'List of selected fields to be exported. Omit to export all available fields.'
+
+ field :message, GraphQL::Types::String,
+ null: true,
+ description: 'Export request result message.'
+
+ def resolve(args)
+ project_path = args.delete(:project_path)
+ project = authorized_find!(project_path)
+
+ check_export_available_for!(project)
+
+ # rubocop:disable CodeReuse/Worker
+ IssuableExportCsvWorker.perform_async(:work_item, current_user.id, project.id, args)
+ # rubocop:enable CodeReuse/Worker
+
+ {
+ message: format(_('Your CSV export request has succeeded. The result will be emailed to %{email}.'),
+ email: current_user.notification_email_or_default),
+ errors: []
+ }
+ end
+
+ def check_export_available_for!(project)
+ return if Feature.enabled?(:import_export_work_items_csv, project)
+
+ error = '`import_export_work_items_csv` feature flag is disabled.'
+
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable, error
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/work_items/update.rb b/app/graphql/mutations/work_items/update.rb
index db6af38d82e..3fd0f5aab62 100644
--- a/app/graphql/mutations/work_items/update.rb
+++ b/app/graphql/mutations/work_items/update.rb
@@ -17,6 +17,8 @@ module Mutations
description: 'Updated work item.'
def resolve(id:, **attributes)
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/408575')
+
work_item = authorized_find!(id: id)
spam_params = ::Spam::SpamParams.new_from_request(request: context[:request])
@@ -42,10 +44,6 @@ module Mutations
private
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
-
def interpret_quick_actions!(work_item, current_user, widget_params, attributes = {})
return unless work_item.work_item_type.widgets.include?(::WorkItems::Widgets::Description)
@@ -60,21 +58,10 @@ module Mutations
description_param[:description] = description if description && description != original_description
- # Widgets have a set of quick action params that they must process.
- # Map them to widget_params so they can be picked up by widget services.
- work_item.work_item_type.widgets
- .filter { |widget| widget.respond_to?(:quick_action_params) }
- .each do |widget|
- widget.quick_action_params
- .filter { |param_name| command_params.key?(param_name) }
- .each do |param_name|
- widget_params[widget.api_symbol] ||= {}
- widget_params[widget.api_symbol][param_name] = command_params.delete(param_name)
- end
- end
-
- # The command_params not processed by widgets (e.g. title) should be placed in 'attributes'.
- attributes.merge!(command_params || {})
+ parsed_params = work_item.transform_quick_action_params(command_params)
+
+ widget_params.merge!(parsed_params[:widgets])
+ attributes.merge!(parsed_params[:common])
end
end
end
diff --git a/app/graphql/queries/repository/path_last_commit.query.graphql b/app/graphql/queries/repository/path_last_commit.query.graphql
index 914be3a72c1..facbf1555fc 100644
--- a/app/graphql/queries/repository/path_last_commit.query.graphql
+++ b/app/graphql/queries/repository/path_last_commit.query.graphql
@@ -27,7 +27,30 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
avatarUrl
webPath
}
- signatureHtml
+ signature {
+ __typename
+ ... on GpgSignature {
+ gpgKeyPrimaryKeyid
+ verificationStatus
+ }
+ ... on X509Signature {
+ verificationStatus
+ x509Certificate {
+ id
+ subject
+ subjectKeyIdentifier
+ x509Issuer {
+ id
+ subject
+ subjectKeyIdentifier
+ }
+ }
+ }
+ ... on SshSignature {
+ verificationStatus
+ keyFingerprintSha256
+ }
+ }
pipelines(ref: $ref, first: 1) {
__typename
edges {
diff --git a/app/graphql/resolvers/achievements/achievements_resolver.rb b/app/graphql/resolvers/achievements/achievements_resolver.rb
new file mode 100644
index 00000000000..eb3f6eaf92e
--- /dev/null
+++ b/app/graphql/resolvers/achievements/achievements_resolver.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Achievements
+ class AchievementsResolver < BaseResolver
+ include LooksAhead
+
+ type ::Types::Achievements::AchievementType.connection_type, null: true
+
+ argument :ids, [::Types::GlobalIDType[::Achievements::Achievement]],
+ required: false,
+ description: 'Filter achievements by IDs.'
+
+ alias_method :namespace, :object
+
+ def resolve_with_lookahead(**args)
+ return ::Achievements::Achievement.none if Feature.disabled?(:achievements, namespace)
+
+ params = {}
+ params[:ids] = args[:ids].map(&:model_id) if args[:ids].present?
+
+ achievements = ::Achievements::AchievementsFinder.new(namespace, params).execute
+ apply_lookahead(achievements)
+ end
+
+ private
+
+ def preloads
+ {
+ user_achievements: [{ user_achievements: [:user, :awarded_by_user, :revoked_by_user] }]
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/achievements/user_achievements_resolver.rb b/app/graphql/resolvers/achievements/user_achievements_resolver.rb
new file mode 100644
index 00000000000..77fb15c3d93
--- /dev/null
+++ b/app/graphql/resolvers/achievements/user_achievements_resolver.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Achievements
+ class UserAchievementsResolver < BaseResolver
+ include LooksAhead
+
+ type ::Types::Achievements::UserAchievementType.connection_type, null: true
+
+ def resolve_with_lookahead
+ user_achievements = object.user_achievements.not_revoked.order_by_id_asc
+
+ apply_lookahead(user_achievements)
+ end
+
+ private
+
+ def unconditional_includes
+ [
+ { achievement: [:namespace] }
+ ]
+ end
+
+ def preloads
+ {
+ user: [:user],
+ awarded_by_user: [:awarded_by_user],
+ revoked_by_user: [:revoked_by_user]
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/analytics/cycle_analytics/base_count_resolver.rb b/app/graphql/resolvers/analytics/cycle_analytics/base_count_resolver.rb
new file mode 100644
index 00000000000..82d38ff89d9
--- /dev/null
+++ b/app/graphql/resolvers/analytics/cycle_analytics/base_count_resolver.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Analytics
+ module CycleAnalytics
+ class BaseCountResolver < BaseResolver
+ type Types::Analytics::CycleAnalytics::MetricType, null: true
+
+ argument :from, Types::TimeType,
+ required: true,
+ description: 'Timestamp marking the start date and time.'
+
+ argument :to, Types::TimeType,
+ required: true,
+ description: 'Timestamp marking the end date and time.'
+
+ def ready?(**args)
+ start_date = args[:from]
+ end_date = args[:to]
+
+ if start_date >= end_date
+ raise Gitlab::Graphql::Errors::ArgumentError,
+ '`from` argument must be before `to` argument'
+ end
+
+ max_days = Gitlab::Analytics::CycleAnalytics::RequestParams::MAX_RANGE_DAYS
+
+ if (end_date.beginning_of_day - start_date.beginning_of_day) > max_days
+ raise Gitlab::Graphql::Errors::ArgumentError,
+ "Max of #{max_days.inspect} timespan is allowed"
+ end
+
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/analytics/cycle_analytics/base_issue_resolver.rb b/app/graphql/resolvers/analytics/cycle_analytics/base_issue_resolver.rb
new file mode 100644
index 00000000000..8128023aecb
--- /dev/null
+++ b/app/graphql/resolvers/analytics/cycle_analytics/base_issue_resolver.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Analytics
+ module CycleAnalytics
+ class BaseIssueResolver < BaseCountResolver
+ type Types::Analytics::CycleAnalytics::MetricType, null: true
+
+ argument :assignee_usernames, [GraphQL::Types::String],
+ required: false,
+ description: 'Usernames of users assigned to the issue.'
+
+ argument :author_username, GraphQL::Types::String,
+ required: false,
+ description: 'Username of the author of the issue.'
+
+ argument :milestone_title, GraphQL::Types::String,
+ required: false,
+ description: 'Milestone applied to the issue.'
+
+ argument :label_names, [GraphQL::Types::String],
+ required: false,
+ description: 'Labels applied to the issue.'
+
+ def finder_params
+ { project_id: object.project.id }
+ end
+
+ # :project level: no customization, returning the original resolver
+ # :group level: add the project_ids argument
+ def self.[](context = :project)
+ case context
+ when :project
+ self
+ when :group
+ Class.new(self) do
+ argument :project_ids, [GraphQL::Types::ID],
+ required: false,
+ description: 'Project IDs within the group hierarchy.'
+
+ define_method :finder_params do
+ { group_id: object.id, include_subgroups: true }
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/analytics/cycle_analytics/deployment_count_resolver.rb b/app/graphql/resolvers/analytics/cycle_analytics/deployment_count_resolver.rb
new file mode 100644
index 00000000000..51a1afdd5ab
--- /dev/null
+++ b/app/graphql/resolvers/analytics/cycle_analytics/deployment_count_resolver.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+# rubocop:disable Graphql/ResolverType (inherited from Resolvers::Analytics::CycleAnalytics::BaseCountResolver)
+module Resolvers
+ module Analytics
+ module CycleAnalytics
+ class DeploymentCountResolver < BaseCountResolver
+ def resolve(**args)
+ value = count(args)
+ {
+ value: value,
+ title: n_('Deploy', 'Deploys', value.to_i),
+ identifier: 'deploys',
+ links: []
+ }
+ end
+
+ private
+
+ def count(args)
+ finder = DeploymentsFinder.new({
+ finished_after: args[:from],
+ finished_before: args[:to],
+ project: object.project,
+ status: :success,
+ order_by: :finished_at
+ })
+
+ finder.execute.count
+ end
+
+ # :project level: no customization, returning the original resolver
+ # :group level: add the project_ids argument
+ def self.[](context = :project)
+ case context
+ when :project
+ self
+ when :group
+ Class.new(self) do
+ argument :project_ids, [GraphQL::Types::ID],
+ required: false,
+ description: 'Project IDs within the group hierarchy.'
+ end
+
+ end
+ end
+ end
+ end
+ end
+end
+# rubocop:enable Graphql/ResolverType
+
+mod = Resolvers::Analytics::CycleAnalytics::DeploymentCountResolver
+mod.prepend_mod_with('Resolvers::Analytics::CycleAnalytics::DeploymentCountResolver')
diff --git a/app/graphql/resolvers/analytics/cycle_analytics/issue_count_resolver.rb b/app/graphql/resolvers/analytics/cycle_analytics/issue_count_resolver.rb
new file mode 100644
index 00000000000..fd20800ee16
--- /dev/null
+++ b/app/graphql/resolvers/analytics/cycle_analytics/issue_count_resolver.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+# rubocop:disable Graphql/ResolverType (inherited from Resolvers::Analytics::CycleAnalytics::BaseIssueResolver)
+module Resolvers
+ module Analytics
+ module CycleAnalytics
+ class IssueCountResolver < BaseIssueResolver
+ def resolve(**args)
+ value = IssuesFinder
+ .new(current_user, process_params(args))
+ .execute
+ .count
+
+ {
+ value: value,
+ title: n_('New Issue', 'New Issues', value),
+ identifier: 'issues',
+ links: []
+ }
+ end
+
+ private
+
+ def process_params(params)
+ params[:assignee_username] = params.delete(:assignee_usernames) if params[:assignee_usernames]
+ params[:label_name] = params.delete(:label_names) if params[:label_names]
+ params[:created_after] = params.delete(:from)
+ params[:created_before] = params.delete(:to)
+ params[:projects] = params[:project_ids] if params[:project_ids]
+
+ params.merge(finder_params)
+ end
+ end
+ end
+ end
+end
+# rubocop:enable Graphql/ResolverType
diff --git a/app/graphql/resolvers/award_emoji/base_votes_count_resolver.rb b/app/graphql/resolvers/award_emoji/base_votes_count_resolver.rb
new file mode 100644
index 00000000000..406c52eb0d5
--- /dev/null
+++ b/app/graphql/resolvers/award_emoji/base_votes_count_resolver.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module AwardEmoji
+ class BaseVotesCountResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ type GraphQL::Types::Int, null: true
+
+ private
+
+ def authorized_resource?(object)
+ Ability.allowed?(current_user, "read_#{object.to_ability_name}".to_sym, object)
+ end
+
+ def votes_batch_loader
+ BatchLoaders::AwardEmojiVotesBatchLoader
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/blobs_resolver.rb b/app/graphql/resolvers/blobs_resolver.rb
index fb5fa4465f9..0b8180dbce7 100644
--- a/app/graphql/resolvers/blobs_resolver.rb
+++ b/app/graphql/resolvers/blobs_resolver.rb
@@ -38,7 +38,7 @@ module Resolvers
private
def validate_ref(ref)
- unless Gitlab::GitRefValidator.validate(ref)
+ unless Gitlab::GitRefValidator.validate(ref, skip_head_ref_check: true)
raise Gitlab::Graphql::Errors::ArgumentError, 'Ref is not valid'
end
end
diff --git a/app/graphql/resolvers/ci/all_jobs_resolver.rb b/app/graphql/resolvers/ci/all_jobs_resolver.rb
index d918bed9f57..5d0193e0e1c 100644
--- a/app/graphql/resolvers/ci/all_jobs_resolver.rb
+++ b/app/graphql/resolvers/ci/all_jobs_resolver.rb
@@ -3,14 +3,37 @@
module Resolvers
module Ci
class AllJobsResolver < BaseResolver
+ include LooksAhead
+
type ::Types::Ci::JobType.connection_type, null: true
argument :statuses, [::Types::Ci::JobStatusEnum],
required: false,
description: 'Filter jobs by status.'
- def resolve(statuses: nil)
- ::Ci::JobsFinder.new(current_user: current_user, params: { scope: statuses }).execute
+ def resolve_with_lookahead(statuses: nil)
+ jobs = ::Ci::JobsFinder.new(current_user: current_user, params: { scope: statuses }).execute
+
+ apply_lookahead(jobs)
+ end
+
+ private
+
+ def preloads
+ {
+ previous_stage_jobs_or_needs: [:needs, :pipeline],
+ artifacts: [:job_artifacts],
+ pipeline: [:user],
+ kind: [:metadata],
+ retryable: [:metadata],
+ project: [{ project: [:route, { namespace: [:route] }] }],
+ commit_path: [:pipeline, { project: { namespace: [:route] } }],
+ ref_path: [{ project: [:route, { namespace: [:route] }] }],
+ browse_artifacts_path: [{ project: { namespace: [:route] } }],
+ play_path: [{ project: { namespace: [:route] } }],
+ web_path: [{ project: { namespace: [:route] } }],
+ tags: [:tags]
+ }
end
end
end
diff --git a/app/graphql/resolvers/ci/inherited_variables_resolver.rb b/app/graphql/resolvers/ci/inherited_variables_resolver.rb
new file mode 100644
index 00000000000..01f966942a4
--- /dev/null
+++ b/app/graphql/resolvers/ci/inherited_variables_resolver.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Ci
+ class InheritedVariablesResolver < BaseResolver
+ type Types::Ci::ProjectVariableType.connection_type, null: true
+
+ def resolve
+ object.group&.self_and_ancestors&.flat_map(&:variables) || []
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/ci/jobs_resolver.rb b/app/graphql/resolvers/ci/jobs_resolver.rb
index 31cc350f331..95816a1ac1a 100644
--- a/app/graphql/resolvers/ci/jobs_resolver.rb
+++ b/app/graphql/resolvers/ci/jobs_resolver.rb
@@ -23,12 +23,17 @@ module Resolvers
required: false,
description: 'Filter jobs by when they are executed.'
- def resolve(statuses: nil, security_report_types: [], retried: nil, when_executed: nil)
+ argument :job_kind, ::Types::Ci::JobKindEnum,
+ required: false,
+ description: 'Filter jobs by kind.'
+
+ def resolve(statuses: nil, security_report_types: [], retried: nil, when_executed: nil, job_kind: nil)
jobs = init_collection(security_report_types)
jobs = jobs.with_status(statuses) if statuses.present?
jobs = jobs.retried if retried
jobs = jobs.with_when_executed(when_executed) if when_executed.present?
jobs = jobs.latest if retried == false
+ jobs = jobs.with_type(job_kind) if job_kind
jobs
end
diff --git a/app/graphql/resolvers/ci/pipeline_job_artifacts_resolver.rb b/app/graphql/resolvers/ci/pipeline_job_artifacts_resolver.rb
index 35d30827561..561c61e3b27 100644
--- a/app/graphql/resolvers/ci/pipeline_job_artifacts_resolver.rb
+++ b/app/graphql/resolvers/ci/pipeline_job_artifacts_resolver.rb
@@ -15,7 +15,7 @@ module Resolvers
def find_job_artifacts
BatchLoader::GraphQL.for(pipeline).batch do |pipelines, loader|
- ActiveRecord::Associations::Preloader.new.preload(pipelines, :job_artifacts) # rubocop: disable CodeReuse/ActiveRecord
+ ActiveRecord::Associations::Preloader.new(records: pipelines, associations: :job_artifacts).call # rubocop: disable CodeReuse/ActiveRecord
pipelines.each { |pl| loader.call(pl, pl.job_artifacts) }
end
diff --git a/app/graphql/resolvers/ci/runner_jobs_resolver.rb b/app/graphql/resolvers/ci/runner_jobs_resolver.rb
index 467a3525867..9fe25a4d13d 100644
--- a/app/graphql/resolvers/ci/runner_jobs_resolver.rb
+++ b/app/graphql/resolvers/ci/runner_jobs_resolver.rb
@@ -36,7 +36,11 @@ module Resolvers
{ pipeline: [:merge_request] },
{ project: [:route, { namespace: :route }] }
],
- commit_path: [:pipeline, { project: [:route, { namespace: [:route] }] }],
+ commit_path: [:pipeline, { project: { namespace: [:route] } }],
+ ref_path: [{ project: [:route, { namespace: [:route] }] }],
+ browse_artifacts_path: [{ project: { namespace: [:route] } }],
+ play_path: [{ project: { namespace: [:route] } }],
+ web_path: [{ project: { namespace: [:route] } }],
short_sha: [:pipeline],
tags: [:tags]
}
diff --git a/app/graphql/resolvers/ci/runner_projects_resolver.rb b/app/graphql/resolvers/ci/runner_projects_resolver.rb
index 2a2d63f85de..c5037965e20 100644
--- a/app/graphql/resolvers/ci/runner_projects_resolver.rb
+++ b/app/graphql/resolvers/ci/runner_projects_resolver.rb
@@ -15,10 +15,10 @@ module Resolvers
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
+ default_value: 'id_asc', # TODO: Remove in %17.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',
+ reason: 'Default sort order will change in GitLab 17.0. ' \
+ 'Specify `"id_asc"` if you require the query results to be ordered by ascending IDs',
milestone: '15.4'
},
description: "Sort order of results. Format: `<field_name>_<sort_direction>`, " \
@@ -34,25 +34,30 @@ module Resolvers
.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
+ unique_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)
+ project_ids_relation: unique_project_ids)
.execute
projects = apply_lookahead(projects)
Preloaders::ProjectPolicyPreloader.new(projects, current_user).execute
+ sorted_project_ids = projects.map(&:id)
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 =
+ 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] } }
+ .transform_values { |runner_id_and_project_id| runner_id_and_project_id.map(&:second) }
+ # Reorder the project IDs according to the order in sorted_project_ids
+ sorted_project_ids_by_runner_id =
+ project_ids_by_runner_id.transform_values { |project_ids| sorted_project_ids.intersection(project_ids) }
runner_ids.each do |runner_id|
- runner_projects = runner_project_ids_by_runner_id[runner_id] || []
+ runner_project_ids = sorted_project_ids_by_runner_id[runner_id] || []
+ runner_projects = runner_project_ids.map { |id| projects_by_id[id] }
loader.call(runner_id, runner_projects)
end
@@ -68,9 +73,9 @@ module Resolvers
def preloads
super.merge({
- full_path: [:route, { namespace: [:route] }],
- web_url: [:route, { namespace: [:route] }]
- })
+ full_path: [:route, { namespace: [:route] }],
+ web_url: [:route, { namespace: [:route] }]
+ })
end
end
end
diff --git a/app/graphql/resolvers/ci/runner_resolver.rb b/app/graphql/resolvers/ci/runner_resolver.rb
index ca94e28b2e9..4250b069d20 100644
--- a/app/graphql/resolvers/ci/runner_resolver.rb
+++ b/app/graphql/resolvers/ci/runner_resolver.rb
@@ -22,11 +22,15 @@ module Resolvers
def find_runner(id:)
runner_id = GitlabSchema.parse_gid(id, expected_type: ::Ci::Runner).model_id.to_i
- preload_tag_list = lookahead.selects?(:tag_list)
+ key = {
+ preload_tag_list: lookahead.selects?(:tag_list),
+ preload_creator: lookahead.selects?(:created_by)
+ }
- BatchLoader::GraphQL.for(runner_id).batch(key: { preload_tag_list: preload_tag_list }) do |ids, loader, batch|
+ BatchLoader::GraphQL.for(runner_id).batch(key: key) do |ids, loader, batch|
results = ::Ci::Runner.id_in(ids)
results = results.with_tags if batch[:key][:preload_tag_list]
+ results = results.with_creator if batch[:key][:preload_creator]
results.each { |record| loader.call(record.id, record) }
end
diff --git a/app/graphql/resolvers/ci/runner_status_resolver.rb b/app/graphql/resolvers/ci/runner_status_resolver.rb
index 447ab306ba7..c8f47b16f75 100644
--- a/app/graphql/resolvers/ci/runner_status_resolver.rb
+++ b/app/graphql/resolvers/ci/runner_status_resolver.rb
@@ -12,17 +12,16 @@ module Resolvers
argument :legacy_mode,
type: GraphQL::Types::String,
- default_value: '14.5',
+ default_value: nil,
required: false,
- description: 'Compatibility mode. A null value turns off compatibility mode.',
+ description: 'No-op, left for compatibility.',
deprecated: {
- reason: 'Will be removed in 17.0. In GitLab 16.0 and later, ' \
- 'the field will act as if `legacyMode` is null',
+ reason: 'Will be removed in 17.0',
milestone: '15.0'
}
- def resolve(legacy_mode:, **args)
- runner.status(legacy_mode)
+ def resolve(**args)
+ runner.status
end
end
end
diff --git a/app/graphql/resolvers/ci/runners_resolver.rb b/app/graphql/resolvers/ci/runners_resolver.rb
index b52a4cc0ab4..735e38c1a5c 100644
--- a/app/graphql/resolvers/ci/runners_resolver.rb
+++ b/app/graphql/resolvers/ci/runners_resolver.rb
@@ -61,9 +61,7 @@ module Resolvers
upgrade_status: params[:upgrade_status],
search: params[:search],
sort: params[:sort]&.to_s,
- preload: {
- tag_name: node_selection&.selects?(:tag_list)
- }
+ preload: false # we'll handle preloading ourselves
}.compact
.merge(parent_param)
end
@@ -79,6 +77,31 @@ module Resolvers
def parent
object.respond_to?(:sync) ? object.sync : object
end
+
+ def preloads
+ super.merge({
+ created_by: [:creator],
+ tag_list: [:tags]
+ })
+ end
+
+ def nested_preloads
+ {
+ created_by: {
+ creator: {
+ full_path: [:route],
+ web_path: [:route],
+ web_url: [:route]
+ }
+ },
+ owner_project: {
+ owner_project: {
+ 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 b7355a1752e..0b9422db2a9 100644
--- a/app/graphql/resolvers/clusters/agent_tokens_resolver.rb
+++ b/app/graphql/resolvers/clusters/agent_tokens_resolver.rb
@@ -9,12 +9,8 @@ module Resolvers
delegate :project, to: :agent
- argument :status, Types::Clusters::AgentTokenStatusEnum,
- required: false,
- description: 'Status of the token.'
-
- def resolve(**args)
- ::Clusters::AgentTokensFinder.new(agent, current_user, args).execute
+ def resolve(**_args)
+ ::Clusters::AgentTokensFinder.new(agent, current_user, status: :active).execute
end
end
end
diff --git a/app/graphql/resolvers/clusters/agents/authorizations/ci_access_resolver.rb b/app/graphql/resolvers/clusters/agents/authorizations/ci_access_resolver.rb
new file mode 100644
index 00000000000..c36338439e6
--- /dev/null
+++ b/app/graphql/resolvers/clusters/agents/authorizations/ci_access_resolver.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Clusters
+ module Agents
+ module Authorizations
+ class CiAccessResolver < BaseResolver
+ type Types::Clusters::Agents::Authorizations::CiAccessType, null: true
+
+ alias_method :project, :object
+
+ def resolve(*)
+ ::Clusters::Agents::Authorizations::CiAccess::Finder.new(project).execute
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/clusters/agents/authorizations/user_access_resolver.rb b/app/graphql/resolvers/clusters/agents/authorizations/user_access_resolver.rb
new file mode 100644
index 00000000000..280db570aa6
--- /dev/null
+++ b/app/graphql/resolvers/clusters/agents/authorizations/user_access_resolver.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Clusters
+ module Agents
+ module Authorizations
+ class UserAccessResolver < BaseResolver
+ type Types::Clusters::Agents::Authorizations::UserAccessType, null: true
+
+ alias_method :project, :object
+
+ def resolve(*)
+ ::Clusters::Agents::Authorizations::UserAccess::Finder.new(current_user, project: project).execute
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/clusters/agents_resolver.rb b/app/graphql/resolvers/clusters/agents_resolver.rb
index 0b9eb361dbd..f138ad2b510 100644
--- a/app/graphql/resolvers/clusters/agents_resolver.rb
+++ b/app/graphql/resolvers/clusters/agents_resolver.rb
@@ -28,7 +28,7 @@ module Resolvers
def preloads
{
activity_events: { activity_events: [:user, agent_token: :agent] },
- tokens: :agent_tokens
+ tokens: :active_agent_tokens
}
end
end
diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
index c68e120ee24..b9326015ac0 100644
--- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb
+++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
@@ -40,6 +40,7 @@ module ResolvesMergeRequests
def preloads
{
assignees: [:assignees],
+ award_emoji: { award_emoji: [:awardable] },
reviewers: [:reviewers],
participants: MergeRequest.participant_includes,
author: [:author],
diff --git a/app/graphql/resolvers/concerns/time_frame_arguments.rb b/app/graphql/resolvers/concerns/time_frame_arguments.rb
index 87b7a96045c..c26898bb2f1 100644
--- a/app/graphql/resolvers/concerns/time_frame_arguments.rb
+++ b/app/graphql/resolvers/concerns/time_frame_arguments.rb
@@ -3,51 +3,15 @@
module TimeFrameArguments
extend ActiveSupport::Concern
- OVERLAPPING_TIMEFRAME_DESC = 'List items overlapping a time frame defined by startDate..endDate (if one date is provided, both must be present)'
-
included do
- argument :start_date, Types::TimeType,
- required: false,
- description: OVERLAPPING_TIMEFRAME_DESC,
- deprecated: { reason: 'Use timeframe.start', milestone: '13.5' }
-
- argument :end_date, Types::TimeType,
- required: false,
- description: OVERLAPPING_TIMEFRAME_DESC,
- deprecated: { reason: 'Use timeframe.end', milestone: '13.5' }
-
argument :timeframe, Types::TimeframeInputType,
required: false,
description: 'List items overlapping the given timeframe.'
end
- # TODO: remove when the start_date and end_date arguments are removed
- def validate_timeframe_params!(args)
- return unless %i[start_date end_date timeframe].any? { |k| args[k].present? }
-
- # the timeframe is passed in as a TimeframeInputType
- timeframe = args[:timeframe].to_h if args[:timeframe]
- return if timeframe && %i[start_date end_date].all? { |k| args[k].nil? }
-
- error_message =
- if timeframe.present?
- "startDate and endDate are deprecated in favor of timeframe. Please use only timeframe."
- elsif args[:start_date].nil? || args[:end_date].nil?
- "Both startDate and endDate must be present."
- elsif args[:start_date] > args[:end_date]
- "startDate is after endDate"
- end
-
- if error_message
- raise Gitlab::Graphql::Errors::ArgumentError, error_message
- end
- end
-
def transform_timeframe_parameters(args)
- if args[:timeframe]
- args[:timeframe].to_h.transform_keys { |k| :"#{k}_date" }
- else
- args.slice(:start_date, :end_date)
- end
+ return {} unless args[:timeframe]
+
+ args[:timeframe].to_h.transform_keys { |k| :"#{k}_date" }
end
end
diff --git a/app/graphql/resolvers/concerns/work_items/shared_filter_arguments.rb b/app/graphql/resolvers/concerns/work_items/shared_filter_arguments.rb
new file mode 100644
index 00000000000..ecb105a64d0
--- /dev/null
+++ b/app/graphql/resolvers/concerns/work_items/shared_filter_arguments.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module WorkItems
+ module SharedFilterArguments
+ extend ActiveSupport::Concern
+
+ included do
+ argument :author_username,
+ GraphQL::Types::String,
+ required: false,
+ description: 'Filter work items by author username.',
+ alpha: { milestone: '15.9' }
+ argument :iids,
+ [GraphQL::Types::String],
+ required: false,
+ description: 'List of IIDs of work items. For example, `["1", "2"]`.'
+ argument :state,
+ Types::IssuableStateEnum,
+ required: false,
+ description: 'Current state of the work item.'
+ argument :types,
+ [Types::IssueTypeEnum],
+ as: :issue_types,
+ description: 'Filter work items by the given work item types.',
+ required: false
+ end
+ end
+end
diff --git a/app/graphql/resolvers/data_transfer/data_transfer_arguments.rb b/app/graphql/resolvers/data_transfer/data_transfer_arguments.rb
new file mode 100644
index 00000000000..da75a78b2ac
--- /dev/null
+++ b/app/graphql/resolvers/data_transfer/data_transfer_arguments.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module DataTransfer
+ module DataTransferArguments
+ extend ActiveSupport::Concern
+
+ included do
+ argument :from, Types::DateType,
+ description:
+ 'Retain egress data for one year. Data for the current month will increase dynamically as egress occurs.',
+ required: false
+ argument :to, Types::DateType,
+ description: 'End date for the data.',
+ required: false
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/data_transfer/group_data_transfer_resolver.rb b/app/graphql/resolvers/data_transfer/group_data_transfer_resolver.rb
new file mode 100644
index 00000000000..83bb144017c
--- /dev/null
+++ b/app/graphql/resolvers/data_transfer/group_data_transfer_resolver.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module DataTransfer
+ class GroupDataTransferResolver < BaseResolver
+ include DataTransferArguments
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ authorizes_object!
+ authorize :read_usage_quotas
+
+ type Types::DataTransfer::GroupDataTransferType, null: false
+
+ alias_method :group, :object
+
+ def resolve(**args)
+ return { egress_nodes: [] } unless Feature.enabled?(:data_transfer_monitoring, group)
+
+ results = if Feature.enabled?(:data_transfer_monitoring_mock_data, group)
+ ::DataTransfer::MockedTransferFinder.new.execute
+ else
+ ::DataTransfer::GroupDataTransferFinder.new(
+ group: group,
+ from: args[:from],
+ to: args[:to],
+ user: current_user
+ ).execute.map(&:attributes)
+ end
+
+ { egress_nodes: results.to_a }
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/data_transfer/project_data_transfer_resolver.rb b/app/graphql/resolvers/data_transfer/project_data_transfer_resolver.rb
new file mode 100644
index 00000000000..c3296f7d4c3
--- /dev/null
+++ b/app/graphql/resolvers/data_transfer/project_data_transfer_resolver.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module DataTransfer
+ class ProjectDataTransferResolver < BaseResolver
+ include DataTransferArguments
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ authorizes_object!
+ authorize :read_usage_quotas
+
+ type Types::DataTransfer::ProjectDataTransferType, null: false
+
+ alias_method :project, :object
+
+ def resolve(**args)
+ return { egress_nodes: [] } unless Feature.enabled?(:data_transfer_monitoring, project.group)
+
+ results = if Feature.enabled?(:data_transfer_monitoring_mock_data, project.group)
+ ::DataTransfer::MockedTransferFinder.new.execute
+ else
+ ::DataTransfer::ProjectDataTransferFinder.new(
+ project: project,
+ from: args[:from],
+ to: args[:to],
+ user: current_user
+ ).execute
+ end
+
+ { egress_nodes: results }
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/data_transfer_resolver.rb b/app/graphql/resolvers/data_transfer_resolver.rb
deleted file mode 100644
index 1a240d2811f..00000000000
--- a/app/graphql/resolvers/data_transfer_resolver.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-# frozen_string_literal: true
-
-module Resolvers
- class DataTransferResolver < BaseResolver
- argument :from, Types::DateType,
- description: 'Retain egress data for 1 year. Current month will increase dynamically as egress occurs.',
- required: false
- argument :to, Types::DateType,
- description: 'End date for the data.',
- required: false
-
- type ::Types::DataTransfer::BaseType, null: false
-
- def self.source
- raise NotImplementedError
- end
-
- def self.project
- Class.new(self) do
- type Types::DataTransfer::ProjectDataTransferType, null: false
-
- def self.source
- "Project"
- end
- end
- end
-
- def self.group
- Class.new(self) do
- type Types::DataTransfer::GroupDataTransferType, null: false
-
- def self.source
- "Group"
- end
- end
- end
-
- def resolve(**_args)
- return unless Feature.enabled?(:data_transfer_monitoring)
-
- start_date = Date.new(2023, 0o1, 0o1)
- date_for_index = ->(i) { (start_date + i.months).strftime('%Y-%m-%d') }
-
- nodes = 0.upto(3).map do |i|
- {
- date: date_for_index.call(i),
- repository_egress: 250_000,
- artifacts_egress: 250_000,
- packages_egress: 250_000,
- registry_egress: 250_000
- }
- end
-
- { egress_nodes: nodes }
- end
- end
-end
diff --git a/app/graphql/resolvers/design_management/version_resolver.rb b/app/graphql/resolvers/design_management/version_resolver.rb
index 7895981d67c..0d2479ded40 100644
--- a/app/graphql/resolvers/design_management/version_resolver.rb
+++ b/app/graphql/resolvers/design_management/version_resolver.rb
@@ -16,10 +16,6 @@ module Resolvers
def resolve(id:)
authorized_find!(id: id)
end
-
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
end
end
end
diff --git a/app/graphql/resolvers/down_votes_count_resolver.rb b/app/graphql/resolvers/down_votes_count_resolver.rb
index 0e7772f988a..5f5340578cd 100644
--- a/app/graphql/resolvers/down_votes_count_resolver.rb
+++ b/app/graphql/resolvers/down_votes_count_resolver.rb
@@ -1,15 +1,12 @@
# frozen_string_literal: true
module Resolvers
- class DownVotesCountResolver < BaseResolver
- include Gitlab::Graphql::Authorize::AuthorizeResource
- include BatchLoaders::AwardEmojiVotesBatchLoader
-
+ class DownVotesCountResolver < Resolvers::AwardEmoji::BaseVotesCountResolver
type GraphQL::Types::Int, null: true
def resolve
authorize!(object)
- load_votes(object, AwardEmoji::DOWNVOTE_NAME)
+ votes_batch_loader.load_downvotes(object)
end
end
end
diff --git a/app/graphql/resolvers/group_labels_resolver.rb b/app/graphql/resolvers/group_labels_resolver.rb
index a22fa9761d6..b88f62b984a 100644
--- a/app/graphql/resolvers/group_labels_resolver.rb
+++ b/app/graphql/resolvers/group_labels_resolver.rb
@@ -13,5 +13,9 @@ module Resolvers
required: false,
description: 'Include only group level labels.',
default_value: false
+
+ before_connection_authorization do |nodes, current_user|
+ Preloaders::LabelsPreloader.new(nodes, current_user).preload_all
+ end
end
end
diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb
index bbf45efa33e..17e3e159a5b 100644
--- a/app/graphql/resolvers/issues_resolver.rb
+++ b/app/graphql/resolvers/issues_resolver.rb
@@ -2,14 +2,19 @@
module Resolvers
class IssuesResolver < Issues::BaseResolver
+ extend ::Gitlab::Utils::Override
prepend ::Issues::LookAheadPreloads
include ::Issues::SortArguments
- NON_FILTER_ARGUMENTS = %i[sort lookahead].freeze
+ NON_FILTER_ARGUMENTS = %i[sort lookahead include_archived].freeze
+ argument :include_archived, GraphQL::Types::Boolean,
+ required: false,
+ default_value: false,
+ description: 'Whether to include issues from archived projects. Defaults to `false`.'
argument :state, Types::IssuableStateEnum,
- required: false,
- description: 'Current state of this issue.'
+ required: false,
+ description: 'Current state of this issue.'
# see app/graphql/types/issue_connection.rb
type 'Types::IssueConnection', null: true
@@ -44,6 +49,13 @@ module Resolvers
private
+ override :prepare_finder_params
+ def prepare_finder_params(args)
+ super.tap do |prepared|
+ prepared[:non_archived] = !prepared.delete(:include_archived)
+ end
+ end
+
def filter_provided?(args)
args.except(*NON_FILTER_ARGUMENTS).values.any?(&:present?)
end
diff --git a/app/graphql/resolvers/kas/agent_configurations_resolver.rb b/app/graphql/resolvers/kas/agent_configurations_resolver.rb
index 9db104287a6..74c5cbe55f1 100644
--- a/app/graphql/resolvers/kas/agent_configurations_resolver.rb
+++ b/app/graphql/resolvers/kas/agent_configurations_resolver.rb
@@ -21,7 +21,7 @@ module Resolvers
private
def can_read_agent_configuration?
- current_user.can?(:read_cluster, project)
+ current_user.can?(:read_cluster_agent, project)
end
def kas_client
diff --git a/app/graphql/resolvers/labels_resolver.rb b/app/graphql/resolvers/labels_resolver.rb
index f0e099e8fb2..10f5917d84b 100644
--- a/app/graphql/resolvers/labels_resolver.rb
+++ b/app/graphql/resolvers/labels_resolver.rb
@@ -17,6 +17,10 @@ module Resolvers
description: 'Include labels from ancestor groups.',
default_value: false
+ before_connection_authorization do |nodes, current_user|
+ Preloaders::LabelsPreloader.new(nodes, current_user).preload_all
+ end
+
def resolve(**args)
return Label.none if parent.nil?
@@ -24,6 +28,11 @@ module Resolvers
# LabelsFinder uses `search` param, so we transform `search_term` into `search`
args[:search] = args.delete(:search_term)
+
+ # Optimization:
+ # Rely on the LabelsPreloader rather than the default parent record preloading in the
+ # finder because LabelsPreloader preloads more associations which are required for the
+ # permission check.
LabelsFinder.new(current_user, parent_param.merge(args)).execute
end
diff --git a/app/graphql/resolvers/merge_requests_resolver.rb b/app/graphql/resolvers/merge_requests_resolver.rb
index 72372ae6b42..0cdff272ee5 100644
--- a/app/graphql/resolvers/merge_requests_resolver.rb
+++ b/app/graphql/resolvers/merge_requests_resolver.rb
@@ -55,6 +55,13 @@ module Resolvers
required: false,
description: 'Limit result to draft merge requests.'
+ argument :approved, GraphQL::Types::Boolean,
+ required: false,
+ description: <<~DESC
+ Limit results to approved merge requests.
+ Available only when the feature flag `mr_approved_filter` is enabled.
+ DESC
+
argument :created_after, Types::TimeType,
required: false,
description: 'Merge requests created after this timestamp.'
diff --git a/app/graphql/resolvers/metrics/dashboard_resolver.rb b/app/graphql/resolvers/metrics/dashboard_resolver.rb
index d2be9fcdd89..5abad0de539 100644
--- a/app/graphql/resolvers/metrics/dashboard_resolver.rb
+++ b/app/graphql/resolvers/metrics/dashboard_resolver.rb
@@ -15,6 +15,7 @@ module Resolvers
alias_method :environment, :object
def resolve(path:)
+ return if Feature.enabled?(:remove_monitor_metrics)
return unless environment
::PerformanceMonitoring::PrometheusDashboard.find_for(path: path, **service_params)
diff --git a/app/graphql/resolvers/metrics/dashboards/annotation_resolver.rb b/app/graphql/resolvers/metrics/dashboards/annotation_resolver.rb
index 9d6b0486c04..aad9bbebafb 100644
--- a/app/graphql/resolvers/metrics/dashboards/annotation_resolver.rb
+++ b/app/graphql/resolvers/metrics/dashboards/annotation_resolver.rb
@@ -17,6 +17,7 @@ module Resolvers
alias_method :dashboard, :object
def resolve(**args)
+ return if Feature.enabled?(:remove_monitor_metrics)
return [] unless dashboard
::Metrics::Dashboards::AnnotationsFinder.new(dashboard: dashboard, params: args).execute
diff --git a/app/graphql/resolvers/milestones_resolver.rb b/app/graphql/resolvers/milestones_resolver.rb
index 25ff783b408..563c6594665 100644
--- a/app/graphql/resolvers/milestones_resolver.rb
+++ b/app/graphql/resolvers/milestones_resolver.rb
@@ -40,8 +40,6 @@ module Resolvers
NON_STABLE_CURSOR_SORTS = %i[expired_last_due_date_asc expired_last_due_date_desc].freeze
def resolve_with_lookahead(**args)
- validate_timeframe_params!(args)
-
milestones = apply_lookahead(MilestonesFinder.new(milestones_finder_params(args)).execute)
if non_stable_cursor_sort?(args[:sort])
diff --git a/app/graphql/resolvers/notes/synthetic_note_resolver.rb b/app/graphql/resolvers/notes/synthetic_note_resolver.rb
index d4eafcd2c49..619f54d80b4 100644
--- a/app/graphql/resolvers/notes/synthetic_note_resolver.rb
+++ b/app/graphql/resolvers/notes/synthetic_note_resolver.rb
@@ -26,10 +26,6 @@ module Resolvers
synthetic_notes.find { |note| note.discussion_id == sha }
end
-
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
end
end
end
diff --git a/app/graphql/resolvers/paginated_tree_resolver.rb b/app/graphql/resolvers/paginated_tree_resolver.rb
index 6c4e978125e..8fd80b1a9b9 100644
--- a/app/graphql/resolvers/paginated_tree_resolver.rb
+++ b/app/graphql/resolvers/paginated_tree_resolver.rb
@@ -22,7 +22,7 @@ module Resolvers
alias_method :repository, :object
def resolve(**args)
- return unless repository.exists?
+ return if repository.empty?
cursor = args.delete(:after)
args[:ref] ||= :head
diff --git a/app/graphql/resolvers/project_merge_requests_resolver.rb b/app/graphql/resolvers/project_merge_requests_resolver.rb
index 66c020a0c14..6a240541341 100644
--- a/app/graphql/resolvers/project_merge_requests_resolver.rb
+++ b/app/graphql/resolvers/project_merge_requests_resolver.rb
@@ -22,7 +22,13 @@ module Resolvers
def only_count_is_selected_with_merged_at_filter?(args)
return unless lookahead
- argument_names = args.compact.except(:lookahead, :sort, :merged_before, :merged_after).keys
+ # Filter out all elements with blank values. If any of the values are not
+ # scalars, e.g. hashes or array, filter blank values from them and remove
+ # them if the resulting collection is empty.
+ argument_names = args.except(:lookahead, :sort, :merged_before, :merged_after).filter_map do |key, value|
+ value = value.to_hash.compact if value.respond_to?(:to_hash)
+ key if value.present?
+ end
# no extra filtering arguments are provided
return unless argument_names.empty?
diff --git a/app/graphql/resolvers/projects/commit_references_resolver.rb b/app/graphql/resolvers/projects/commit_references_resolver.rb
new file mode 100644
index 00000000000..ca25bad468c
--- /dev/null
+++ b/app/graphql/resolvers/projects/commit_references_resolver.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Projects
+ class CommitReferencesResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ argument :commit_sha, GraphQL::Types::String,
+ required: true,
+ description: 'Project commit SHA identifier. For example, `287774414568010855642518513f085491644061`.'
+
+ authorize :read_commit
+
+ alias_method :project, :object
+
+ calls_gitaly!
+
+ type ::Types::CommitReferencesType, null: true
+
+ def resolve(commit_sha:)
+ authorized_find!(oid: commit_sha)
+ end
+
+ def find_object(oid:)
+ project.repository.commit(oid)
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/projects/fork_details_resolver.rb b/app/graphql/resolvers/projects/fork_details_resolver.rb
index fcc13a1bc1e..620ce395915 100644
--- a/app/graphql/resolvers/projects/fork_details_resolver.rb
+++ b/app/graphql/resolvers/projects/fork_details_resolver.rb
@@ -13,8 +13,15 @@ module Resolvers
def resolve(**args)
return unless project.forked?
+ return unless authorized_fork_source?
- ::Projects::Forks::DivergenceCounts.new(project, args[:ref]).counts
+ ::Projects::Forks::Details.new(project, args[:ref])
+ end
+
+ private
+
+ def authorized_fork_source?
+ Ability.allowed?(current_user, :read_code, project.fork_source)
end
end
end
diff --git a/app/graphql/resolvers/timelog_resolver.rb b/app/graphql/resolvers/timelog_resolver.rb
index dc42a5f38c9..d2b67451698 100644
--- a/app/graphql/resolvers/timelog_resolver.rb
+++ b/app/graphql/resolvers/timelog_resolver.rb
@@ -121,7 +121,7 @@ module Resolvers
def apply_user_filter(timelogs, args)
return timelogs unless args[:username]
- user = UserFinder.new(args[:username]).find_by_username!
+ user = UserFinder.new(args[:username]).find_by_username
timelogs.for_user(user)
end
diff --git a/app/graphql/resolvers/up_votes_count_resolver.rb b/app/graphql/resolvers/up_votes_count_resolver.rb
index 1c78facb694..8b2d705c07a 100644
--- a/app/graphql/resolvers/up_votes_count_resolver.rb
+++ b/app/graphql/resolvers/up_votes_count_resolver.rb
@@ -1,15 +1,12 @@
# frozen_string_literal: true
module Resolvers
- class UpVotesCountResolver < BaseResolver
- include Gitlab::Graphql::Authorize::AuthorizeResource
- include BatchLoaders::AwardEmojiVotesBatchLoader
-
+ class UpVotesCountResolver < Resolvers::AwardEmoji::BaseVotesCountResolver
type GraphQL::Types::Int, null: true
def resolve
authorize!(object)
- load_votes(object, AwardEmoji::UPVOTE_NAME)
+ votes_batch_loader.load_upvotes(object)
end
end
end
diff --git a/app/graphql/resolvers/user_resolver.rb b/app/graphql/resolvers/user_resolver.rb
index f0fd60e9cbb..ddced5ee859 100644
--- a/app/graphql/resolvers/user_resolver.rb
+++ b/app/graphql/resolvers/user_resolver.rb
@@ -39,7 +39,7 @@ module Resolvers
def batch_load(username)
BatchLoader::GraphQL.for(username).batch do |usernames, loader|
User.by_username(usernames).each do |user|
- loader.call(user.username, user)
+ loader.call(username, user)
end
end
end
diff --git a/app/graphql/resolvers/work_item_resolver.rb b/app/graphql/resolvers/work_item_resolver.rb
index b174a0d2693..34e2f329efd 100644
--- a/app/graphql/resolvers/work_item_resolver.rb
+++ b/app/graphql/resolvers/work_item_resolver.rb
@@ -13,11 +13,5 @@ module Resolvers
def resolve(id:)
authorized_find!(id: id)
end
-
- private
-
- def find_object(id:)
- GitlabSchema.find_by_gid(id)
- end
end
end
diff --git a/app/graphql/resolvers/work_items_resolver.rb b/app/graphql/resolvers/work_items_resolver.rb
index 0c9aac80274..14eec4f696a 100644
--- a/app/graphql/resolvers/work_items_resolver.rb
+++ b/app/graphql/resolvers/work_items_resolver.rb
@@ -4,30 +4,19 @@ module Resolvers
class WorkItemsResolver < BaseResolver
include SearchArguments
include LooksAhead
+ include ::WorkItems::SharedFilterArguments
- type Types::WorkItemType.connection_type, null: true
+ argument :iid,
+ GraphQL::Types::String,
+ required: false,
+ description: 'IID of the work item. For example, "1".'
+ argument :sort,
+ Types::WorkItemSortEnum,
+ description: 'Sort work items by criteria.',
+ required: false,
+ default_value: :created_desc
- argument :author_username, GraphQL::Types::String,
- required: false,
- description: 'Filter work items by author username.',
- alpha: { milestone: '15.9' }
- argument :iid, GraphQL::Types::String,
- required: false,
- description: 'IID of the issue. For example, "1".'
- argument :iids, [GraphQL::Types::String],
- required: false,
- description: 'List of IIDs of work items. For example, `["1", "2"]`.'
- argument :sort, Types::WorkItemSortEnum,
- description: 'Sort work items by this criteria.',
- required: false,
- default_value: :created_desc
- argument :state, Types::IssuableStateEnum,
- required: false,
- description: 'Current state of this work item.'
- argument :types, [Types::IssueTypeEnum],
- as: :issue_types,
- description: 'Filter work items by the given work item types.',
- required: false
+ type Types::WorkItemType.connection_type, null: true
def resolve_with_lookahead(**args)
return WorkItem.none if resource_parent.nil?
@@ -42,7 +31,7 @@ module Resolvers
def preloads
{
work_item_type: :work_item_type,
- web_url: { project: { namespace: :route } },
+ web_url: { namespace: :route, project: [:project_namespace, { namespace: :route }] },
widgets: { work_item_type: :enabled_widget_definitions }
}
end
@@ -66,7 +55,9 @@ module Resolvers
parent: :work_item_parent,
children: { work_item_children_by_relative_position: [:author, { project: :project_feature }] },
labels: :labels,
- milestone: { milestone: [:project, :group] }
+ milestone: { milestone: [:project, :group] },
+ subscribed: [:assignees, :award_emoji, { notes: [:author, :award_emoji] }],
+ award_emoji: { award_emoji: :awardable }
}
end
diff --git a/app/graphql/subscriptions/base_subscription.rb b/app/graphql/subscriptions/base_subscription.rb
index 5f7931787df..dcc9fe708d6 100644
--- a/app/graphql/subscriptions/base_subscription.rb
+++ b/app/graphql/subscriptions/base_subscription.rb
@@ -12,6 +12,18 @@ module Subscriptions
current_user.reset if current_user
end
+ # We override graphql-ruby's default `subscribe` since it returns
+ # :no_response instead, which leads to empty hashes rendered out
+ # to the caller which has caused problems in the client.
+ #
+ # Eventually, we should move to an approach where the caller receives
+ # a response here upon subscribing, but we don't need this currently
+ # because Vue components also perform an initial fetch query.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/402614
+ def subscribe(*)
+ nil
+ end
+
def authorized?(*)
raise NotImplementedError
end
diff --git a/app/graphql/subscriptions/issuable_updated.rb b/app/graphql/subscriptions/issuable_updated.rb
index ad78fd4b4a1..63fe81bbc32 100644
--- a/app/graphql/subscriptions/issuable_updated.rb
+++ b/app/graphql/subscriptions/issuable_updated.rb
@@ -10,10 +10,6 @@ module Subscriptions
required: true,
description: 'ID of the issuable.'
- def subscribe(issuable_id:)
- nil
- end
-
def authorized?(issuable_id:)
issuable = force(GitlabSchema.find_by_gid(issuable_id))
diff --git a/app/graphql/subscriptions/notes/base.rb b/app/graphql/subscriptions/notes/base.rb
index 3653c01e0e2..c117dc295f2 100644
--- a/app/graphql/subscriptions/notes/base.rb
+++ b/app/graphql/subscriptions/notes/base.rb
@@ -9,10 +9,6 @@ module Subscriptions
required: false,
description: 'ID of the noteable.'
- def subscribe(*args)
- nil
- end
-
def authorized?(noteable_id:)
noteable = force(GitlabSchema.find_by_gid(noteable_id))
diff --git a/app/graphql/types/achievements/achievement_type.rb b/app/graphql/types/achievements/achievement_type.rb
index 67cc9778797..ec558981465 100644
--- a/app/graphql/types/achievements/achievement_type.rb
+++ b/app/graphql/types/achievements/achievement_type.rb
@@ -14,7 +14,6 @@ module Types
field :namespace,
::Types::NamespaceType,
- null: false,
description: 'Namespace of the achievement.'
field :name,
@@ -42,6 +41,14 @@ module Types
null: false,
description: 'Timestamp the achievement was last updated.'
+ field :user_achievements,
+ Types::Achievements::UserAchievementType.connection_type,
+ null: true,
+ alpha: { milestone: '15.10' },
+ description: "Recipients for the achievement.",
+ extras: [:lookahead],
+ resolver: ::Resolvers::Achievements::UserAchievementsResolver
+
def avatar_url
object.avatar_url(only_path: false)
end
diff --git a/app/graphql/types/achievements/user_achievement_type.rb b/app/graphql/types/achievements/user_achievement_type.rb
new file mode 100644
index 00000000000..bf161d2f1e5
--- /dev/null
+++ b/app/graphql/types/achievements/user_achievement_type.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module Types
+ module Achievements
+ class UserAchievementType < BaseObject
+ graphql_name 'UserAchievement'
+
+ authorize :read_user_achievement
+
+ field :id,
+ ::Types::GlobalIDType[::Achievements::UserAchievement],
+ null: false,
+ description: 'ID of the user achievement.'
+
+ field :achievement,
+ ::Types::Achievements::AchievementType,
+ null: false,
+ description: 'Achievement awarded.'
+
+ field :user,
+ ::Types::UserType,
+ null: false,
+ description: 'Achievement recipient.'
+
+ field :awarded_by_user,
+ ::Types::UserType,
+ null: false,
+ description: 'Awarded by.'
+
+ field :revoked_by_user,
+ ::Types::UserType,
+ null: true,
+ description: 'Revoked by.'
+
+ field :created_at,
+ Types::TimeType,
+ null: false,
+ description: 'Timestamp the achievement was created.'
+
+ field :updated_at,
+ Types::TimeType,
+ null: false,
+ description: 'Timestamp the achievement was last updated.'
+
+ field :revoked_at,
+ Types::TimeType,
+ null: true,
+ description: 'Timestamp the achievement was revoked.'
+ end
+ end
+end
diff --git a/app/graphql/types/alert_management/alert_type.rb b/app/graphql/types/alert_management/alert_type.rb
index a13453f9194..5784c7a4872 100644
--- a/app/graphql/types/alert_management/alert_type.rb
+++ b/app/graphql/types/alert_management/alert_type.rb
@@ -114,7 +114,9 @@ module Types
field :metrics_dashboard_url,
GraphQL::Types::String,
null: true,
- description: 'URL for metrics embed for the alert.'
+ description: 'URL for metrics embed for the alert.',
+ deprecated: { reason: 'Returns no data. Underlying feature was removed in 16.0',
+ milestone: '16.0' }
field :runbook,
GraphQL::Types::String,
@@ -145,6 +147,12 @@ module Types
def notes
object.ordered_notes
end
+
+ def metrics_dashboard_url
+ return if Feature.enabled?(:remove_monitor_metrics)
+
+ object.metrics_dashboard_url
+ end
end
end
end
diff --git a/app/graphql/types/analytics/cycle_analytics/flow_metrics.rb b/app/graphql/types/analytics/cycle_analytics/flow_metrics.rb
new file mode 100644
index 00000000000..c9a28767e11
--- /dev/null
+++ b/app/graphql/types/analytics/cycle_analytics/flow_metrics.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Types
+ module Analytics
+ module CycleAnalytics
+ module FlowMetrics
+ def self.[](context = :project)
+ Class.new(BaseObject) do
+ graphql_name "#{context.capitalize}ValueStreamAnalyticsFlowMetrics"
+ description 'Exposes aggregated value stream flow metrics'
+
+ field :issue_count,
+ Types::Analytics::CycleAnalytics::MetricType,
+ null: true,
+ description: 'Number of issues opened in the given period.',
+ resolver: Resolvers::Analytics::CycleAnalytics::IssueCountResolver[context]
+ field :deployment_count,
+ Types::Analytics::CycleAnalytics::MetricType,
+ null: true,
+ description: 'Number of production deployments in the given period.',
+ resolver: Resolvers::Analytics::CycleAnalytics::DeploymentCountResolver[context]
+ end
+ end
+ end
+ end
+ end
+end
+
+mod = Types::Analytics::CycleAnalytics::FlowMetrics
+mod.prepend_mod_with('Types::Analytics::CycleAnalytics::FlowMetrics')
diff --git a/app/graphql/types/analytics/cycle_analytics/link_type.rb b/app/graphql/types/analytics/cycle_analytics/link_type.rb
new file mode 100644
index 00000000000..3db6b58ac55
--- /dev/null
+++ b/app/graphql/types/analytics/cycle_analytics/link_type.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Types
+ module Analytics
+ module CycleAnalytics
+ # rubocop: disable Graphql/AuthorizeTypes
+ class LinkType < BaseObject
+ graphql_name 'ValueStreamMetricLinkType'
+
+ field :name,
+ GraphQL::Types::String,
+ null: false,
+ description: 'Name of the link group.'
+
+ field :label,
+ GraphQL::Types::String,
+ null: false,
+ description: 'Label for the link.'
+
+ field :url,
+ GraphQL::Types::String,
+ null: false,
+ description: 'Drill-down URL.'
+
+ field :docs_link,
+ GraphQL::Types::Boolean,
+ null: true,
+ description: 'Link to the metric documentation.'
+ end
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/analytics/cycle_analytics/metric_type.rb b/app/graphql/types/analytics/cycle_analytics/metric_type.rb
new file mode 100644
index 00000000000..3f1a239019f
--- /dev/null
+++ b/app/graphql/types/analytics/cycle_analytics/metric_type.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Types
+ module Analytics
+ module CycleAnalytics
+ # rubocop: disable Graphql/AuthorizeTypes
+ class MetricType < BaseObject
+ graphql_name 'ValueStreamAnalyticsMetric'
+ description ''
+
+ field :value,
+ GraphQL::Types::Float,
+ null: true,
+ description: 'Value for the metric.'
+
+ field :identifier,
+ GraphQL::Types::String,
+ null: false,
+ description: 'Identifier for the metric.'
+
+ field :unit,
+ GraphQL::Types::String,
+ null: true,
+ description: 'Unit of measurement.'
+
+ field :title,
+ GraphQL::Types::String,
+ null: false,
+ description: 'Title for the metric.'
+
+ field :links,
+ [LinkType],
+ null: false,
+ description: 'Optional links for drilling down.'
+ end
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/board_list_type.rb b/app/graphql/types/board_list_type.rb
index 2352a21bd87..20661da8d94 100644
--- a/app/graphql/types/board_list_type.rb
+++ b/app/graphql/types/board_list_type.rb
@@ -55,7 +55,7 @@ module Types
# board lists have a data dependency on label - so we batch load them here
def title
BatchLoader::GraphQL.for(object).batch do |lists, callback|
- ActiveRecord::Associations::Preloader.new.preload(lists, :label) # rubocop: disable CodeReuse/ActiveRecord
+ ActiveRecord::Associations::Preloader.new(records: lists, associations: :label).call # rubocop: disable CodeReuse/ActiveRecord
# all list titles are preloaded at this point
lists.each { |list| callback.call(list, list.title) }
diff --git a/app/graphql/types/branch_protections/base_access_level_type.rb b/app/graphql/types/branch_protections/base_access_level_type.rb
index 472733a6bc5..e6514ba8d7d 100644
--- a/app/graphql/types/branch_protections/base_access_level_type.rb
+++ b/app/graphql/types/branch_protections/base_access_level_type.rb
@@ -14,7 +14,7 @@ module Types
type: GraphQL::Types::String,
null: false,
description: 'Human readable representation for this access level.',
- hash_key: 'humanize'
+ method: 'humanize'
end
end
end
diff --git a/app/graphql/types/ci/catalog/resource_type.rb b/app/graphql/types/ci/catalog/resource_type.rb
new file mode 100644
index 00000000000..b5947826fa1
--- /dev/null
+++ b/app/graphql/types/ci/catalog/resource_type.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ module Catalog
+ # rubocop: disable Graphql/AuthorizeTypes
+ class ResourceType < BaseObject
+ graphql_name 'CiCatalogResource'
+
+ connection_type_class(Types::CountableConnectionType)
+
+ field :id, GraphQL::Types::ID, null: false, description: 'ID of the catalog resource.',
+ alpha: { milestone: '15.11' }
+
+ field :name, GraphQL::Types::String, null: true, description: 'Name of the catalog resource.',
+ alpha: { milestone: '15.11' }
+
+ field :description, GraphQL::Types::String, null: true, description: 'Description of the catalog resource.',
+ alpha: { milestone: '15.11' }
+
+ field :icon, GraphQL::Types::String, null: true, description: 'Icon for the catalog resource.',
+ method: :avatar_path, alpha: { milestone: '15.11' }
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+ end
+end
diff --git a/app/graphql/types/ci/ci_cd_setting_type.rb b/app/graphql/types/ci/ci_cd_setting_type.rb
index dd6647b749d..f7ef94f58c0 100644
--- a/app/graphql/types/ci/ci_cd_setting_type.rb
+++ b/app/graphql/types/ci/ci_cd_setting_type.rb
@@ -30,11 +30,7 @@ module Types
field :merge_trains_enabled, GraphQL::Types::Boolean, null: true,
description: 'Whether merge trains are enabled.',
method: :merge_trains_enabled?
- field :opt_in_jwt,
- GraphQL::Types::Boolean,
- null: true,
- description: 'When disabled, the JSON Web Token is always available in all jobs in the pipeline.',
- method: :opt_in_jwt?
+
field :project, Types::ProjectType, null: true,
description: 'Project the CI/CD settings belong to.'
end
diff --git a/app/graphql/types/ci/config/include_type_enum.rb b/app/graphql/types/ci/config/include_type_enum.rb
index 328824ae996..7ebcf786dd8 100644
--- a/app/graphql/types/ci/config/include_type_enum.rb
+++ b/app/graphql/types/ci/config/include_type_enum.rb
@@ -11,6 +11,7 @@ module Types
value 'local', description: 'Local include.', value: :local
value 'file', description: 'Project file include.', value: :file
value 'template', description: 'Template include.', value: :template
+ value 'component', description: 'Component include.', value: :component
end
end
end
diff --git a/app/graphql/types/ci/inherited_ci_variable_type.rb b/app/graphql/types/ci/inherited_ci_variable_type.rb
new file mode 100644
index 00000000000..2d8dcdaeefe
--- /dev/null
+++ b/app/graphql/types/ci/inherited_ci_variable_type.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ # rubocop: disable Graphql/AuthorizeTypes
+ class InheritedCiVariableType < BaseObject
+ graphql_name 'InheritedCiVariable'
+ description 'CI/CD variables a project inherites from its parent group and ancestors.'
+
+ field :id, GraphQL::Types::ID,
+ null: false,
+ description: 'ID of the variable.'
+
+ field :key, GraphQL::Types::String,
+ null: true,
+ description: 'Name of the variable.'
+
+ field :raw, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates whether the variable is raw.'
+
+ field :variable_type, ::Types::Ci::VariableTypeEnum,
+ null: true,
+ description: 'Type of the variable.'
+
+ 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.'
+
+ field :masked, GraphQL::Types::Boolean,
+ null: true,
+ description: 'Indicates whether the variable is masked.'
+
+ field :group_name, GraphQL::Types::String,
+ null: true,
+ description: 'Indicates group the variable belongs to.'
+
+ field :group_ci_cd_settings_path, GraphQL::Types::String,
+ null: true,
+ description: 'Indicates the path to the CI/CD settings of the group the variable belongs to.'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/ci/job_trace_type.rb b/app/graphql/types/ci/job_trace_type.rb
new file mode 100644
index 00000000000..a68e26106b8
--- /dev/null
+++ b/app/graphql/types/ci/job_trace_type.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+# rubocop: disable Graphql/AuthorizeTypes
+module Types
+ module Ci
+ class JobTraceType < BaseObject
+ graphql_name 'CiJobTrace'
+
+ field :html_summary, GraphQL::Types::String, null: false,
+ alpha: { milestone: '15.11' }, # As we want the option to change from 10 if needed
+ description: "HTML summary containing the last 10 lines of the trace."
+
+ def html_summary
+ object.html(last_lines: 10).html_safe
+ end
+ end
+ end
+end
+# rubocop: enable Graphql/AuthorizeTypes
diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb
index a97e9cee4b1..e77c2a38608 100644
--- a/app/graphql/types/ci/job_type.rb
+++ b/app/graphql/types/ci/job_type.rb
@@ -7,6 +7,8 @@ module Types
class JobType < BaseObject
graphql_name 'CiJob'
+ present_using ::Ci::BuildPresenter
+
connection_type_class(Types::LimitedCountableConnectionType)
expose_permissions Types::PermissionTypes::Ci::Job
@@ -25,6 +27,11 @@ module Types
description: 'References to builds that must complete before the jobs run.'
field :pipeline, Types::Ci::PipelineType, null: true,
description: 'Pipeline the job belongs to.'
+
+ field :runner, Types::Ci::RunnerType, null: true, description: 'Runner assigned to execute the job.'
+ field :runner_manager, ::Types::Ci::RunnerManagerType, null: true,
+ description: 'Runner manager assigned to the job.',
+ alpha: { milestone: '15.11' }
field :stage, Types::Ci::StageType, null: true,
description: 'Stage of the job.'
field :status,
@@ -76,6 +83,8 @@ module Types
description: 'Whether the job has a manual action.'
field :manual_variables, ManualVariableType.connection_type, null: true,
description: 'Variables added to a manual job when the job is triggered.'
+ field :play_path, GraphQL::Types::String, null: true,
+ description: 'Play path of the job.'
field :playable, GraphQL::Types::Boolean, null: false, method: :playable?,
description: 'Indicates the job can be played.'
field :previous_stage_jobs_or_needs, Types::Ci::JobNeedUnion.connection_type, null: true,
@@ -86,14 +95,18 @@ module Types
description: 'Path to the ref.'
field :retried, GraphQL::Types::Boolean, null: true,
description: 'Indicates that the job has been retried.'
- field :retryable, GraphQL::Types::Boolean, null: false, method: :retryable?,
+ field :retryable, GraphQL::Types::Boolean, null: false,
description: 'Indicates the job can be retried.'
+ field :scheduled, GraphQL::Types::Boolean, null: false, method: :scheduled?,
+ description: 'Indicates the job is scheduled.'
field :scheduling_type, GraphQL::Types::String, null: true,
description: 'Type of job scheduling. Value is `dag` if the job uses the `needs` keyword, and `stage` otherwise.'
field :short_sha, type: GraphQL::Types::String, null: false,
description: 'Short SHA1 ID of the commit.'
field :stuck, GraphQL::Types::Boolean, null: false, method: :stuck?,
description: 'Indicates the job is stuck.'
+ field :trace, Types::Ci::JobTraceType, null: true,
+ description: 'Trace generated by the job.'
field :triggered, GraphQL::Types::Boolean, null: true,
description: 'Whether the job was triggered.'
field :web_path, GraphQL::Types::String, null: true,
@@ -101,10 +114,25 @@ module Types
field :project, Types::ProjectType, null: true, description: 'Project that the job belongs to.'
+ field :can_play_job, GraphQL::Types::Boolean,
+ null: false, resolver_method: :can_play_job?,
+ description: 'Indicates whether the current user can play the job.'
+
+ field :failure_message, GraphQL::Types::String, null: true,
+ description: 'Message on why the job failed.'
+
+ def can_play_job?
+ object.playable? && Ability.allowed?(current_user, :play_job, object)
+ end
+
def kind
- return ::Ci::Build unless [::Ci::Build, ::Ci::Bridge].include?(object.class)
+ return ::Ci::Build unless [::Ci::Build, ::Ci::Bridge].include?(object.build.class)
+
+ object.build.class
+ end
- object.class
+ def retryable
+ object.build.retryable?
end
def pipeline
@@ -129,6 +157,10 @@ module Types
end
end
+ def trace
+ object.trace if object.has_trace?
+ end
+
def previous_stage_jobs_or_needs
if object.scheduling_type == 'stage'
Gitlab::Graphql::Lazy.with_value(previous_stage_jobs) do |jobs|
@@ -157,6 +189,24 @@ module Types
::Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Stage, object.stage_id).find
end
+ def runner
+ Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Runner, object.runner_id).find
+ end
+
+ def runner_manager
+ BatchLoader::GraphQL.for(object.id).batch(key: :runner_managers) do |build_ids, loader|
+ plucked_build_to_runner_manager_ids =
+ ::Ci::RunnerManagerBuild.for_build(build_ids).pluck_build_id_and_runner_manager_id
+ runner_managers = ::Ci::RunnerManager.id_in(plucked_build_to_runner_manager_ids.values.uniq)
+ Preloaders::RunnerManagerPolicyPreloader.new(runner_managers, current_user).execute
+ runner_managers_by_id = runner_managers.index_by(&:id)
+
+ build_ids.each do |build_id|
+ loader.call(build_id, runner_managers_by_id[plucked_build_to_runner_manager_ids[build_id]])
+ end
+ end
+ end
+
# This class is a secret union!
# TODO: turn this into an actual union, so that fields can be referenced safely!
def id
@@ -183,6 +233,10 @@ module Types
::Gitlab::Routing.url_helpers.project_job_path(object.project, object)
end
+ def play_path
+ ::Gitlab::Routing.url_helpers.play_project_job_path(object.project, object)
+ end
+
def browse_artifacts_path
::Gitlab::Routing.url_helpers.browse_project_job_artifacts_path(object.project, object)
end
diff --git a/app/graphql/types/ci/runner_manager_type.rb b/app/graphql/types/ci/runner_manager_type.rb
new file mode 100644
index 00000000000..2a5053f8f07
--- /dev/null
+++ b/app/graphql/types/ci/runner_manager_type.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ class RunnerManagerType < BaseObject
+ graphql_name 'CiRunnerManager'
+
+ connection_type_class(::Types::CountableConnectionType)
+
+ authorize :read_runner_manager
+
+ alias_method :runner_manager, :object
+
+ field :architecture_name, GraphQL::Types::String, null: true,
+ description: 'Architecture provided by the runner manager.',
+ method: :architecture
+ field :contacted_at, Types::TimeType, null: true,
+ description: 'Timestamp of last contact from the runner manager.',
+ method: :contacted_at
+ field :created_at, Types::TimeType, null: true,
+ description: 'Timestamp of creation of the runner manager.'
+ field :executor_name, GraphQL::Types::String, null: true,
+ description: 'Executor last advertised by the runner.',
+ method: :executor_name
+ field :id, ::Types::GlobalIDType[::Ci::RunnerManager], null: false,
+ description: 'ID of the runner manager.'
+ field :ip_address, GraphQL::Types::String, null: true,
+ description: 'IP address of the runner manager.'
+ field :platform_name, GraphQL::Types::String, null: true,
+ description: 'Platform provided by the runner manager.',
+ method: :platform
+ field :revision, GraphQL::Types::String, null: true, description: 'Revision of the runner.'
+ field :runner, RunnerType, null: true, description: 'Runner configuration for the runner manager.'
+ field :status,
+ Types::Ci::RunnerStatusEnum,
+ null: false,
+ description: 'Status of the runner manager.'
+ field :system_id, GraphQL::Types::String,
+ null: false,
+ description: 'System ID associated with the runner manager.',
+ method: :system_xid
+ field :version, GraphQL::Types::String, null: true, description: 'Version of the runner.'
+
+ def executor_name
+ ::Ci::Runner::EXECUTOR_TYPE_TO_NAMES[runner_manager.executor_type&.to_sym]
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb
index 10d18f9ad2a..8e509cc8493 100644
--- a/app/graphql/types/ci/runner_type.rb
+++ b/app/graphql/types/ci/runner_type.rb
@@ -14,9 +14,6 @@ module Types
JOB_COUNT_LIMIT = 1000
- # Only allow ephemeral_authentication_token to be visible for a short while
- RUNNER_EPHEMERAL_TOKEN_AVAILABILITY_TIME = 3.hours
-
alias_method :runner, :object
field :access_level, ::Types::Ci::RunnerAccessLevelEnum, null: false,
@@ -34,14 +31,20 @@ module Types
method: :contacted_at
field :created_at, Types::TimeType, null: true,
description: 'Timestamp of creation of this runner.'
+ field :created_by, Types::UserType, null: true,
+ description: 'User that created this runner.',
+ method: :creator
field :description, GraphQL::Types::String, null: true,
description: 'Description of the runner.'
field :edit_admin_url, GraphQL::Types::String, null: true,
description: 'Admin form URL of the runner. Only available for administrators.'
field :ephemeral_authentication_token, GraphQL::Types::String, null: true,
- description: 'Ephemeral authentication token used for runner machine registration.',
+ description: 'Ephemeral authentication token used for runner manager registration. Only available for the creator of the runner for a limited time during registration.',
authorize: :read_ephemeral_token,
alpha: { milestone: '15.9' }
+ field :ephemeral_register_url, GraphQL::Types::String, null: true,
+ description: 'URL of the registration page of the runner manager. Only available for the creator of the runner for a limited time during registration.',
+ alpha: { milestone: '15.11' }
field :executor_name, GraphQL::Types::String, null: true,
description: 'Executor last advertised by the runner.',
method: :executor_name
@@ -58,7 +61,7 @@ module Types
Types::Ci::RunnerJobExecutionStatusEnum,
null: true,
description: 'Job execution status of the runner.',
- deprecated: { milestone: '15.7', reason: :alpha }
+ alpha: { milestone: '15.7' }
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,
@@ -67,6 +70,10 @@ module Types
description: 'Indicates the runner is locked.'
field :maintenance_note, GraphQL::Types::String, null: true,
description: 'Runner\'s maintenance notes.'
+ field :managers, ::Types::Ci::RunnerManagerType.connection_type, null: true,
+ description: 'Machines associated with the runner configuration.',
+ method: :runner_managers,
+ alpha: { milestone: '15.10' }
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,
@@ -84,6 +91,8 @@ module Types
null: true,
resolver: ::Resolvers::Ci::RunnerProjectsResolver,
description: 'Find projects the runner is associated with. For project runners only.'
+ field :register_admin_url, GraphQL::Types::String, null: true,
+ description: 'URL of the temporary registration page of the runner. Only available before the runner is registered. Only available for administrators.'
field :revision, GraphQL::Types::String, null: true,
description: 'Revision of the runner.'
field :run_untagged, GraphQL::Types::Boolean, null: false,
@@ -141,12 +150,27 @@ module Types
Gitlab::Routing.url_helpers.edit_admin_runner_url(runner) if can_admin_runners?
end
- def ephemeral_authentication_token
- return unless runner.authenticated_user_registration_type?
- return unless runner.created_at > RUNNER_EPHEMERAL_TOKEN_AVAILABILITY_TIME.ago
- return if runner.runner_machines.any?
+ def ephemeral_register_url
+ return unless context[:current_user]&.can?(:read_ephemeral_token, runner) && runner.registration_available?
+
+ case runner.runner_type
+ when 'instance_type'
+ Gitlab::Routing.url_helpers.register_admin_runner_url(runner)
+ when 'group_type'
+ Gitlab::Routing.url_helpers.register_group_runner_url(runner.groups[0], runner)
+ when 'project_type'
+ Gitlab::Routing.url_helpers.register_project_runner_url(runner.projects[0], runner)
+ end
+ end
- runner.token
+ def register_admin_url
+ return unless can_admin_runners? && runner.registration_available?
+
+ Gitlab::Routing.url_helpers.register_admin_runner_url(runner)
+ end
+
+ def ephemeral_authentication_token
+ runner.token if runner.registration_available?
end
def project_count
diff --git a/app/graphql/types/clusters/agent_activity_event_type.rb b/app/graphql/types/clusters/agent_activity_event_type.rb
index 3484acfe25e..1d0ec7c4959 100644
--- a/app/graphql/types/clusters/agent_activity_event_type.rb
+++ b/app/graphql/types/clusters/agent_activity_event_type.rb
@@ -5,7 +5,7 @@ module Types
class AgentActivityEventType < BaseObject
graphql_name 'ClusterAgentActivityEvent'
- authorize :read_cluster
+ authorize :read_cluster_agent
connection_type_class(Types::CountableConnectionType)
diff --git a/app/graphql/types/clusters/agent_token_type.rb b/app/graphql/types/clusters/agent_token_type.rb
index 24489707698..720ee2f685b 100644
--- a/app/graphql/types/clusters/agent_token_type.rb
+++ b/app/graphql/types/clusters/agent_token_type.rb
@@ -5,7 +5,7 @@ module Types
class AgentTokenType < BaseObject
graphql_name 'ClusterAgentToken'
- authorize :read_cluster
+ authorize :read_cluster_agent
connection_type_class(Types::CountableConnectionType)
diff --git a/app/graphql/types/clusters/agent_type.rb b/app/graphql/types/clusters/agent_type.rb
index 5d7b8815cde..317a1aab628 100644
--- a/app/graphql/types/clusters/agent_type.rb
+++ b/app/graphql/types/clusters/agent_type.rb
@@ -5,7 +5,7 @@ module Types
class AgentType < BaseObject
graphql_name 'ClusterAgent'
- authorize :read_cluster
+ authorize :read_cluster_agent
connection_type_class(Types::CountableConnectionType)
diff --git a/app/graphql/types/clusters/agents/authorizations/ci_access_type.rb b/app/graphql/types/clusters/agents/authorizations/ci_access_type.rb
new file mode 100644
index 00000000000..a60f32b8b0b
--- /dev/null
+++ b/app/graphql/types/clusters/agents/authorizations/ci_access_type.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Types
+ module Clusters
+ module Agents
+ module Authorizations
+ class CiAccessType < BaseObject # rubocop:disable Graphql/AuthorizeTypes
+ graphql_name 'ClusterAgentAuthorizationCiAccess'
+
+ field :agent, Types::Clusters::AgentType,
+ description: 'Authorized cluster agent.',
+ null: true
+
+ field :config, GraphQL::Types::JSON, # rubocop:disable Graphql/JSONType
+ description: 'Configuration for the authorized project.',
+ null: true
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/clusters/agents/authorizations/user_access_type.rb b/app/graphql/types/clusters/agents/authorizations/user_access_type.rb
new file mode 100644
index 00000000000..8c5a466cde2
--- /dev/null
+++ b/app/graphql/types/clusters/agents/authorizations/user_access_type.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Types
+ module Clusters
+ module Agents
+ module Authorizations
+ class UserAccessType < BaseObject # rubocop:disable Graphql/AuthorizeTypes
+ graphql_name 'ClusterAgentAuthorizationUserAccess'
+
+ field :agent, Types::Clusters::AgentType,
+ description: 'Authorized cluster agent.',
+ null: true
+
+ field :config, GraphQL::Types::JSON, # rubocop:disable Graphql/JSONType
+ description: 'Configuration for the authorized project.',
+ null: true
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/commit_references_type.rb b/app/graphql/types/commit_references_type.rb
new file mode 100644
index 00000000000..2844a552f3e
--- /dev/null
+++ b/app/graphql/types/commit_references_type.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+module Types
+ class CommitReferencesType < BaseObject
+ graphql_name 'CommitReferences'
+
+ authorize :read_commit
+
+ def self.field_for_tipping_refs(field_name, field_description)
+ field field_name, ::Types::Projects::CommitParentNamesType,
+ null: true,
+ calls_gitaly: true,
+ description: field_description do
+ argument :limit, GraphQL::Types::Int,
+ required: true,
+ default_value: 100,
+ description: 'Number of ref names to return.',
+ validates: { numericality: { within: 1..1000 } }
+ end
+ end
+
+ def self.field_for_containing_refs(field_name, field_description)
+ field field_name, ::Types::Projects::CommitParentNamesType,
+ null: true,
+ calls_gitaly: true,
+ description: field_description do
+ argument :exclude_tipped, GraphQL::Types::Boolean,
+ required: true,
+ default_value: false,
+ description: 'Exclude tipping refs. WARNING: This argument can be confusing, if there is a limit.
+ for example set the limit to 5 and in the 5 out a total of 25 refs there is 2 tipped refs,
+ then the method will only 3 refs, even though there is more.'
+ # rubocop: disable GraphQL/ArgumentUniqueness
+ argument :limit, GraphQL::Types::Int,
+ required: true,
+ default_value: 100,
+ description: 'Number of ref names to return.',
+ validates: { numericality: { within: 1..1000 } }
+ # rubocop: enable GraphQL/ArgumentUniqueness
+ end
+ end
+
+ field_for_tipping_refs :tipping_tags, "Get tag names tipping at a given commit."
+
+ field_for_tipping_refs :tipping_branches, "Get branch names tipping at a given commit."
+
+ field_for_containing_refs :containing_tags, "Get tag names containing a given commit."
+
+ field_for_containing_refs :containing_branches, "Get branch names containing a given commit."
+
+ def tipping_tags(limit:)
+ { names: object.tipping_tags(limit: limit) }
+ end
+
+ def tipping_branches(limit:)
+ { names: object.tipping_branches(limit: limit) }
+ end
+
+ def containing_tags(limit:, exclude_tipped:)
+ { names: object.tags_containing(limit: limit, exclude_tipped: exclude_tipped) }
+ end
+
+ def containing_branches(limit:, exclude_tipped:)
+ { names: object.branches_containing(limit: limit, exclude_tipped: exclude_tipped) }
+ end
+ end
+end
diff --git a/app/graphql/types/commit_signatures/ssh_signature_type.rb b/app/graphql/types/commit_signatures/ssh_signature_type.rb
index 92eb4f7949a..d5db98c39a0 100644
--- a/app/graphql/types/commit_signatures/ssh_signature_type.rb
+++ b/app/graphql/types/commit_signatures/ssh_signature_type.rb
@@ -10,14 +10,19 @@ module Types
authorize :download_code
- field :user, Types::UserType, null: true,
- method: :signed_by_user,
- calls_gitaly: true,
- description: 'User associated with the key.'
+ 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.'
+ null: true,
+ description: 'SSH key used for the signature.'
+
+ field :key_fingerprint_sha256, String,
+ null: true,
+ description: 'Fingerprint of the key.'
end
end
end
diff --git a/app/graphql/types/data_transfer/base_type.rb b/app/graphql/types/data_transfer/base_type.rb
index e077612bfd5..5031bd5c612 100644
--- a/app/graphql/types/data_transfer/base_type.rb
+++ b/app/graphql/types/data_transfer/base_type.rb
@@ -7,7 +7,7 @@ module Types
field :egress_nodes, type: Types::DataTransfer::EgressNodeType.connection_type,
description: 'Data nodes.',
- null: true # disallow null once data_transfer_monitoring feature flag is rolled-out!
+ null: true # disallow null once data_transfer_monitoring feature flag is rolled-out! https://gitlab.com/gitlab-org/gitlab/-/issues/397693
end
end
end
diff --git a/app/graphql/types/data_transfer/egress_node_type.rb b/app/graphql/types/data_transfer/egress_node_type.rb
index a050540999f..f0ad3d15b53 100644
--- a/app/graphql/types/data_transfer/egress_node_type.rb
+++ b/app/graphql/types/data_transfer/egress_node_type.rb
@@ -26,12 +26,8 @@ module Types
null: false
field :registry_egress, GraphQL::Types::BigInt,
- description: 'Registery egress for that project in that period of time.',
+ description: 'Registry egress for that project in that period of time.',
null: false
-
- def total_egress
- object.values.select { |x| x.is_a?(Integer) }.sum
- end
end
end
end
diff --git a/app/graphql/types/data_transfer/project_data_transfer_type.rb b/app/graphql/types/data_transfer/project_data_transfer_type.rb
index f385aa20a7e..36afa20194e 100644
--- a/app/graphql/types/data_transfer/project_data_transfer_type.rb
+++ b/app/graphql/types/data_transfer/project_data_transfer_type.rb
@@ -8,12 +8,14 @@ module Types
field :total_egress, GraphQL::Types::BigInt,
description: 'Total egress for that project in that period of time.',
- null: true # disallow null once data_transfer_monitoring feature flag is rolled-out!
+ null: true, # disallow null once data_transfer_monitoring feature flag is rolled-out! https://gitlab.com/gitlab-org/gitlab/-/issues/397693
+ extras: [:parent]
- def total_egress(**_)
- return unless Feature.enabled?(:data_transfer_monitoring)
+ def total_egress(parent:)
+ return unless Feature.enabled?(:data_transfer_monitoring, parent.group)
+ return 40_000_000 if Feature.enabled?(:data_transfer_monitoring_mock_data, parent.group)
- 40_000_000
+ object[:egress_nodes].sum('repository_egress + artifacts_egress + packages_egress + registry_egress')
end
end
end
diff --git a/app/graphql/types/design_management/design_type.rb b/app/graphql/types/design_management/design_type.rb
index cc4c0e19ec7..be5edd17643 100644
--- a/app/graphql/types/design_management/design_type.rb
+++ b/app/graphql/types/design_management/design_type.rb
@@ -15,6 +15,11 @@ module Types
implements(Types::CurrentUserTodos)
implements(Types::TodoableInterface)
+ field :description,
+ GraphQL::Types::String,
+ null: true,
+ description: 'Description of the design.'
+
field :web_url,
GraphQL::Types::String,
null: false,
@@ -25,6 +30,8 @@ module Types
resolver: Resolvers::DesignManagement::VersionsResolver,
description: "All versions related to this design ordered newest first."
+ markdown_field :description_html, null: true
+
# Returns a `DesignManagement::Version` for this query based on the
# `atVersion` argument passed to a parent node if present, or otherwise
# the most recent `Version` for the issue.
diff --git a/app/graphql/types/environment_type.rb b/app/graphql/types/environment_type.rb
index 5f58fc38540..a3737cbcd0d 100644
--- a/app/graphql/types/environment_type.rb
+++ b/app/graphql/types/environment_type.rb
@@ -53,7 +53,8 @@ module Types
field :metrics_dashboard, Types::Metrics::DashboardType, null: true,
description: 'Metrics dashboard schema for the environment.',
- resolver: Resolvers::Metrics::DashboardResolver
+ resolver: Resolvers::Metrics::DashboardResolver,
+ deprecated: { reason: 'Returns no data. Underlying feature was removed in 16.0', milestone: '16.0' }
field :latest_opened_most_severe_alert,
Types::AlertManagement::AlertType,
diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb
index 3543ac29c17..da2c06d04b7 100644
--- a/app/graphql/types/group_type.rb
+++ b/app/graphql/types/group_type.rb
@@ -167,6 +167,11 @@ module Types
null: false,
description: 'Total size of the dependency proxy cached images.'
+ field :dependency_proxy_total_size_in_bytes,
+ GraphQL::Types::Int,
+ null: false,
+ description: 'Total size of the dependency proxy cached images in bytes.'
+
field :dependency_proxy_image_prefix,
GraphQL::Types::String,
null: false,
@@ -241,7 +246,7 @@ module Types
field :data_transfer, Types::DataTransfer::GroupDataTransferType,
null: true,
- resolver: Resolvers::DataTransferResolver.group,
+ resolver: Resolvers::DataTransfer::GroupDataTransferResolver,
description: 'Data transfer data point for a specific period. This is mocked data under a development feature flag.'
def label(title:)
@@ -279,10 +284,14 @@ module Types
def dependency_proxy_total_size
ActiveSupport::NumberHelper.number_to_human_size(
- group.dependency_proxy_manifests.sum(:size) + group.dependency_proxy_blobs.sum(:size)
+ dependency_proxy_total_size_in_bytes
)
end
+ def dependency_proxy_total_size_in_bytes
+ group.dependency_proxy_manifests.sum(:size) + group.dependency_proxy_blobs.sum(:size)
+ end
+
def dependency_proxy_setting
group.dependency_proxy_setting || group.create_dependency_proxy_setting
end
diff --git a/app/graphql/types/issuable_subscription_event_enum.rb b/app/graphql/types/issuable_subscription_event_enum.rb
new file mode 100644
index 00000000000..0f56fab8b46
--- /dev/null
+++ b/app/graphql/types/issuable_subscription_event_enum.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Types
+ class IssuableSubscriptionEventEnum < BaseEnum
+ graphql_name 'IssuableSubscriptionEvent'
+ description 'Values for subscribing and unsubscribing from issuables'
+
+ value 'SUBSCRIBE', 'Subscribe to an issuable.', value: 'subscribe'
+ value 'UNSUBSCRIBE', 'Unsubscribe from an issuable.', value: 'unsubscribe'
+ end
+end
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index 1e5833a5cf0..488e4d10cbc 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -122,6 +122,7 @@ module Types
description: 'Collection of design images associated with this issue.'
field :type, Types::IssueTypeEnum, null: true,
+ method: :issue_type,
description: 'Type of the issue.'
field :alert_management_alert,
@@ -209,14 +210,6 @@ module Types
def escalation_status
object.supports_escalation? ? object.escalation_status&.status_name : nil
end
-
- def type
- if Feature.enabled?(:issue_type_uses_work_item_types_table)
- object.work_item_type.base_type
- else
- object.issue_type
- end
- end
end
end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 3c288c1d496..f32dfc0dbcf 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -219,6 +219,13 @@ module Types
field :timelogs, Types::TimelogType.connection_type, null: false,
description: 'Timelogs on the merge request.'
+ field :award_emoji, Types::AwardEmojis::AwardEmojiType.connection_type,
+ null: true,
+ description: 'List of award emojis associated with the merge request.'
+
+ field :prepared_at, Types::TimeType, null: true,
+ description: 'Timestamp of when the merge request was prepared.'
+
markdown_field :title_html, null: true
markdown_field :description_html, null: true
@@ -295,6 +302,13 @@ module Types
def detailed_merge_status
::MergeRequests::Mergeability::DetailedMergeStatusService.new(merge_request: object).execute
end
+
+ # This is temporary to fix a bug where `committers` is already loaded and memoized
+ # and calling it again with a certain GraphQL query can cause the Rails to to throw
+ # a ActiveRecord::ImmutableRelation error
+ def committers
+ object.commits.committers
+ 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 1ba72ae33b5..e7246068a05 100644
--- a/app/graphql/types/merge_requests/detailed_merge_status_enum.rb
+++ b/app/graphql/types/merge_requests/detailed_merge_status_enum.rb
@@ -45,6 +45,9 @@ module Types
value 'EXTERNAL_STATUS_CHECKS',
value: :status_checks_must_pass,
description: 'Status checks must pass.'
+ value 'PREPARING',
+ value: :preparing,
+ description: 'Merge request diff is being created.'
end
end
end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index e48e9deae96..7e436d74dcf 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -6,7 +6,11 @@ module Types
include Gitlab::Graphql::MountMutation
- mount_mutation Mutations::Achievements::Create
+ mount_mutation Mutations::Achievements::Award, alpha: { milestone: '15.10' }
+ mount_mutation Mutations::Achievements::Create, alpha: { milestone: '15.8' }
+ mount_mutation Mutations::Achievements::Delete, alpha: { milestone: '15.11' }
+ mount_mutation Mutations::Achievements::Revoke, alpha: { milestone: '15.10' }
+ mount_mutation Mutations::Achievements::Update, alpha: { milestone: '15.11' }
mount_mutation Mutations::Admin::SidekiqQueues::DeleteJobs
mount_mutation Mutations::AlertManagement::CreateAlertIssue
mount_mutation Mutations::AlertManagement::UpdateAlertStatus
@@ -48,6 +52,7 @@ module Types
mount_mutation Mutations::DependencyProxy::ImageTtlGroupPolicy::Update
mount_mutation Mutations::DependencyProxy::GroupSettings::Update
mount_mutation Mutations::Environments::CanaryIngress::Update
+ mount_mutation Mutations::Environments::Stop
mount_mutation Mutations::IncidentManagement::TimelineEvent::Create, alpha: { milestone: '15.6' }
mount_mutation Mutations::IncidentManagement::TimelineEvent::PromoteFromNote
mount_mutation Mutations::IncidentManagement::TimelineEvent::Update
@@ -69,6 +74,7 @@ module Types
mount_mutation Mutations::Issues::BulkUpdate, alpha: { milestone: '15.9' }
mount_mutation Mutations::Labels::Create
mount_mutation Mutations::Members::Groups::BulkUpdate
+ mount_mutation Mutations::Members::Projects::BulkUpdate
mount_mutation Mutations::MergeRequests::Accept
mount_mutation Mutations::MergeRequests::Create
mount_mutation Mutations::MergeRequests::Update
@@ -80,8 +86,14 @@ module Types
mount_mutation Mutations::MergeRequests::SetAssignees
mount_mutation Mutations::MergeRequests::SetReviewers
mount_mutation Mutations::MergeRequests::ReviewerRereview
- mount_mutation Mutations::Metrics::Dashboard::Annotations::Create
- mount_mutation Mutations::Metrics::Dashboard::Annotations::Delete
+ mount_mutation Mutations::Metrics::Dashboard::Annotations::Create, deprecated: {
+ reason: 'Underlying feature was removed in 16.0',
+ milestone: '16.0'
+ }
+ mount_mutation Mutations::Metrics::Dashboard::Annotations::Delete, deprecated: {
+ reason: 'Underlying feature was removed in 16.0',
+ milestone: '16.0'
+ }
mount_mutation Mutations::Notes::Create::Note, calls_gitaly: true
mount_mutation Mutations::Notes::Create::DiffNote, calls_gitaly: true
mount_mutation Mutations::Notes::Create::ImageDiffNote, calls_gitaly: true
@@ -89,6 +101,7 @@ module Types
mount_mutation Mutations::Notes::Update::ImageDiffNote
mount_mutation Mutations::Notes::RepositionImageDiffNote
mount_mutation Mutations::Notes::Destroy
+ mount_mutation Mutations::Projects::SyncFork, calls_gitaly: true, alpha: { milestone: '15.9' }
mount_mutation Mutations::Releases::Create
mount_mutation Mutations::Releases::Update
mount_mutation Mutations::Releases::Delete
@@ -114,6 +127,7 @@ module Types
mount_mutation Mutations::DesignManagement::Upload, calls_gitaly: true
mount_mutation Mutations::DesignManagement::Delete, calls_gitaly: true
mount_mutation Mutations::DesignManagement::Move
+ mount_mutation Mutations::DesignManagement::Update
mount_mutation Mutations::ContainerExpirationPolicies::Update
mount_mutation Mutations::ContainerRepositories::Destroy
mount_mutation Mutations::ContainerRepositories::DestroyTags
@@ -137,8 +151,10 @@ module Types
mount_mutation Mutations::Ci::Job::Cancel
mount_mutation Mutations::Ci::Job::Unschedule
mount_mutation Mutations::Ci::JobArtifact::Destroy
+ mount_mutation Mutations::Ci::JobArtifact::BulkDestroy, alpha: { milestone: '15.10' }
mount_mutation Mutations::Ci::JobTokenScope::AddProject
mount_mutation Mutations::Ci::JobTokenScope::RemoveProject
+ mount_mutation Mutations::Ci::Runner::Create, alpha: { milestone: '15.10' }
mount_mutation Mutations::Ci::Runner::Update
mount_mutation Mutations::Ci::Runner::Delete
mount_mutation Mutations::Ci::Runner::BulkDelete, alpha: { milestone: '15.3' }
@@ -160,6 +176,8 @@ module Types
mount_mutation Mutations::WorkItems::DeleteTask, alpha: { milestone: '15.1' }
mount_mutation Mutations::WorkItems::Update, alpha: { milestone: '15.1' }
mount_mutation Mutations::WorkItems::UpdateTask, alpha: { milestone: '15.1' }
+ mount_mutation Mutations::WorkItems::Export, alpha: { milestone: '15.10' }
+ mount_mutation Mutations::WorkItems::Convert, alpha: { milestone: '15.11' }
mount_mutation Mutations::SavedReplies::Create
mount_mutation Mutations::SavedReplies::Update
mount_mutation Mutations::Pages::MarkOnboardingComplete
diff --git a/app/graphql/types/namespace_type.rb b/app/graphql/types/namespace_type.rb
index fc55ff512b6..3420f16213f 100644
--- a/app/graphql/types/namespace_type.rb
+++ b/app/graphql/types/namespace_type.rb
@@ -68,7 +68,9 @@ module Types
null: true,
alpha: { milestone: '15.8' },
description: "Achievements for the namespace. " \
- "Returns `null` if the `achievements` feature flag is disabled."
+ "Returns `null` if the `achievements` feature flag is disabled.",
+ extras: [:lookahead],
+ resolver: ::Resolvers::Achievements::AchievementsResolver
markdown_field :description_html, null: true
@@ -83,10 +85,6 @@ module Types
def root_storage_statistics
Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader.new(object.id).find
end
-
- def achievements
- object.achievements if Feature.enabled?(:achievements, object)
- end
end
end
diff --git a/app/graphql/types/packages/package_base_type.rb b/app/graphql/types/packages/package_base_type.rb
index 9ec4bb73c47..8dd2a4467d6 100644
--- a/app/graphql/types/packages/package_base_type.rb
+++ b/app/graphql/types/packages/package_base_type.rb
@@ -52,8 +52,6 @@ module Types
object.nuget_metadatum
when 'pypi'
object.pypi_metadatum
- else
- nil
end
end
# rubocop: enable GraphQL/ResolverMethodLength
diff --git a/app/graphql/types/packages/package_details_type.rb b/app/graphql/types/packages/package_details_type.rb
index f63b41b3c92..e00d6eac72f 100644
--- a/app/graphql/types/packages/package_details_type.rb
+++ b/app/graphql/types/packages/package_details_type.rb
@@ -63,11 +63,11 @@ module Types
end
def pypi_url
- pypi_registry_url(object.project.id)
+ pypi_registry_url(object.project)
end
def public_package
- object.project.public? || object.project.project_feature.package_registry_access_level == ProjectFeature::PUBLIC
+ object.project.project_feature.public_packages?
end
end
end
diff --git a/app/graphql/types/packages/package_file_type.rb b/app/graphql/types/packages/package_file_type.rb
index 3ee0d983745..4f1173a9e0b 100644
--- a/app/graphql/types/packages/package_file_type.rb
+++ b/app/graphql/types/packages/package_file_type.rb
@@ -29,8 +29,6 @@ module Types
object.conan_file_metadatum
when 'helm'
object.helm_file_metadatum
- else
- nil
end
end
end
diff --git a/app/graphql/types/permission_types/ci/pipeline_schedules.rb b/app/graphql/types/permission_types/ci/pipeline_schedules.rb
index 268ac6096d0..dd9d94aa578 100644
--- a/app/graphql/types/permission_types/ci/pipeline_schedules.rb
+++ b/app/graphql/types/permission_types/ci/pipeline_schedules.rb
@@ -6,11 +6,16 @@ module Types
class PipelineSchedules < BasePermissionType
graphql_name 'PipelineSchedulePermissions'
- abilities :take_ownership_pipeline_schedule,
- :update_pipeline_schedule,
+ abilities :update_pipeline_schedule,
:admin_pipeline_schedule
ability_field :play_pipeline_schedule, calls_gitaly: true
+ ability_field :take_ownership_pipeline_schedule,
+ deprecated: {
+ reason: 'Use admin_pipeline_schedule permission to determine if the user can take ownership ' \
+ 'of a pipeline schedule',
+ milestone: '15.9'
+ }
end
end
end
diff --git a/app/graphql/types/permission_types/group_enum.rb b/app/graphql/types/permission_types/group_enum.rb
index f636d43790f..6d51d94a70d 100644
--- a/app/graphql/types/permission_types/group_enum.rb
+++ b/app/graphql/types/permission_types/group_enum.rb
@@ -10,6 +10,9 @@ module Types
value 'TRANSFER_PROJECTS',
value: :transfer_projects,
description: 'Groups where the user can transfer projects to.'
+ value 'IMPORT_PROJECTS',
+ value: :import_projects,
+ description: 'Groups where the user can import projects to.'
end
end
end
diff --git a/app/graphql/types/permission_types/issue.rb b/app/graphql/types/permission_types/issue.rb
index b38971b64cd..a76dc88adfc 100644
--- a/app/graphql/types/permission_types/issue.rb
+++ b/app/graphql/types/permission_types/issue.rb
@@ -8,7 +8,7 @@ module Types
abilities :read_issue, :admin_issue, :update_issue, :reopen_issue,
:read_design, :create_design, :destroy_design,
- :create_note
+ :create_note, :update_design
end
end
end
diff --git a/app/graphql/types/permission_types/work_item.rb b/app/graphql/types/permission_types/work_item.rb
index f35f42001e0..0b6a384ec0e 100644
--- a/app/graphql/types/permission_types/work_item.rb
+++ b/app/graphql/types/permission_types/work_item.rb
@@ -6,7 +6,8 @@ module Types
graphql_name 'WorkItemPermissions'
description 'Check permissions for the current user on a work item'
- abilities :read_work_item, :update_work_item, :delete_work_item, :admin_work_item
+ abilities :read_work_item, :update_work_item, :delete_work_item,
+ :admin_work_item, :admin_parent_link, :set_work_item_metadata
end
end
end
diff --git a/app/graphql/types/project_statistics_redirect_type.rb b/app/graphql/types/project_statistics_redirect_type.rb
new file mode 100644
index 00000000000..c8fec0a54c4
--- /dev/null
+++ b/app/graphql/types/project_statistics_redirect_type.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ class ProjectStatisticsRedirectType < BaseObject
+ graphql_name 'ProjectStatisticsRedirect'
+
+ field :repository, GraphQL::Types::String, null: false,
+ description: 'Redirection Route for repository.'
+
+ field :wiki, GraphQL::Types::String, null: false,
+ description: 'Redirection Route for wiki.'
+
+ field :build_artifacts, GraphQL::Types::String, null: false,
+ description: 'Redirection Route for job_artifacts.'
+
+ field :packages, GraphQL::Types::String, null: false,
+ description: 'Redirection Route for packages.'
+
+ field :snippets, GraphQL::Types::String, null: false,
+ description: 'Redirection Route for snippets.'
+
+ field :container_registry, GraphQL::Types::String, null: false,
+ description: 'Redirection Route for container_registry.'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index c105ab9814c..f8a516501c3 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -24,9 +24,9 @@ module Types
authorize: :create_pipeline,
alpha: { milestone: '15.3' },
description: 'CI/CD config variable.' do
- argument :sha, GraphQL::Types::String,
+ argument :ref, GraphQL::Types::String,
required: true,
- description: 'Sha.'
+ description: 'Ref.'
end
field :full_path, GraphQL::Types::ID,
@@ -136,6 +136,11 @@ module Types
null: true,
description: 'Indicates if CI/CD pipeline jobs are enabled for the current user.'
+ field :is_catalog_resource, GraphQL::Types::Boolean,
+ alpha: { milestone: '15.11' },
+ null: true,
+ description: 'Indicates if a project is a catalog resource.'
+
field :public_jobs, GraphQL::Types::Boolean,
null: true,
description: 'Indicates if there is public access to pipelines and job details of the project, ' \
@@ -209,6 +214,11 @@ module Types
null: true,
description: 'Statistics of the project.'
+ field :statistics_details_paths, Types::ProjectStatisticsRedirectType,
+ null: true,
+ description: 'Redirects for Statistics of the project.',
+ calls_gitaly: true
+
field :repository, Types::RepositoryType,
null: true,
description: 'Git repository of the project.'
@@ -347,6 +357,12 @@ module Types
authorize: :admin_build,
resolver: Resolvers::Ci::VariablesResolver
+ field :inherited_ci_variables, Types::Ci::InheritedCiVariableType.connection_type,
+ null: true,
+ description: "List of CI/CD variables the project inherited from its parent group and ancestors.",
+ authorize: :admin_build,
+ resolver: Resolvers::Ci::InheritedVariablesResolver
+
field :ci_cd_settings, Types::Ci::CiCdSettingType,
null: true,
description: 'CI/CD settings for the project.'
@@ -517,6 +533,18 @@ module Types
description: 'Cluster agents associated with the project.',
resolver: ::Resolvers::Clusters::AgentsResolver
+ field :ci_access_authorized_agents, ::Types::Clusters::Agents::Authorizations::CiAccessType.connection_type,
+ null: true,
+ description: 'Authorized cluster agents for the project through ci_access keyword.',
+ resolver: ::Resolvers::Clusters::Agents::Authorizations::CiAccessResolver,
+ authorize: :read_cluster_agent
+
+ field :user_access_authorized_agents, ::Types::Clusters::Agents::Authorizations::UserAccessType.connection_type,
+ null: true,
+ description: 'Authorized cluster agents for the project through user_access keyword.',
+ resolver: ::Resolvers::Clusters::Agents::Authorizations::UserAccessResolver,
+ authorize: :read_cluster_agent
+
field :merge_commit_template, GraphQL::Types::String,
null: true,
description: 'Template used to create merge commit message in merge requests.'
@@ -567,8 +595,8 @@ module Types
description: "Find runners visible to the current user."
field :data_transfer, Types::DataTransfer::ProjectDataTransferType,
- null: true, # disallow null once data_transfer_monitoring feature flag is rolled-out!
- resolver: Resolvers::DataTransferResolver.project,
+ null: true, # disallow null once data_transfer_monitoring feature flag is rolled-out! https://gitlab.com/gitlab-org/gitlab/-/issues/391682
+ resolver: Resolvers::DataTransfer::ProjectDataTransferResolver,
description: 'Data transfer data point for a specific period. This is mocked data under a development feature flag.'
field :visible_forks, Types::ProjectType.connection_type,
@@ -581,6 +609,20 @@ module Types
description: 'Minimum access level.'
end
+ field :flow_metrics,
+ ::Types::Analytics::CycleAnalytics::FlowMetrics[:project],
+ null: true,
+ description: 'Flow metrics for value stream analytics.',
+ method: :project_namespace,
+ authorize: :read_cycle_analytics,
+ alpha: { milestone: '15.10' }
+
+ field :commit_references, ::Types::CommitReferencesType,
+ null: true,
+ resolver: Resolvers::Projects::CommitReferencesResolver,
+ alpha: { milestone: '16.0' },
+ description: "Get tag names containing a given commit."
+
def timelog_categories
object.project_namespace.timelog_categories if Feature.enabled?(:timelog_categories)
end
@@ -627,6 +669,16 @@ module Types
BatchLoader::GraphQL.wrap(object.forks_count)
end
+ def is_catalog_resource # rubocop:disable Naming/PredicateName
+ lazy_catalog_resource = BatchLoader::GraphQL.for(object.id).batch do |project_ids, loader|
+ ::Ci::Catalog::Resource.for_projects(project_ids).each do |catalog_resource|
+ loader.call(catalog_resource.project_id, catalog_resource)
+ end
+ end
+
+ Gitlab::Graphql::Lazy.with_value(lazy_catalog_resource, &:present?)
+ end
+
def statistics
Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader.new(object.id).find
end
@@ -635,10 +687,8 @@ module Types
project.container_repositories.size
end
- # Even if the parameter name is `sha`, it is actually a ref name. We always send `ref` to the endpoint.
- # See: https://gitlab.com/gitlab-org/gitlab/-/issues/389065
- def ci_config_variables(sha:)
- result = ::Ci::ListConfigVariablesService.new(object, context[:current_user]).execute(sha)
+ def ci_config_variables(ref:)
+ result = ::Ci::ListConfigVariablesService.new(object, context[:current_user]).execute(ref)
return if result.nil?
@@ -657,7 +707,7 @@ module Types
if project.repository.empty?
raise Gitlab::Graphql::Errors::MutationError,
- _(format('You must %s before using Security features.', add_file_docs_link.html_safe)).html_safe
+ Gitlab::Utils::ErrorMessage.to_user_facing(_(format('You must %s before using Security features.', add_file_docs_link.html_safe)).html_safe)
end
::Security::CiConfiguration::SastParserService.new(object).configuration
@@ -681,6 +731,19 @@ module Types
end
end
+ def statistics_details_paths
+ root_ref = project.repository.root_ref || project.default_branch_or_main
+
+ {
+ repository: Gitlab::Routing.url_helpers.project_tree_url(project, root_ref),
+ wiki: Gitlab::Routing.url_helpers.project_wikis_pages_url(project),
+ build_artifacts: Gitlab::Routing.url_helpers.project_artifacts_url(project),
+ packages: Gitlab::Routing.url_helpers.project_packages_url(project),
+ snippets: Gitlab::Routing.url_helpers.project_snippets_url(project),
+ container_registry: Gitlab::Routing.url_helpers.project_container_registry_index_url(project)
+ }
+ end
+
private
def project
diff --git a/app/graphql/types/projects/commit_parent_names_type.rb b/app/graphql/types/projects/commit_parent_names_type.rb
new file mode 100644
index 00000000000..39f8f1cdd07
--- /dev/null
+++ b/app/graphql/types/projects/commit_parent_names_type.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Types
+ module Projects
+ # rubocop: disable Graphql/AuthorizeTypes
+ class CommitParentNamesType < BaseObject
+ graphql_name 'CommitParentNames'
+
+ field :names, [GraphQL::Types::String], null: true, description: 'Names of the commit parent (branch or tag).'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/projects/fork_details_type.rb b/app/graphql/types/projects/fork_details_type.rb
index 88c17d89620..6157dc47255 100644
--- a/app/graphql/types/projects/fork_details_type.rb
+++ b/app/graphql/types/projects/fork_details_type.rb
@@ -9,11 +9,37 @@ module Types
field :ahead, GraphQL::Types::Int,
null: true,
+ calls_gitaly: true,
+ method: :ahead,
description: 'Number of commits ahead of upstream.'
field :behind, GraphQL::Types::Int,
null: true,
+ calls_gitaly: true,
+ method: :behind,
description: 'Number of commits behind upstream.'
+
+ field :is_syncing, GraphQL::Types::Boolean,
+ null: true,
+ method: :syncing?,
+ description: 'Indicates if there is a synchronization in progress.'
+
+ field :has_conflicts, GraphQL::Types::Boolean,
+ null: true,
+ method: :has_conflicts?,
+ description: 'Indicates if the fork conflicts with its upstream project.'
+
+ def ahead
+ counts[:ahead]
+ end
+
+ def behind
+ counts[:behind]
+ end
+
+ def counts
+ @counts ||= object.counts
+ end
end
# rubocop: enable Graphql/AuthorizeTypes
end
diff --git a/app/graphql/types/projects/namespace_project_sort_enum.rb b/app/graphql/types/projects/namespace_project_sort_enum.rb
index 7c7b54226d3..14a315781ef 100644
--- a/app/graphql/types/projects/namespace_project_sort_enum.rb
+++ b/app/graphql/types/projects/namespace_project_sort_enum.rb
@@ -7,8 +7,9 @@ module Types
description 'Values for sorting projects'
value 'SIMILARITY', 'Most similar to the search query.', value: :similarity
- value 'STORAGE', 'Sort by storage size.', value: :storage
- value 'ACTIVITY_DESC', 'Sort by latest activity, in descending order.', value: :latest_activity_desc
+ value 'ACTIVITY_DESC', 'Sort by latest activity, descending order.', value: :latest_activity_desc
end
end
end
+
+Types::Projects::NamespaceProjectSortEnum.prepend_mod
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index fb906759ba4..20dce54d740 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -14,6 +14,13 @@ module Types
null: true,
description: 'CI related settings that apply to the entire instance.'
field :ci_config, resolver: Resolvers::Ci::ConfigResolver, complexity: 126 # AUTHENTICATED_MAX_COMPLEXITY / 2 + 1
+
+ field :ci_pipeline_stage, ::Types::Ci::StageType,
+ null: true, description: 'Stage belonging to a CI pipeline.' do
+ argument :id, type: ::Types::GlobalIDType[::Ci::Stage],
+ required: true, description: 'Global ID of the CI stage.'
+ end
+
field :ci_variables,
Types::Ci::InstanceVariableType.connection_type,
null: true,
@@ -202,6 +209,15 @@ module Types
def query_complexity
context.query
end
+
+ def ci_pipeline_stage(id:)
+ stage = ::Gitlab::Graphql::Lazy.force(GitlabSchema.find_by_gid(id))
+ authorized = Ability.allowed?(current_user, :read_build, stage.project)
+
+ return unless authorized
+
+ stage
+ end
end
end
diff --git a/app/graphql/types/relative_position_type_enum.rb b/app/graphql/types/relative_position_type_enum.rb
new file mode 100644
index 00000000000..e0d28bea648
--- /dev/null
+++ b/app/graphql/types/relative_position_type_enum.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Types
+ class RelativePositionTypeEnum < BaseEnum
+ graphql_name 'RelativePositionType'
+ description 'The position to which the object should be moved'
+
+ value 'BEFORE', 'Object is moved before an adjacent object.'
+ value 'AFTER', 'Object is moved after an adjacent object.'
+ end
+end
diff --git a/app/graphql/types/release_asset_link_type.rb b/app/graphql/types/release_asset_link_type.rb
index b2bc52c7745..c1669f67666 100644
--- a/app/graphql/types/release_asset_link_type.rb
+++ b/app/graphql/types/release_asset_link_type.rb
@@ -9,12 +9,6 @@ module Types
present_using Releases::LinkPresenter
- field :external, GraphQL::Types::Boolean,
- null: true,
- method: :external?,
- description: 'Indicates the link points to an external resource.',
- deprecated: { reason: 'No longer used', milestone: '15.9' }
-
field :id, GraphQL::Types::ID, null: false,
description: 'ID of the link.'
field :link_type,
diff --git a/app/graphql/types/repository_type.rb b/app/graphql/types/repository_type.rb
index ab5d1bd8c9e..40eade3a4d1 100644
--- a/app/graphql/types/repository_type.rb
+++ b/app/graphql/types/repository_type.rb
@@ -28,3 +28,5 @@ module Types
description: 'Tree of the repository.'
end
end
+
+Types::RepositoryType.prepend_mod
diff --git a/app/graphql/types/root_storage_statistics_type.rb b/app/graphql/types/root_storage_statistics_type.rb
index 64aaf3e73a0..67ee0589882 100644
--- a/app/graphql/types/root_storage_statistics_type.rb
+++ b/app/graphql/types/root_storage_statistics_type.rb
@@ -12,6 +12,7 @@ module Types
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.'
field :pipeline_artifacts_size, GraphQL::Types::Float, null: false, description: 'CI pipeline artifacts size in bytes.'
+ field :registry_size_estimated, GraphQL::Types::Boolean, null: false, description: 'Indicates whether the deduplicated Container Registry size for the namespace is an estimated value or not.'
field :repository_size, GraphQL::Types::Float, null: false, description: 'Git repository size in bytes.'
field :snippets_size, GraphQL::Types::Float, null: false, description: 'Snippets size in bytes.'
field :storage_size, GraphQL::Types::Float, null: false, description: 'Total storage in bytes.'
diff --git a/app/graphql/types/time_tracking/timelog_connection_type.rb b/app/graphql/types/time_tracking/timelog_connection_type.rb
index 43e6955c2a3..6ea7a3478c7 100644
--- a/app/graphql/types/time_tracking/timelog_connection_type.rb
+++ b/app/graphql/types/time_tracking/timelog_connection_type.rb
@@ -5,7 +5,7 @@ module Types
# rubocop: disable Graphql/AuthorizeTypes
class TimelogConnectionType < CountableConnectionType
field :total_spent_time,
- GraphQL::Types::Int,
+ GraphQL::Types::BigInt,
null: false,
description: 'Total time spent in seconds.'
diff --git a/app/graphql/types/timelog_type.rb b/app/graphql/types/timelog_type.rb
index 3a060518cd9..88baca028ef 100644
--- a/app/graphql/types/timelog_type.rb
+++ b/app/graphql/types/timelog_type.rb
@@ -49,6 +49,10 @@ module Types
null: true,
description: 'Summary of how the time was spent.'
+ field :project, Types::ProjectType,
+ null: false,
+ description: 'Target project of the timelog merge request or issue.'
+
def user
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.user_id).find
end
diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb
index 9115b5a4760..b4950cc60e3 100644
--- a/app/graphql/types/user_interface.rb
+++ b/app/graphql/types/user_interface.rb
@@ -153,9 +153,18 @@ module Types
field :profile_enable_gitpod_path, GraphQL::Types::String, null: true,
description: 'Web path to enable Gitpod for the user.'
+ field :user_achievements,
+ Types::Achievements::UserAchievementType.connection_type,
+ null: true,
+ alpha: { milestone: '15.10' },
+ description: "Achievements for the user. " \
+ "Only returns for namespaces where the `achievements` feature flag is enabled.",
+ extras: [:lookahead],
+ resolver: ::Resolvers::Achievements::UserAchievementsResolver
+
definition_methods do
def resolve_type(object, context)
- # in the absense of other information, we cannot tell - just default to
+ # in the absence of other information, we cannot tell - just default to
# the core user type.
::Types::UserType
end
@@ -166,3 +175,5 @@ module Types
end
end
end
+
+Types::UserInterface.prepend_mod
diff --git a/app/graphql/types/user_preferences_type.rb b/app/graphql/types/user_preferences_type.rb
index 9a1ea4a2e4f..094c7352c96 100644
--- a/app/graphql/types/user_preferences_type.rb
+++ b/app/graphql/types/user_preferences_type.rb
@@ -10,6 +10,10 @@ module Types
description: 'Sort order for issue lists.',
null: true
+ field :visibility_pipeline_id_type, Types::VisibilityPipelineIdTypeEnum,
+ description: 'Determines whether the pipeline list shows ID or IID.',
+ null: true
+
def issues_sort
object.issues_sort.to_sym
end
diff --git a/app/graphql/types/visibility_pipeline_id_type_enum.rb b/app/graphql/types/visibility_pipeline_id_type_enum.rb
new file mode 100644
index 00000000000..8f0ae7d0c2f
--- /dev/null
+++ b/app/graphql/types/visibility_pipeline_id_type_enum.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Types
+ class VisibilityPipelineIdTypeEnum < BaseEnum
+ graphql_name 'VisibilityPipelineIdType'
+ description 'Determines whether the pipeline list shows ID or IID'
+
+ UserPreference.visibility_pipeline_id_types.each_key do |field|
+ value field.upcase, value: field, description: "Display pipeline #{field.upcase}."
+ end
+ end
+end
diff --git a/app/graphql/types/work_item_type.rb b/app/graphql/types/work_item_type.rb
index b46362f66b8..1e58781dbb9 100644
--- a/app/graphql/types/work_item_type.rb
+++ b/app/graphql/types/work_item_type.rb
@@ -27,7 +27,10 @@ module Types
GraphQL::Types::Int,
null: false,
description: 'Lock version of the work item. Incremented each time the work item is updated.'
- field :project, Types::ProjectType, null: false,
+ field :namespace, Types::NamespaceType, null: true,
+ description: 'Namespace the work item belongs to.',
+ alpha: { milestone: '15.10' }
+ field :project, Types::ProjectType, null: true,
description: 'Project the work item belongs to.',
alpha: { milestone: '15.3' }
field :state, WorkItemStateEnum, null: false,
@@ -36,6 +39,18 @@ module Types
description: 'Title of the work item.'
field :updated_at, Types::TimeType, null: false,
description: 'Timestamp of when the work item was last updated.'
+
+ field :create_note_email, GraphQL::Types::String,
+ null: true,
+ description: 'User specific email address for the work item.'
+
+ field :reference, GraphQL::Types::String, null: false,
+ description: 'Internal reference of the work item. Returned in shortened format by default.',
+ method: :to_reference do
+ argument :full, GraphQL::Types::Boolean, required: false, default_value: false,
+ description: 'Boolean option specifying whether the reference should be returned in full.'
+ end
+
field :widgets,
[Types::WorkItems::WidgetInterface],
null: true,
@@ -51,5 +66,9 @@ module Types
def web_url
Gitlab::UrlBuilder.build(object)
end
+
+ def create_note_email
+ object.creatable_note_email_address(context[:current_user])
+ end
end
end
diff --git a/app/graphql/types/work_items/available_export_fields_enum.rb b/app/graphql/types/work_items/available_export_fields_enum.rb
new file mode 100644
index 00000000000..f5b26d9818d
--- /dev/null
+++ b/app/graphql/types/work_items/available_export_fields_enum.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ class AvailableExportFieldsEnum < BaseEnum
+ graphql_name 'AvailableExportFields'
+ description 'Available fields to be exported as CSV'
+
+ value 'ID', value: 'id', description: 'Unique identifier.'
+ value 'TITLE', value: 'title', description: 'Title.'
+ value 'DESCRIPTION', value: 'description', description: 'Description.'
+ value 'TYPE', value: 'type', description: 'Type of the work item.'
+ value 'AUTHOR', value: 'author', description: 'Author name.'
+ value 'AUTHOR_USERNAME', value: 'author username', description: 'Author username.'
+ value 'CREATED_AT', value: 'created_at', description: 'Date of creation.'
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/award_emoji_update_action_enum.rb b/app/graphql/types/work_items/award_emoji_update_action_enum.rb
new file mode 100644
index 00000000000..5b2512a215f
--- /dev/null
+++ b/app/graphql/types/work_items/award_emoji_update_action_enum.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ class AwardEmojiUpdateActionEnum < BaseEnum
+ graphql_name 'WorkItemAwardEmojiUpdateAction'
+ description 'Values for work item award emoji update enum'
+
+ value 'ADD', 'Adds the emoji.', value: :add
+ value 'REMOVE', 'Removes the emoji.', value: :remove
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/todo_update_action_enum.rb b/app/graphql/types/work_items/todo_update_action_enum.rb
new file mode 100644
index 00000000000..d9ce8f65396
--- /dev/null
+++ b/app/graphql/types/work_items/todo_update_action_enum.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ class TodoUpdateActionEnum < BaseEnum
+ graphql_name 'WorkItemTodoUpdateAction'
+ description 'Values for work item to-do update enum'
+
+ value 'MARK_AS_DONE', 'Marks the to-do as done.', value: 'mark_as_done'
+ value 'ADD', 'Adds the to-do.', value: 'add'
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/widget_interface.rb b/app/graphql/types/work_items/widget_interface.rb
index 672a78f12e1..53ea901ea10 100644
--- a/app/graphql/types/work_items/widget_interface.rb
+++ b/app/graphql/types/work_items/widget_interface.rb
@@ -18,7 +18,10 @@ module Types
::Types::WorkItems::Widgets::AssigneesType,
::Types::WorkItems::Widgets::StartAndDueDateType,
::Types::WorkItems::Widgets::MilestoneType,
- ::Types::WorkItems::Widgets::NotesType
+ ::Types::WorkItems::Widgets::NotesType,
+ ::Types::WorkItems::Widgets::NotificationsType,
+ ::Types::WorkItems::Widgets::CurrentUserTodosType,
+ ::Types::WorkItems::Widgets::AwardEmojiType
].freeze
def self.ce_orphan_types
@@ -44,6 +47,12 @@ module Types
::Types::WorkItems::Widgets::MilestoneType
when ::WorkItems::Widgets::Notes
::Types::WorkItems::Widgets::NotesType
+ when ::WorkItems::Widgets::Notifications
+ ::Types::WorkItems::Widgets::NotificationsType
+ when ::WorkItems::Widgets::CurrentUserTodos
+ ::Types::WorkItems::Widgets::CurrentUserTodosType
+ when ::WorkItems::Widgets::AwardEmoji
+ ::Types::WorkItems::Widgets::AwardEmojiType
else
raise "Unknown GraphQL type for widget #{object}"
end
diff --git a/app/graphql/types/work_items/widgets/award_emoji_type.rb b/app/graphql/types/work_items/widgets/award_emoji_type.rb
new file mode 100644
index 00000000000..421bb8f0e98
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/award_emoji_type.rb
@@ -0,0 +1,41 @@
+# 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 AwardEmojiType < BaseObject
+ graphql_name 'WorkItemWidgetAwardEmoji'
+ description 'Represents the award emoji widget'
+
+ implements Types::WorkItems::WidgetInterface
+
+ field :award_emoji,
+ ::Types::AwardEmojis::AwardEmojiType.connection_type,
+ null: true,
+ description: 'Award emoji on the work item.'
+ field :downvotes,
+ GraphQL::Types::Int,
+ null: false,
+ description: 'Number of downvotes the work item has received.'
+ field :upvotes,
+ GraphQL::Types::Int,
+ null: false,
+ description: 'Number of upvotes the work item has received.'
+
+ def downvotes
+ BatchLoaders::AwardEmojiVotesBatchLoader
+ .load_downvotes(object.work_item, awardable_class: 'Issue')
+ end
+
+ def upvotes
+ BatchLoaders::AwardEmojiVotesBatchLoader
+ .load_upvotes(object.work_item, awardable_class: 'Issue')
+ end
+ end
+ # rubocop:enable Graphql/AuthorizeTypes
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/widgets/award_emoji_update_input_type.rb b/app/graphql/types/work_items/widgets/award_emoji_update_input_type.rb
new file mode 100644
index 00000000000..1d43d4913d2
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/award_emoji_update_input_type.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ module Widgets
+ class AwardEmojiUpdateInputType < BaseInputObject
+ graphql_name 'WorkItemWidgetAwardEmojiUpdateInput'
+
+ argument :action, ::Types::WorkItems::AwardEmojiUpdateActionEnum,
+ required: true,
+ description: 'Action for the update.'
+
+ argument :name,
+ GraphQL::Types::String,
+ required: true,
+ description: copy_field_description(Types::AwardEmojis::AwardEmojiType, :name)
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/widgets/current_user_todos_input_type.rb b/app/graphql/types/work_items/widgets/current_user_todos_input_type.rb
new file mode 100644
index 00000000000..630958def53
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/current_user_todos_input_type.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ module Widgets
+ class CurrentUserTodosInputType < BaseInputObject
+ graphql_name 'WorkItemWidgetCurrentUserTodosInput'
+
+ argument :action, ::Types::WorkItems::TodoUpdateActionEnum,
+ required: true,
+ description: 'Action for the update.'
+
+ argument :todo_id,
+ ::Types::GlobalIDType[::Todo],
+ required: false,
+ description: "Global ID of the to-do. If not present, all to-dos of the work item will be updated.",
+ prepare: ->(id, _) { id.model_id }
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/widgets/current_user_todos_type.rb b/app/graphql/types/work_items/widgets/current_user_todos_type.rb
new file mode 100644
index 00000000000..1c7cdd631e2
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/current_user_todos_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 CurrentUserTodosType < BaseObject
+ graphql_name 'WorkItemWidgetCurrentUserTodos'
+ description 'Represents a todos widget'
+
+ implements Types::WorkItems::WidgetInterface
+ implements Types::CurrentUserTodos
+
+ private
+
+ # Overriden as `Types::CurrentUserTodos` relies on `unpresented` being the Issuable record.
+ def unpresented
+ object.work_item
+ end
+ end
+ # rubocop:enable Graphql/AuthorizeTypes
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/widgets/hierarchy_update_input_type.rb b/app/graphql/types/work_items/widgets/hierarchy_update_input_type.rb
index e1a9ebb76e9..297b06a8fab 100644
--- a/app/graphql/types/work_items/widgets/hierarchy_update_input_type.rb
+++ b/app/graphql/types/work_items/widgets/hierarchy_update_input_type.rb
@@ -6,16 +6,27 @@ module Types
class HierarchyUpdateInputType < BaseInputObject
graphql_name 'WorkItemWidgetHierarchyUpdateInput'
- argument :parent_id, ::Types::GlobalIDType[::WorkItem],
+ argument :adjacent_work_item_id,
+ ::Types::GlobalIDType[::WorkItem],
required: false,
loads: ::Types::WorkItemType,
- description: 'Global ID of the parent work item. Use `null` to remove the association.'
+ description: 'ID of the work item to be switched with.'
argument :children_ids, [::Types::GlobalIDType[::WorkItem]],
required: false,
description: 'Global IDs of children work items.',
loads: ::Types::WorkItemType,
as: :children
+
+ argument :parent_id, ::Types::GlobalIDType[::WorkItem],
+ required: false,
+ loads: ::Types::WorkItemType,
+ description: 'Global ID of the parent work item. Use `null` to remove the association.'
+
+ argument :relative_position,
+ Types::RelativePositionTypeEnum,
+ required: false,
+ description: 'Type of switch. Valid values are `BEFORE` or `AFTER`.'
end
end
end
diff --git a/app/graphql/types/work_items/widgets/notifications_type.rb b/app/graphql/types/work_items/widgets/notifications_type.rb
new file mode 100644
index 00000000000..85928817d07
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/notifications_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 NotificationsType < BaseObject
+ graphql_name 'WorkItemWidgetNotifications'
+ description 'Represents the notifications widget'
+
+ implements Types::WorkItems::WidgetInterface
+
+ field :subscribed, GraphQL::Types::Boolean,
+ null: false,
+ description: 'Whether the current user is subscribed to notifications on the work item.'
+
+ def subscribed
+ object.work_item.subscribed?(current_user, object.work_item.project)
+ end
+ end
+ # rubocop:enable Graphql/AuthorizeTypes
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/widgets/notifications_update_input_type.rb b/app/graphql/types/work_items/widgets/notifications_update_input_type.rb
new file mode 100644
index 00000000000..2f3b46c3f45
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/notifications_update_input_type.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ module Widgets
+ class NotificationsUpdateInputType < BaseInputObject
+ graphql_name 'WorkItemWidgetNotificationsUpdateInput'
+
+ argument :subscribed,
+ GraphQL::Types::Boolean,
+ required: true,
+ description: 'Desired state of the subscription.'
+ end
+ end
+ end
+end