From 8b573c94895dc0ac0e1d9d59cf3e8745e8b539ca Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 17 Dec 2020 11:59:07 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-7-stable-ee --- app/graphql/mutations/alert_management/base.rb | 2 +- .../alert_management/create_alert_issue.rb | 1 + .../alert_management/http_integration/destroy.rb | 2 +- .../http_integration/reset_token.rb | 2 +- .../alert_management/http_integration/update.rb | 2 +- .../prometheus_integration/reset_token.rb | 2 +- .../prometheus_integration/update.rb | 2 +- app/graphql/mutations/award_emojis/add.rb | 2 - app/graphql/mutations/award_emojis/base.rb | 23 ++-- app/graphql/mutations/award_emojis/remove.rb | 2 - app/graphql/mutations/award_emojis/toggle.rb | 2 - .../mutations/boards/common_mutation_arguments.rb | 24 ++++ app/graphql/mutations/boards/create.rb | 26 +---- app/graphql/mutations/boards/lists/create.rb | 20 +--- app/graphql/mutations/boards/update.rb | 43 +++++++ app/graphql/mutations/ci/base.rb | 2 +- .../mutations/concerns/mutations/finds_by_gid.rb | 9 ++ .../mutations/container_repositories/destroy.rb | 13 +-- .../container_repositories/destroy_base.rb | 18 +++ .../container_repositories/destroy_tags.rb | 50 ++++++++ app/graphql/mutations/design_management/base.rb | 2 +- .../mutations/discussions/toggle_resolve.rb | 2 +- .../environments/canary_ingress/update.rb | 39 +++++++ app/graphql/mutations/issues/update.rb | 2 +- app/graphql/mutations/merge_requests/base.rb | 2 +- .../metrics/dashboard/annotations/create.rb | 4 +- .../metrics/dashboard/annotations/delete.rb | 2 +- app/graphql/mutations/notes/create/base.rb | 2 +- app/graphql/mutations/notes/create/note.rb | 2 +- app/graphql/mutations/notes/destroy.rb | 2 +- .../mutations/notes/reposition_image_diff_note.rb | 2 +- app/graphql/mutations/notes/update/base.rb | 2 +- app/graphql/mutations/releases/create.rb | 3 +- app/graphql/mutations/releases/delete.rb | 40 +++++++ app/graphql/mutations/releases/update.rb | 70 ++++++++++++ app/graphql/mutations/snippets/create.rb | 31 ++--- app/graphql/mutations/snippets/destroy.rb | 2 +- app/graphql/mutations/snippets/mark_as_spam.rb | 4 +- app/graphql/mutations/snippets/update.rb | 8 +- app/graphql/mutations/todos/mark_done.rb | 2 +- app/graphql/mutations/todos/restore.rb | 2 +- app/graphql/mutations/todos/restore_many.rb | 4 +- .../queries/epic/epic_children.query.graphql | 126 +++++++++++++++++++++ .../queries/epic/epic_details.query.graphql | 20 ++++ .../resolvers/alert_management/alert_resolver.rb | 5 + .../resolvers/assigned_merge_requests_resolver.rb | 1 + .../resolvers/authored_merge_requests_resolver.rb | 1 + app/graphql/resolvers/base_resolver.rb | 12 ++ .../resolvers/board_list_issues_resolver.rb | 2 +- app/graphql/resolvers/board_lists_resolver.rb | 10 +- app/graphql/resolvers/ci/config_resolver.rb | 60 ++++++++++ app/graphql/resolvers/ci/jobs_resolver.rb | 2 + .../resolvers/ci/pipeline_stages_resolver.rb | 3 + app/graphql/resolvers/ci/runner_setup_resolver.rb | 5 +- .../resolvers/concerns/caching_array_resolver.rb | 17 ++- .../resolvers/concerns/manual_authorization.rb | 11 ++ .../resolvers/design_management/design_resolver.rb | 2 + .../version/design_at_version_resolver.rb | 2 + .../version_in_collection_resolver.rb | 2 + .../design_management/versions_resolver.rb | 2 + .../sentry_error_stack_trace_resolver.rb | 2 + .../error_tracking/sentry_errors_resolver.rb | 27 +++-- app/graphql/resolvers/group_members_resolver.rb | 5 + .../resolvers/issue_status_counts_resolver.rb | 2 + app/graphql/resolvers/issues_resolver.rb | 4 +- app/graphql/resolvers/members_resolver.rb | 4 +- .../resolvers/merge_request_pipelines_resolver.rb | 22 +++- app/graphql/resolvers/merge_requests_resolver.rb | 10 +- app/graphql/resolvers/project_members_resolver.rb | 5 + .../resolvers/project_merge_requests_resolver.rb | 1 + app/graphql/resolvers/project_pipeline_resolver.rb | 4 +- .../project_pipeline_statistics_resolver.rb | 28 +++++ .../resolvers/projects/jira_imports_resolver.rb | 23 ---- .../resolvers/projects/services_resolver.rb | 6 +- .../review_requested_merge_requests_resolver.rb | 13 +++ app/graphql/resolvers/snippets/blobs_resolver.rb | 6 +- .../resolvers/user_discussions_count_resolver.rb | 26 +++++ app/graphql/resolvers/user_notes_count_resolver.rb | 26 +++++ .../resolvers/users/group_count_resolver.rb | 2 + app/graphql/resolvers/users_resolver.rb | 2 +- .../types/alert_management/domain_filter_enum.rb | 13 +++ .../prometheus_integration_type.rb | 2 +- app/graphql/types/award_emojis/award_emoji_type.rb | 9 +- app/graphql/types/base_field.rb | 7 +- app/graphql/types/base_interface.rb | 2 + app/graphql/types/board_list_type.rb | 7 +- app/graphql/types/board_type.rb | 6 + app/graphql/types/ci/analytics_type.rb | 33 ++++++ app/graphql/types/ci/ci_cd_setting_type.rb | 20 ++++ app/graphql/types/ci/config/config_type.rb | 21 ++++ app/graphql/types/ci/config/group_type.rb | 19 ++++ app/graphql/types/ci/config/job_type.rb | 21 ++++ app/graphql/types/ci/config/need_type.rb | 15 +++ app/graphql/types/ci/config/stage_type.rb | 17 +++ app/graphql/types/ci/config/status_enum.rb | 15 +++ app/graphql/types/ci/detailed_status_type.rb | 30 ++--- app/graphql/types/ci/group_type.rb | 7 +- .../types/ci/job_artifact_file_type_enum.rb | 13 +++ app/graphql/types/ci/job_artifact_type.rb | 24 ++++ app/graphql/types/ci/job_type.rb | 30 +++-- app/graphql/types/ci/pipeline_type.rb | 24 +++- app/graphql/types/ci/stage_type.rb | 7 +- app/graphql/types/commit_type.rb | 11 +- .../types/concerns/gitlab_style_deprecations.rb | 4 +- app/graphql/types/container_repository_type.rb | 5 + .../design_management/design_collection_type.rb | 2 +- .../error_tracking/sentry_error_collection_type.rb | 21 +--- app/graphql/types/group_invitation_type.rb | 7 +- app/graphql/types/group_member_relation_enum.rb | 12 ++ app/graphql/types/group_member_type.rb | 7 +- app/graphql/types/group_type.rb | 25 ++-- app/graphql/types/issue_type.rb | 26 +---- app/graphql/types/jira_import_type.rb | 3 +- app/graphql/types/jira_users_mapping_input_type.rb | 2 +- app/graphql/types/merge_request_connection_type.rb | 15 +++ app/graphql/types/merge_request_type.rb | 54 ++++++++- app/graphql/types/mutation_type.rb | 4 + app/graphql/types/namespace_type.rb | 8 +- app/graphql/types/notes/diff_position_type.rb | 42 +++++-- app/graphql/types/notes/note_type.rb | 14 ++- .../types/permission_types/merge_request.rb | 4 +- app/graphql/types/project_member_relation_enum.rb | 12 ++ app/graphql/types/project_type.rb | 71 ++++++++---- app/graphql/types/query_type.rb | 10 +- app/graphql/types/snippets/blob_viewer_type.rb | 14 ++- app/graphql/types/sort_enum.rb | 8 +- app/graphql/types/terraform/state_type.rb | 8 +- app/graphql/types/terraform/state_version_type.rb | 35 +++++- app/graphql/types/todo_type.rb | 21 +++- app/graphql/types/tree/blob_type.rb | 11 +- app/graphql/types/tree/tree_type.rb | 31 ++--- app/graphql/types/user_type.rb | 12 +- 132 files changed, 1451 insertions(+), 367 deletions(-) create mode 100644 app/graphql/mutations/boards/common_mutation_arguments.rb create mode 100644 app/graphql/mutations/boards/update.rb create mode 100644 app/graphql/mutations/concerns/mutations/finds_by_gid.rb create mode 100644 app/graphql/mutations/container_repositories/destroy_base.rb create mode 100644 app/graphql/mutations/container_repositories/destroy_tags.rb create mode 100644 app/graphql/mutations/environments/canary_ingress/update.rb create mode 100644 app/graphql/mutations/releases/delete.rb create mode 100644 app/graphql/mutations/releases/update.rb create mode 100644 app/graphql/queries/epic/epic_children.query.graphql create mode 100644 app/graphql/queries/epic/epic_details.query.graphql create mode 100644 app/graphql/resolvers/ci/config_resolver.rb create mode 100644 app/graphql/resolvers/concerns/manual_authorization.rb create mode 100644 app/graphql/resolvers/project_pipeline_statistics_resolver.rb delete mode 100644 app/graphql/resolvers/projects/jira_imports_resolver.rb create mode 100644 app/graphql/resolvers/review_requested_merge_requests_resolver.rb create mode 100644 app/graphql/resolvers/user_discussions_count_resolver.rb create mode 100644 app/graphql/resolvers/user_notes_count_resolver.rb create mode 100644 app/graphql/types/alert_management/domain_filter_enum.rb create mode 100644 app/graphql/types/ci/analytics_type.rb create mode 100644 app/graphql/types/ci/ci_cd_setting_type.rb create mode 100644 app/graphql/types/ci/config/config_type.rb create mode 100644 app/graphql/types/ci/config/group_type.rb create mode 100644 app/graphql/types/ci/config/job_type.rb create mode 100644 app/graphql/types/ci/config/need_type.rb create mode 100644 app/graphql/types/ci/config/stage_type.rb create mode 100644 app/graphql/types/ci/config/status_enum.rb create mode 100644 app/graphql/types/ci/job_artifact_file_type_enum.rb create mode 100644 app/graphql/types/ci/job_artifact_type.rb create mode 100644 app/graphql/types/group_member_relation_enum.rb create mode 100644 app/graphql/types/merge_request_connection_type.rb create mode 100644 app/graphql/types/project_member_relation_enum.rb (limited to 'app/graphql') diff --git a/app/graphql/mutations/alert_management/base.rb b/app/graphql/mutations/alert_management/base.rb index 81d5ee95f06..8c6b4005cf8 100644 --- a/app/graphql/mutations/alert_management/base.rb +++ b/app/graphql/mutations/alert_management/base.rb @@ -11,7 +11,7 @@ module Mutations argument :iid, GraphQL::STRING_TYPE, required: true, - description: "The iid of the alert to mutate" + description: "The IID of the alert to mutate" field :alert, Types::AlertManagement::AlertType, diff --git a/app/graphql/mutations/alert_management/create_alert_issue.rb b/app/graphql/mutations/alert_management/create_alert_issue.rb index 2ddb94700c2..2c128e1b339 100644 --- a/app/graphql/mutations/alert_management/create_alert_issue.rb +++ b/app/graphql/mutations/alert_management/create_alert_issue.rb @@ -10,6 +10,7 @@ module Mutations result = create_alert_issue(alert, current_user) track_usage_event(:incident_management_incident_created, current_user.id) + track_usage_event(:incident_management_alert_create_incident, current_user.id) prepare_response(alert, result) end diff --git a/app/graphql/mutations/alert_management/http_integration/destroy.rb b/app/graphql/mutations/alert_management/http_integration/destroy.rb index 0f478760aab..45d4bd778da 100644 --- a/app/graphql/mutations/alert_management/http_integration/destroy.rb +++ b/app/graphql/mutations/alert_management/http_integration/destroy.rb @@ -8,7 +8,7 @@ module Mutations argument :id, Types::GlobalIDType[::AlertManagement::HttpIntegration], required: true, - description: "The id of the integration to remove" + description: "The ID of the integration to remove" def resolve(id:) integration = authorized_find!(id: id) diff --git a/app/graphql/mutations/alert_management/http_integration/reset_token.rb b/app/graphql/mutations/alert_management/http_integration/reset_token.rb index eefab156825..3938b38260e 100644 --- a/app/graphql/mutations/alert_management/http_integration/reset_token.rb +++ b/app/graphql/mutations/alert_management/http_integration/reset_token.rb @@ -8,7 +8,7 @@ module Mutations argument :id, Types::GlobalIDType[::AlertManagement::HttpIntegration], required: true, - description: "The id of the integration to mutate" + description: "The ID of the integration to mutate" def resolve(id:) integration = authorized_find!(id: id) diff --git a/app/graphql/mutations/alert_management/http_integration/update.rb b/app/graphql/mutations/alert_management/http_integration/update.rb index 309c45b04ac..98e0f7eb14f 100644 --- a/app/graphql/mutations/alert_management/http_integration/update.rb +++ b/app/graphql/mutations/alert_management/http_integration/update.rb @@ -8,7 +8,7 @@ module Mutations argument :id, Types::GlobalIDType[::AlertManagement::HttpIntegration], required: true, - description: "The id of the integration to mutate" + description: "The ID of the integration to mutate" argument :name, GraphQL::STRING_TYPE, required: false, diff --git a/app/graphql/mutations/alert_management/prometheus_integration/reset_token.rb b/app/graphql/mutations/alert_management/prometheus_integration/reset_token.rb index 745ac51f6e3..effecd8364d 100644 --- a/app/graphql/mutations/alert_management/prometheus_integration/reset_token.rb +++ b/app/graphql/mutations/alert_management/prometheus_integration/reset_token.rb @@ -8,7 +8,7 @@ module Mutations argument :id, Types::GlobalIDType[::PrometheusService], required: true, - description: "The id of the integration to mutate" + description: "The ID of the integration to mutate" def resolve(id:) integration = authorized_find!(id: id) diff --git a/app/graphql/mutations/alert_management/prometheus_integration/update.rb b/app/graphql/mutations/alert_management/prometheus_integration/update.rb index 1f0dea119c5..46f4c23b739 100644 --- a/app/graphql/mutations/alert_management/prometheus_integration/update.rb +++ b/app/graphql/mutations/alert_management/prometheus_integration/update.rb @@ -8,7 +8,7 @@ module Mutations argument :id, Types::GlobalIDType[::PrometheusService], required: true, - description: "The id of the integration to mutate" + description: "The ID of the integration to mutate" argument :active, GraphQL::BOOLEAN_TYPE, required: false, diff --git a/app/graphql/mutations/award_emojis/add.rb b/app/graphql/mutations/award_emojis/add.rb index 856fdd5fb14..e7ee2ec4fad 100644 --- a/app/graphql/mutations/award_emojis/add.rb +++ b/app/graphql/mutations/award_emojis/add.rb @@ -8,8 +8,6 @@ module Mutations def resolve(args) awardable = authorized_find!(id: args[:awardable_id]) - check_object_is_awardable!(awardable) - service = ::AwardEmojis::AddService.new(awardable, args[:name], current_user).execute { diff --git a/app/graphql/mutations/award_emojis/base.rb b/app/graphql/mutations/award_emojis/base.rb index df6b883529e..4bd8304c3fc 100644 --- a/app/graphql/mutations/award_emojis/base.rb +++ b/app/graphql/mutations/award_emojis/base.rb @@ -3,12 +3,16 @@ module Mutations module AwardEmojis class Base < BaseMutation + include ::Mutations::FindsByGid + + NOT_EMOJI_AWARDABLE = 'You cannot award emoji to this resource.' + authorize :award_emoji argument :awardable_id, ::Types::GlobalIDType[::Awardable], required: true, - description: 'The global id of the awardable resource' + description: 'The global ID of the awardable resource' argument :name, GraphQL::STRING_TYPE, @@ -22,20 +26,15 @@ module Mutations private + # TODO: remove this method when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 def find_object(id:) - # TODO: remove this line when the compatibility layer is removed - # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 - id = ::Types::GlobalIDType[::Awardable].coerce_isolated_input(id) - GitlabSchema.find_by_gid(id) + super(id: ::Types::GlobalIDType[::Awardable].coerce_isolated_input(id)) end - # Called by mutations methods after performing an authorization check - # of an awardable object. - def check_object_is_awardable!(object) - unless object.is_a?(Awardable) && object.emoji_awardable? - raise Gitlab::Graphql::Errors::ResourceNotAvailable, - 'Cannot award emoji to this resource' - end + def authorize!(object) + super + raise_resource_not_available_error!(NOT_EMOJI_AWARDABLE) unless object.emoji_awardable? end end end diff --git a/app/graphql/mutations/award_emojis/remove.rb b/app/graphql/mutations/award_emojis/remove.rb index c654688c6dc..a9655daeea7 100644 --- a/app/graphql/mutations/award_emojis/remove.rb +++ b/app/graphql/mutations/award_emojis/remove.rb @@ -8,8 +8,6 @@ module Mutations def resolve(args) awardable = authorized_find!(id: args[:awardable_id]) - check_object_is_awardable!(awardable) - service = ::AwardEmojis::DestroyService.new(awardable, args[:name], current_user).execute { diff --git a/app/graphql/mutations/award_emojis/toggle.rb b/app/graphql/mutations/award_emojis/toggle.rb index 679ec7a14ff..e741f972b1b 100644 --- a/app/graphql/mutations/award_emojis/toggle.rb +++ b/app/graphql/mutations/award_emojis/toggle.rb @@ -12,8 +12,6 @@ module Mutations def resolve(args) awardable = authorized_find!(id: args[:awardable_id]) - check_object_is_awardable!(awardable) - service = ::AwardEmojis::ToggleService.new(awardable, args[:name], current_user).execute toggled_on = awardable.awarded_emoji?(args[:name], current_user) diff --git a/app/graphql/mutations/boards/common_mutation_arguments.rb b/app/graphql/mutations/boards/common_mutation_arguments.rb new file mode 100644 index 00000000000..c4f8d299318 --- /dev/null +++ b/app/graphql/mutations/boards/common_mutation_arguments.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Mutations + module Boards + module CommonMutationArguments + extend ActiveSupport::Concern + + included do + argument :name, + GraphQL::STRING_TYPE, + required: false, + description: 'The board name.' + argument :hide_backlog_list, + GraphQL::BOOLEAN_TYPE, + required: false, + description: copy_field_description(Types::BoardType, :hide_backlog_list) + argument :hide_closed_list, + GraphQL::BOOLEAN_TYPE, + required: false, + description: copy_field_description(Types::BoardType, :hide_closed_list) + end + end + end +end diff --git a/app/graphql/mutations/boards/create.rb b/app/graphql/mutations/boards/create.rb index ebbd19930ec..92bce557446 100644 --- a/app/graphql/mutations/boards/create.rb +++ b/app/graphql/mutations/boards/create.rb @@ -7,36 +7,18 @@ module Mutations graphql_name 'CreateBoard' + include Mutations::Boards::CommonMutationArguments + field :board, Types::BoardType, null: true, description: 'The board after mutation.' - argument :name, - GraphQL::STRING_TYPE, - required: false, - description: 'The board name.' - argument :assignee_id, - GraphQL::STRING_TYPE, - required: false, - description: 'The ID of the user to be assigned to the board.' - argument :milestone_id, - Types::GlobalIDType[Milestone], - required: false, - description: 'The ID of the milestone to be assigned to the board.' - argument :weight, - GraphQL::BOOLEAN_TYPE, - required: false, - description: 'The weight of the board.' - argument :label_ids, - [Types::GlobalIDType[Label]], - required: false, - description: 'The IDs of labels to be added to the board.' - authorize :admin_board def resolve(args) board_parent = authorized_resource_parent_find!(args) + response = ::Boards::CreateService.new(board_parent, current_user, args).execute { @@ -47,3 +29,5 @@ module Mutations end end end + +Mutations::Boards::Create.prepend_if_ee('::EE::Mutations::Boards::Create') diff --git a/app/graphql/mutations/boards/lists/create.rb b/app/graphql/mutations/boards/lists/create.rb index 3fe1052315f..f6df63365b2 100644 --- a/app/graphql/mutations/boards/lists/create.rb +++ b/app/graphql/mutations/boards/lists/create.rb @@ -27,30 +27,16 @@ module Mutations board = authorized_find!(id: args[:board_id]) params = create_list_params(args) - authorize_list_type_resource!(board, params) - - list = create_list(board, params) + response = create_list(board, params) { - list: list.valid? ? list : nil, - errors: errors_on_object(list) + list: response.success? ? response.payload[:list] : nil, + errors: response.errors } end private - # Overridden in EE - def authorize_list_type_resource!(board, params) - return unless params[:label_id] - - labels = ::Labels::AvailableLabelsService.new(current_user, board.resource_parent, params) - .filter_labels_ids_in_param(:label_id) - - unless labels.present? - raise Gitlab::Graphql::Errors::ArgumentError, 'Label not found!' - end - end - def create_list(board, params) create_list_service = ::Boards::Lists::CreateService.new(board.resource_parent, current_user, params) diff --git a/app/graphql/mutations/boards/update.rb b/app/graphql/mutations/boards/update.rb new file mode 100644 index 00000000000..5cb434e41fd --- /dev/null +++ b/app/graphql/mutations/boards/update.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Mutations + module Boards + class Update < ::Mutations::BaseMutation + graphql_name 'UpdateBoard' + + include Mutations::Boards::CommonMutationArguments + + argument :id, + ::Types::GlobalIDType[::Board], + required: true, + description: 'The board global ID.' + + field :board, + Types::BoardType, + null: true, + description: 'The board after mutation.' + + authorize :admin_board + + def resolve(id:, **args) + board = authorized_find!(id: id) + + ::Boards::UpdateService.new(board.resource_parent, current_user, args).execute(board) + + { + board: board, + errors: errors_on_object(board) + } + end + + def find_object(id:) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = ::Types::GlobalIDType[::Board].coerce_isolated_input(id) + GitlabSchema.find_by_gid(id) + end + end + end +end + +Mutations::Boards::Update.prepend_if_ee('::EE::Mutations::Boards::Update') diff --git a/app/graphql/mutations/ci/base.rb b/app/graphql/mutations/ci/base.rb index aaece2a3021..0ccee5661b7 100644 --- a/app/graphql/mutations/ci/base.rb +++ b/app/graphql/mutations/ci/base.rb @@ -7,7 +7,7 @@ module Mutations argument :id, PipelineID, required: true, - description: 'The id of the pipeline to mutate' + description: 'The ID of the pipeline to mutate' private diff --git a/app/graphql/mutations/concerns/mutations/finds_by_gid.rb b/app/graphql/mutations/concerns/mutations/finds_by_gid.rb new file mode 100644 index 00000000000..157f87a413d --- /dev/null +++ b/app/graphql/mutations/concerns/mutations/finds_by_gid.rb @@ -0,0 +1,9 @@ +# 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/container_repositories/destroy.rb b/app/graphql/mutations/container_repositories/destroy.rb index 8312193147f..90fba66e7b3 100644 --- a/app/graphql/mutations/container_repositories/destroy.rb +++ b/app/graphql/mutations/container_repositories/destroy.rb @@ -2,9 +2,7 @@ module Mutations module ContainerRepositories - class Destroy < Mutations::BaseMutation - include ::Mutations::PackageEventable - + class Destroy < ::Mutations::ContainerRepositories::DestroyBase graphql_name 'DestroyContainerRepository' authorize :destroy_container_image @@ -31,15 +29,6 @@ module Mutations errors: [] } end - - private - - def find_object(id:) - # TODO: remove this line when the compatibility layer is removed - # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 - id = ::Types::GlobalIDType[::ContainerRepository].coerce_isolated_input(id) - GitlabSchema.find_by_gid(id) - end end end end diff --git a/app/graphql/mutations/container_repositories/destroy_base.rb b/app/graphql/mutations/container_repositories/destroy_base.rb new file mode 100644 index 00000000000..ddaa6c52121 --- /dev/null +++ b/app/graphql/mutations/container_repositories/destroy_base.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Mutations + module ContainerRepositories + class DestroyBase < Mutations::BaseMutation + include ::Mutations::PackageEventable + + private + + def find_object(id:) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = ::Types::GlobalIDType[::ContainerRepository].coerce_isolated_input(id) + GitlabSchema.find_by_gid(id) + end + end + end +end diff --git a/app/graphql/mutations/container_repositories/destroy_tags.rb b/app/graphql/mutations/container_repositories/destroy_tags.rb new file mode 100644 index 00000000000..ca6a67867c3 --- /dev/null +++ b/app/graphql/mutations/container_repositories/destroy_tags.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Mutations + module ContainerRepositories + class DestroyTags < ::Mutations::ContainerRepositories::DestroyBase + LIMIT = 20.freeze + + TOO_MANY_TAGS_ERROR_MESSAGE = "Number of tags is greater than #{LIMIT}" + + graphql_name 'DestroyContainerRepositoryTags' + + authorize :destroy_container_image + + argument :id, + ::Types::GlobalIDType[::ContainerRepository], + required: true, + description: 'ID of the container repository.' + + argument :tag_names, + [GraphQL::STRING_TYPE], + required: true, + description: "Container repository tag(s) to delete. Total number can't be greater than #{LIMIT}", + prepare: ->(tag_names, _) do + raise Gitlab::Graphql::Errors::ArgumentError, TOO_MANY_TAGS_ERROR_MESSAGE if tag_names.size > LIMIT + + tag_names + end + + field :deleted_tag_names, + [GraphQL::STRING_TYPE], + description: 'Deleted container repository tags', + null: false + + def resolve(id:, tag_names:) + container_repository = authorized_find!(id: id) + + result = ::Projects::ContainerRepository::DeleteTagsService + .new(container_repository.project, current_user, tags: tag_names) + .execute(container_repository) + + track_event(:delete_tag_bulk, :tag) if result[:status] == :success + + { + errors: Array(result[:message]), + deleted_tag_names: result[:deleted] || [] + } + end + end + end +end diff --git a/app/graphql/mutations/design_management/base.rb b/app/graphql/mutations/design_management/base.rb index 918e5709b94..69fd22e46cd 100644 --- a/app/graphql/mutations/design_management/base.rb +++ b/app/graphql/mutations/design_management/base.rb @@ -11,7 +11,7 @@ module Mutations argument :iid, GraphQL::ID_TYPE, required: true, - description: "The iid of the issue to modify designs for" + description: "The IID of the issue to modify designs for" private diff --git a/app/graphql/mutations/discussions/toggle_resolve.rb b/app/graphql/mutations/discussions/toggle_resolve.rb index 4492da74706..0e3baf8d548 100644 --- a/app/graphql/mutations/discussions/toggle_resolve.rb +++ b/app/graphql/mutations/discussions/toggle_resolve.rb @@ -10,7 +10,7 @@ module Mutations argument :id, Types::GlobalIDType[Discussion], required: true, - description: 'The global id of the discussion' + description: 'The global ID of the discussion' argument :resolve, GraphQL::BOOLEAN_TYPE, diff --git a/app/graphql/mutations/environments/canary_ingress/update.rb b/app/graphql/mutations/environments/canary_ingress/update.rb new file mode 100644 index 00000000000..1798143053a --- /dev/null +++ b/app/graphql/mutations/environments/canary_ingress/update.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Mutations + module Environments + module CanaryIngress + class Update < ::Mutations::BaseMutation + graphql_name 'EnvironmentsCanaryIngressUpdate' + + authorize :update_environment + + argument :id, + ::Types::GlobalIDType[::Environment], + required: true, + description: 'The global ID of the environment to update' + + argument :weight, + GraphQL::INT_TYPE, + required: true, + description: 'The weight of the Canary Ingress' + + def resolve(id:, **kwargs) + environment = authorized_find!(id: id) + + result = ::Environments::CanaryIngress::UpdateService + .new(environment.project, current_user, kwargs) + .execute_async(environment) + + { errors: Array.wrap(result[:message]) } + end + + def find_object(id:) + # TODO: remove as part of https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = ::Types::GlobalIDType[::Environment].coerce_isolated_input(id) + GitlabSchema.find_by_gid(id) + end + end + end + end +end diff --git a/app/graphql/mutations/issues/update.rb b/app/graphql/mutations/issues/update.rb index 9b216b31f9b..d34e351b2a6 100644 --- a/app/graphql/mutations/issues/update.rb +++ b/app/graphql/mutations/issues/update.rb @@ -11,7 +11,7 @@ module Mutations required: false, description: copy_field_description(Types::IssueType, :title) - argument :milestone_id, GraphQL::ID_TYPE, + argument :milestone_id, GraphQL::ID_TYPE, # rubocop: disable Graphql/IDType required: false, description: 'The ID of the milestone to assign to the issue. On update milestone will be removed if set to null' diff --git a/app/graphql/mutations/merge_requests/base.rb b/app/graphql/mutations/merge_requests/base.rb index 96228855ace..57920259cf7 100644 --- a/app/graphql/mutations/merge_requests/base.rb +++ b/app/graphql/mutations/merge_requests/base.rb @@ -11,7 +11,7 @@ module Mutations argument :iid, GraphQL::STRING_TYPE, required: true, - description: "The iid of the merge request to mutate" + description: "The IID of the merge request to mutate" field :merge_request, Types::MergeRequestType, diff --git a/app/graphql/mutations/metrics/dashboard/annotations/create.rb b/app/graphql/mutations/metrics/dashboard/annotations/create.rb index b064f55825f..c2ec88c68ed 100644 --- a/app/graphql/mutations/metrics/dashboard/annotations/create.rb +++ b/app/graphql/mutations/metrics/dashboard/annotations/create.rb @@ -20,12 +20,12 @@ module Mutations argument :environment_id, ::Types::GlobalIDType[::Environment], required: false, - description: 'The global id of the environment to add an annotation to' + description: 'The global ID of the environment to add an annotation to' argument :cluster_id, ::Types::GlobalIDType[::Clusters::Cluster], required: false, - description: 'The global id of the cluster to add an annotation to' + description: 'The global ID of the cluster to add an annotation to' argument :starting_at, Types::TimeType, required: true, diff --git a/app/graphql/mutations/metrics/dashboard/annotations/delete.rb b/app/graphql/mutations/metrics/dashboard/annotations/delete.rb index d6731dfcafd..5d6763d8711 100644 --- a/app/graphql/mutations/metrics/dashboard/annotations/delete.rb +++ b/app/graphql/mutations/metrics/dashboard/annotations/delete.rb @@ -11,7 +11,7 @@ module Mutations argument :id, ::Types::GlobalIDType[::Metrics::Dashboard::Annotation], required: true, - description: 'The global ID of the annotation to delete' + description: 'Global ID of the annotation to delete' def resolve(id:) annotation = authorized_find!(id: id) diff --git a/app/graphql/mutations/notes/create/base.rb b/app/graphql/mutations/notes/create/base.rb index 3cfdaf84760..a1d81c62d91 100644 --- a/app/graphql/mutations/notes/create/base.rb +++ b/app/graphql/mutations/notes/create/base.rb @@ -11,7 +11,7 @@ module Mutations argument :noteable_id, ::Types::GlobalIDType[::Noteable], required: true, - description: 'The global id of the resource to add a note to' + description: 'The global ID of the resource to add a note to' argument :body, GraphQL::STRING_TYPE, diff --git a/app/graphql/mutations/notes/create/note.rb b/app/graphql/mutations/notes/create/note.rb index e97037171f7..f1cd3bddca8 100644 --- a/app/graphql/mutations/notes/create/note.rb +++ b/app/graphql/mutations/notes/create/note.rb @@ -9,7 +9,7 @@ module Mutations argument :discussion_id, ::Types::GlobalIDType[::Discussion], required: false, - description: 'The global id of the discussion this note is in reply to' + description: 'The global ID of the discussion this note is in reply to' private diff --git a/app/graphql/mutations/notes/destroy.rb b/app/graphql/mutations/notes/destroy.rb index 63e5eeb5ecf..0e6a215bf00 100644 --- a/app/graphql/mutations/notes/destroy.rb +++ b/app/graphql/mutations/notes/destroy.rb @@ -10,7 +10,7 @@ module Mutations argument :id, ::Types::GlobalIDType[::Note], required: true, - description: 'The global id of the note to destroy' + description: 'The global ID of the note to destroy' def resolve(id:) note = authorized_find!(id: id) diff --git a/app/graphql/mutations/notes/reposition_image_diff_note.rb b/app/graphql/mutations/notes/reposition_image_diff_note.rb index 0d88bcd9a30..15bfb361b13 100644 --- a/app/graphql/mutations/notes/reposition_image_diff_note.rb +++ b/app/graphql/mutations/notes/reposition_image_diff_note.rb @@ -16,7 +16,7 @@ module Mutations loads: Types::Notes::NoteType, as: :note, required: true, - description: 'The global id of the DiffNote to update' + description: 'The global ID of the DiffNote to update' argument :position, Types::Notes::UpdateDiffImagePositionInputType, diff --git a/app/graphql/mutations/notes/update/base.rb b/app/graphql/mutations/notes/update/base.rb index 1d5738ada77..42dac20f5d3 100644 --- a/app/graphql/mutations/notes/update/base.rb +++ b/app/graphql/mutations/notes/update/base.rb @@ -11,7 +11,7 @@ module Mutations argument :id, ::Types::GlobalIDType[::Note], required: true, - description: 'The global id of the note to update' + description: 'The global ID of the note to update' def resolve(args) note = authorized_find!(id: args[:id]) diff --git a/app/graphql/mutations/releases/create.rb b/app/graphql/mutations/releases/create.rb index 57c1541c368..156cd252848 100644 --- a/app/graphql/mutations/releases/create.rb +++ b/app/graphql/mutations/releases/create.rb @@ -40,12 +40,11 @@ module Mutations authorize :create_release - def resolve(project_path:, milestones: nil, assets: nil, **scalars) + def resolve(project_path:, assets: nil, **scalars) project = authorized_find!(full_path: project_path) params = { **scalars, - milestones: milestones.presence || [], assets: assets.to_h }.with_indifferent_access diff --git a/app/graphql/mutations/releases/delete.rb b/app/graphql/mutations/releases/delete.rb new file mode 100644 index 00000000000..e887b702cce --- /dev/null +++ b/app/graphql/mutations/releases/delete.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Mutations + module Releases + class Delete < Base + graphql_name 'ReleaseDelete' + + field :release, + Types::ReleaseType, + null: true, + description: 'The deleted release.' + + argument :tag_name, GraphQL::STRING_TYPE, + required: true, as: :tag, + description: 'Name of the tag associated with the release to delete.' + + authorize :destroy_release + + def resolve(project_path:, tag:) + project = authorized_find!(full_path: project_path) + + params = { tag: tag }.with_indifferent_access + + result = ::Releases::DestroyService.new(project, current_user, params).execute + + if result[:status] == :success + { + release: result[:release], + errors: [] + } + else + { + release: nil, + errors: [result[:message]] + } + end + end + end + end +end diff --git a/app/graphql/mutations/releases/update.rb b/app/graphql/mutations/releases/update.rb new file mode 100644 index 00000000000..bf72b907679 --- /dev/null +++ b/app/graphql/mutations/releases/update.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Mutations + module Releases + class Update < Base + graphql_name 'ReleaseUpdate' + + field :release, + Types::ReleaseType, + null: true, + description: 'The release after mutation.' + + argument :tag_name, GraphQL::STRING_TYPE, + required: true, as: :tag, + description: 'Name of the tag associated with the release' + + argument :name, GraphQL::STRING_TYPE, + required: false, + description: 'Name of the release' + + argument :description, GraphQL::STRING_TYPE, + required: false, + description: 'Description (release notes) of the release' + + argument :released_at, Types::TimeType, + required: false, + description: 'The release date' + + argument :milestones, [GraphQL::STRING_TYPE], + required: false, + description: 'The title of each milestone the release is associated with. GitLab Premium customers can specify group milestones.' + + authorize :update_release + + def ready?(**args) + if args.key?(:released_at) && args[:released_at].nil? + raise Gitlab::Graphql::Errors::ArgumentError, + 'if the releasedAt argument is provided, it cannot be null' + end + + if args.key?(:milestones) && args[:milestones].nil? + raise Gitlab::Graphql::Errors::ArgumentError, + 'if the milestones argument is provided, it cannot be null' + end + + super + end + + def resolve(project_path:, **scalars) + project = authorized_find!(full_path: project_path) + + params = scalars.with_indifferent_access + + release_result = ::Releases::UpdateService.new(project, current_user, params).execute + + if release_result[:status] == :success + { + release: release_result[:release], + errors: [] + } + else + { + release: nil, + errors: [release_result[:message]] + } + end + end + end + end +end diff --git a/app/graphql/mutations/snippets/create.rb b/app/graphql/mutations/snippets/create.rb index 37c0f80310c..56c3b398949 100644 --- a/app/graphql/mutations/snippets/create.rb +++ b/app/graphql/mutations/snippets/create.rb @@ -4,7 +4,8 @@ module Mutations module Snippets class Create < BaseMutation include SpammableMutationFields - include ResolvesProject + + authorize :create_snippet graphql_name 'CreateSnippet' @@ -37,17 +38,15 @@ module Mutations description: 'Actions to perform over the snippet repository and blobs', required: false - def resolve(args) - project_path = args.delete(:project_path) - + def resolve(project_path: nil, **args) if project_path.present? - project = find_project!(project_path: project_path) - elsif !can_create_personal_snippet? - raise_resource_not_available_error! + project = authorized_find!(project_path) + else + authorize!(:global) end service_response = ::Snippets::CreateService.new(project, - context[:current_user], + current_user, create_params(args)).execute snippet = service_response.payload[:snippet] @@ -67,20 +66,8 @@ module Mutations private - def find_project!(project_path:) - authorized_find!(full_path: project_path) - end - - def find_object(full_path:) - resolve_project(full_path: full_path) - end - - def authorized_resource?(project) - Ability.allowed?(context[:current_user], :create_snippet, project) - end - - def can_create_personal_snippet? - Ability.allowed?(context[:current_user], :create_snippet) + def find_object(full_path) + Project.find_by_full_path(full_path) end def create_params(args) diff --git a/app/graphql/mutations/snippets/destroy.rb b/app/graphql/mutations/snippets/destroy.rb index 4915d7dd77a..bee6503372d 100644 --- a/app/graphql/mutations/snippets/destroy.rb +++ b/app/graphql/mutations/snippets/destroy.rb @@ -9,7 +9,7 @@ module Mutations argument :id, ::Types::GlobalIDType[::Snippet], required: true, - description: 'The global id of the snippet to destroy' + description: 'The global ID of the snippet to destroy' def resolve(id:) snippet = authorized_find!(id: id) diff --git a/app/graphql/mutations/snippets/mark_as_spam.rb b/app/graphql/mutations/snippets/mark_as_spam.rb index d6b96c699c0..2d6fea1f5ec 100644 --- a/app/graphql/mutations/snippets/mark_as_spam.rb +++ b/app/graphql/mutations/snippets/mark_as_spam.rb @@ -7,7 +7,7 @@ module Mutations argument :id, ::Types::GlobalIDType[::Snippet], required: true, - description: 'The global id of the snippet to update' + description: 'The global ID of the snippet to update' def resolve(id:) snippet = authorized_find!(id: id) @@ -23,7 +23,7 @@ module Mutations private def mark_as_spam(snippet) - Spam::MarkAsSpamService.new(spammable: snippet).execute + Spam::MarkAsSpamService.new(target: snippet).execute end def authorized_resource?(snippet) diff --git a/app/graphql/mutations/snippets/update.rb b/app/graphql/mutations/snippets/update.rb index bcaa807e4c1..6df1ad6d8b9 100644 --- a/app/graphql/mutations/snippets/update.rb +++ b/app/graphql/mutations/snippets/update.rb @@ -9,7 +9,7 @@ module Mutations argument :id, ::Types::GlobalIDType[::Snippet], required: true, - description: 'The global id of the snippet to update' + description: 'The global ID of the snippet to update' argument :title, GraphQL::STRING_TYPE, required: false, @@ -27,11 +27,11 @@ module Mutations description: 'Actions to perform over the snippet repository and blobs', required: false - def resolve(args) - snippet = authorized_find!(id: args.delete(:id)) + def resolve(id:, **args) + snippet = authorized_find!(id: id) result = ::Snippets::UpdateService.new(snippet.project, - context[:current_user], + current_user, update_params(args)).execute(snippet) snippet = result.payload[:snippet] diff --git a/app/graphql/mutations/todos/mark_done.rb b/app/graphql/mutations/todos/mark_done.rb index 3d73022f266..2ae50846108 100644 --- a/app/graphql/mutations/todos/mark_done.rb +++ b/app/graphql/mutations/todos/mark_done.rb @@ -10,7 +10,7 @@ module Mutations argument :id, ::Types::GlobalIDType[::Todo], required: true, - description: 'The global id of the todo to mark as done' + description: 'The global ID of the todo to mark as done' field :todo, Types::TodoType, null: false, diff --git a/app/graphql/mutations/todos/restore.rb b/app/graphql/mutations/todos/restore.rb index 7c8f92d32f5..c532b455a16 100644 --- a/app/graphql/mutations/todos/restore.rb +++ b/app/graphql/mutations/todos/restore.rb @@ -10,7 +10,7 @@ module Mutations argument :id, ::Types::GlobalIDType[::Todo], required: true, - description: 'The global id of the todo to restore' + description: 'The global ID of the todo to restore' field :todo, Types::TodoType, null: false, diff --git a/app/graphql/mutations/todos/restore_many.rb b/app/graphql/mutations/todos/restore_many.rb index 9e0a95c48ec..59965589856 100644 --- a/app/graphql/mutations/todos/restore_many.rb +++ b/app/graphql/mutations/todos/restore_many.rb @@ -10,11 +10,11 @@ module Mutations argument :ids, [::Types::GlobalIDType[::Todo]], required: true, - description: 'The global ids of the todos to restore (a maximum of 50 is supported at once)' + description: 'The global IDs of the todos to restore (a maximum of 50 is supported at once)' field :updated_ids, [::Types::GlobalIDType[Todo]], null: false, - description: 'The ids of the updated todo items', + description: 'The IDs of the updated todo items', deprecated: { reason: 'Use todos', milestone: '13.2' } field :todos, [::Types::TodoType], diff --git a/app/graphql/queries/epic/epic_children.query.graphql b/app/graphql/queries/epic/epic_children.query.graphql new file mode 100644 index 00000000000..c12778109d0 --- /dev/null +++ b/app/graphql/queries/epic/epic_children.query.graphql @@ -0,0 +1,126 @@ +fragment PageInfo on PageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor +} + +fragment RelatedTreeBaseEpic on Epic { + id + iid + title + webPath + relativePosition + userPermissions { + __typename + adminEpic + createEpic + } + descendantCounts { + __typename + openedEpics + closedEpics + openedIssues + closedIssues + } + healthStatus { + __typename + issuesAtRisk + issuesOnTrack + issuesNeedingAttention + } +} + +fragment EpicNode on Epic { + ...RelatedTreeBaseEpic + state + reference(full: true) + relationPath + createdAt + closedAt + hasChildren + hasIssues + group { + __typename + fullPath + } +} + +query childItems( + $fullPath: ID! + $iid: ID + $pageSize: Int = 100 + $epicEndCursor: String = "" + $issueEndCursor: String = "" +) { + group(fullPath: $fullPath) { + __typename + id + path + fullPath + epic(iid: $iid) { + __typename + ...RelatedTreeBaseEpic + children(first: $pageSize, after: $epicEndCursor) { + __typename + edges { + __typename + node { + __typename + ...EpicNode + } + } + pageInfo { + __typename + ...PageInfo + } + } + issues(first: $pageSize, after: $issueEndCursor) { + __typename + edges { + __typename + node { + __typename + iid + epicIssueId + title + closedAt + state + createdAt + confidential + dueDate + weight + webPath + reference(full: true) + relationPath + relativePosition + assignees { + __typename + edges { + __typename + node { + __typename + webUrl + name + username + avatarUrl + } + } + } + milestone { + __typename + title + startDate + dueDate + } + healthStatus + } + } + pageInfo { + __typename + ...PageInfo + } + } + } + } +} diff --git a/app/graphql/queries/epic/epic_details.query.graphql b/app/graphql/queries/epic/epic_details.query.graphql new file mode 100644 index 00000000000..406d630b180 --- /dev/null +++ b/app/graphql/queries/epic/epic_details.query.graphql @@ -0,0 +1,20 @@ +query epicDetails($fullPath: ID!, $iid: ID!) { + group(fullPath: $fullPath) { + __typename + epic(iid: $iid) { + __typename + participants { + __typename + edges { + __typename + node { + __typename + name + avatarUrl + webUrl + } + } + } + } + } +} diff --git a/app/graphql/resolvers/alert_management/alert_resolver.rb b/app/graphql/resolvers/alert_management/alert_resolver.rb index c3219d9cdc3..b115bd80113 100644 --- a/app/graphql/resolvers/alert_management/alert_resolver.rb +++ b/app/graphql/resolvers/alert_management/alert_resolver.rb @@ -18,6 +18,11 @@ module Resolvers description: 'Sort alerts by this criteria', required: false + argument :domain, Types::AlertManagement::DomainFilterEnum, + description: 'Filter query for given domain', + required: true, + default_value: 'operations' + argument :search, GraphQL::STRING_TYPE, description: 'Search query for title, description, service, or monitoring_tool.', required: false diff --git a/app/graphql/resolvers/assigned_merge_requests_resolver.rb b/app/graphql/resolvers/assigned_merge_requests_resolver.rb index 30415ef5d2d..385f8db51b0 100644 --- a/app/graphql/resolvers/assigned_merge_requests_resolver.rb +++ b/app/graphql/resolvers/assigned_merge_requests_resolver.rb @@ -4,6 +4,7 @@ module Resolvers class AssignedMergeRequestsResolver < UserMergeRequestsResolverBase type ::Types::MergeRequestType.connection_type, null: true accept_author + accept_reviewer def user_role :assignee diff --git a/app/graphql/resolvers/authored_merge_requests_resolver.rb b/app/graphql/resolvers/authored_merge_requests_resolver.rb index 1426ca83c06..4de1046ce0d 100644 --- a/app/graphql/resolvers/authored_merge_requests_resolver.rb +++ b/app/graphql/resolvers/authored_merge_requests_resolver.rb @@ -4,6 +4,7 @@ module Resolvers class AuthoredMergeRequestsResolver < UserMergeRequestsResolverBase type ::Types::MergeRequestType.connection_type, null: true accept_assignee + accept_reviewer def user_role :author diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb index 87a63231b22..539e37db1c2 100644 --- a/app/graphql/resolvers/base_resolver.rb +++ b/app/graphql/resolvers/base_resolver.rb @@ -8,6 +8,14 @@ module Resolvers argument_class ::Types::BaseArgument + def self.requires_argument! + @requires_argument = true + end + + def self.field_options + super.merge(requires_argument: @requires_argument) + end + def self.singular_type return unless type @@ -109,6 +117,10 @@ module Resolvers [args[:iid], args[:iids]].any? ? 0 : 0.01 end + def offset_pagination(relation) + ::Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection.new(relation) + end + override :object def object super.tap do |obj| diff --git a/app/graphql/resolvers/board_list_issues_resolver.rb b/app/graphql/resolvers/board_list_issues_resolver.rb index 3421e1024c0..3e4a5a3cb70 100644 --- a/app/graphql/resolvers/board_list_issues_resolver.rb +++ b/app/graphql/resolvers/board_list_issues_resolver.rb @@ -16,7 +16,7 @@ module Resolvers filter_params = issue_filters(args[:filters]).merge(board_id: list.board.id, id: list.id) service = ::Boards::Issues::ListService.new(list.board.resource_parent, context[:current_user], filter_params) - Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection.new(service.execute) + offset_pagination(service.execute) end # https://gitlab.com/gitlab-org/gitlab/-/issues/235681 diff --git a/app/graphql/resolvers/board_lists_resolver.rb b/app/graphql/resolvers/board_lists_resolver.rb index ef12dfa19ff..35d938c50be 100644 --- a/app/graphql/resolvers/board_lists_resolver.rb +++ b/app/graphql/resolvers/board_lists_resolver.rb @@ -3,9 +3,13 @@ module Resolvers class BoardListsResolver < BaseResolver include BoardIssueFilterable + prepend ManualAuthorization include Gitlab::Graphql::Authorize::AuthorizeResource type Types::BoardListType, null: true + extras [:lookahead] + + authorize :read_list argument :id, Types::GlobalIDType[List], required: false, @@ -27,7 +31,7 @@ module Resolvers List.preload_preferences_for_user(lists, context[:current_user]) end - Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection.new(lists) + offset_pagination(lists) end private @@ -42,10 +46,6 @@ module Resolvers service.execute(board, create_default_lists: false) end - def authorized_resource?(board) - Ability.allowed?(context[:current_user], :read_list, board) - end - def load_preferences?(lookahead) lookahead&.selection(:edges)&.selection(:node)&.selects?(:collapsed) end diff --git a/app/graphql/resolvers/ci/config_resolver.rb b/app/graphql/resolvers/ci/config_resolver.rb new file mode 100644 index 00000000000..d6e7c206691 --- /dev/null +++ b/app/graphql/resolvers/ci/config_resolver.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module Resolvers + module Ci + class ConfigResolver < BaseResolver + type Types::Ci::Config::ConfigType, null: true + + argument :content, GraphQL::STRING_TYPE, + required: true, + description: 'Contents of .gitlab-ci.yml' + + def resolve(content:) + result = ::Gitlab::Ci::YamlProcessor.new(content).execute + + response = if result.errors.empty? + { + status: :valid, + errors: [], + stages: make_stages(result.jobs) + } + else + { + status: :invalid, + errors: result.errors + } + end + + response.merge(merged_yaml: result.merged_yaml) + end + + private + + def make_jobs(config_jobs) + config_jobs.map do |job_name, job| + { + name: job_name, + stage: job[:stage], + group_name: CommitStatus.new(name: job_name).group_name, + needs: job.dig(:needs, :job) || [] + } + end + end + + def make_groups(job_data) + jobs = make_jobs(job_data) + + jobs_by_group = jobs.group_by { |job| job[:group_name] } + jobs_by_group.map do |name, jobs| + { jobs: jobs, name: name, stage: jobs.first[:stage], size: jobs.size } + end + end + + def make_stages(jobs) + make_groups(jobs) + .group_by { |group| group[:stage] } + .map { |name, groups| { name: name, groups: groups } } + end + end + end +end diff --git a/app/graphql/resolvers/ci/jobs_resolver.rb b/app/graphql/resolvers/ci/jobs_resolver.rb index 8a9ae42b375..2c4911748a5 100644 --- a/app/graphql/resolvers/ci/jobs_resolver.rb +++ b/app/graphql/resolvers/ci/jobs_resolver.rb @@ -5,6 +5,8 @@ module Resolvers class JobsResolver < BaseResolver alias_method :pipeline, :object + type ::Types::Ci::JobType.connection_type, null: true + argument :security_report_types, [Types::Security::ReportTypeEnum], required: false, description: 'Filter jobs by the type of security report they produce' diff --git a/app/graphql/resolvers/ci/pipeline_stages_resolver.rb b/app/graphql/resolvers/ci/pipeline_stages_resolver.rb index f9817d8b97b..98170e0cd2e 100644 --- a/app/graphql/resolvers/ci/pipeline_stages_resolver.rb +++ b/app/graphql/resolvers/ci/pipeline_stages_resolver.rb @@ -5,6 +5,9 @@ module Resolvers class PipelineStagesResolver < BaseResolver include LooksAhead + type Types::Ci::StageType.connection_type, null: true + extras [:lookahead] + alias_method :pipeline, :object def resolve_with_lookahead diff --git a/app/graphql/resolvers/ci/runner_setup_resolver.rb b/app/graphql/resolvers/ci/runner_setup_resolver.rb index 241cd57f74b..f68d71174c3 100644 --- a/app/graphql/resolvers/ci/runner_setup_resolver.rb +++ b/app/graphql/resolvers/ci/runner_setup_resolver.rb @@ -23,7 +23,10 @@ module Resolvers def resolve(platform:, architecture:, **args) instructions = Gitlab::Ci::RunnerInstructions.new( - { current_user: current_user, os: platform, arch: architecture }.merge(target_param(args)) + current_user: current_user, + os: platform, + arch: architecture, + **target_param(args) ) { diff --git a/app/graphql/resolvers/concerns/caching_array_resolver.rb b/app/graphql/resolvers/concerns/caching_array_resolver.rb index 4f2c8b98928..e7555dcf42c 100644 --- a/app/graphql/resolvers/concerns/caching_array_resolver.rb +++ b/app/graphql/resolvers/concerns/caching_array_resolver.rb @@ -43,8 +43,10 @@ # (i.e. `resolve(**args).sync == query_for(query_input(**args)).to_a`). # # Classes may implement: -# - #item_found(A, R) (return value is ignored) # - max_union_size Integer (the maximum number of queries to run in any one union) +# - preload -> Preloads|NilClass (a set of preloads to apply to each query) +# - #item_found(A, R) (return value is ignored) +# - allowed?(R) -> Boolean (if this method returns false, the value is not resolved) module CachingArrayResolver MAX_UNION_SIZE = 50 @@ -62,6 +64,7 @@ module CachingArrayResolver queries.in_groups_of(max_union_size, false).each do |group| by_id = model_class .from_union(tag(group), remove_duplicates: false) + .preload(preload) # rubocop: disable CodeReuse/ActiveRecord .group_by { |r| r[primary_key] } by_id.values.each do |item_group| @@ -75,6 +78,16 @@ module CachingArrayResolver end end + # Override to apply filters on a per-item basis + def allowed?(item) + true + end + + # Override to specify preloads for each query + def preload + nil + end + # Override this to intercept the items once they are found def item_found(query_input, item) end @@ -94,6 +107,8 @@ module CachingArrayResolver end def found(loader, key, value) + return unless allowed?(value) + loader.call(key) do |vs| item_found(key, value) vs << value diff --git a/app/graphql/resolvers/concerns/manual_authorization.rb b/app/graphql/resolvers/concerns/manual_authorization.rb new file mode 100644 index 00000000000..182110b9594 --- /dev/null +++ b/app/graphql/resolvers/concerns/manual_authorization.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# TODO: remove this entirely when framework authorization is released +# See: https://gitlab.com/gitlab-org/gitlab/-/issues/290216 +module ManualAuthorization + def resolve(**args) + super + rescue ::Gitlab::Graphql::Errors::ResourceNotAvailable + nil + end +end diff --git a/app/graphql/resolvers/design_management/design_resolver.rb b/app/graphql/resolvers/design_management/design_resolver.rb index e0a68bae397..b60c14ca835 100644 --- a/app/graphql/resolvers/design_management/design_resolver.rb +++ b/app/graphql/resolvers/design_management/design_resolver.rb @@ -5,6 +5,8 @@ module Resolvers class DesignResolver < BaseResolver type ::Types::DesignManagement::DesignType, null: true + requires_argument! + argument :id, ::Types::GlobalIDType[::DesignManagement::Design], required: false, description: 'Find a design by its ID' diff --git a/app/graphql/resolvers/design_management/version/design_at_version_resolver.rb b/app/graphql/resolvers/design_management/version/design_at_version_resolver.rb index 70021057f71..49a4974bfbf 100644 --- a/app/graphql/resolvers/design_management/version/design_at_version_resolver.rb +++ b/app/graphql/resolvers/design_management/version/design_at_version_resolver.rb @@ -12,6 +12,8 @@ module Resolvers type Types::DesignManagement::DesignAtVersionType, null: true + requires_argument! + authorize :read_design argument :id, DesignAtVersionID, diff --git a/app/graphql/resolvers/design_management/version_in_collection_resolver.rb b/app/graphql/resolvers/design_management/version_in_collection_resolver.rb index ecd7ab3ee45..7d20cfc2c8e 100644 --- a/app/graphql/resolvers/design_management/version_in_collection_resolver.rb +++ b/app/graphql/resolvers/design_management/version_in_collection_resolver.rb @@ -7,6 +7,8 @@ module Resolvers type Types::DesignManagement::VersionType, null: true + requires_argument! + authorize :read_design alias_method :collection, :object diff --git a/app/graphql/resolvers/design_management/versions_resolver.rb b/app/graphql/resolvers/design_management/versions_resolver.rb index 23858c8e991..3c718a631db 100644 --- a/app/graphql/resolvers/design_management/versions_resolver.rb +++ b/app/graphql/resolvers/design_management/versions_resolver.rb @@ -9,6 +9,8 @@ module Resolvers VersionID = ::Types::GlobalIDType[::DesignManagement::Version] + extras [:parent] + argument :earlier_or_equal_to_sha, GraphQL::STRING_TYPE, as: :sha, required: false, diff --git a/app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb b/app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb index 669b487db10..13b5672d750 100644 --- a/app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb +++ b/app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb @@ -3,6 +3,8 @@ module Resolvers module ErrorTracking class SentryErrorStackTraceResolver < BaseResolver + type Types::ErrorTracking::SentryErrorStackTraceType, null: true + argument :id, ::Types::GlobalIDType[::Gitlab::ErrorTracking::DetailedError], required: true, description: 'ID of the Sentry issue' diff --git a/app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb b/app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb index c5cf924ce7f..e844ffedbeb 100644 --- a/app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb +++ b/app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb @@ -4,19 +4,26 @@ module Resolvers module ErrorTracking class SentryErrorsResolver < BaseResolver type Types::ErrorTracking::SentryErrorType.connection_type, null: true + extension Gitlab::Graphql::Extensions::ExternallyPaginatedArrayExtension + + argument :search_term, ::GraphQL::STRING_TYPE, + description: 'Search query for the Sentry error details', + required: false + + # TODO: convert to Enum + argument :sort, ::GraphQL::STRING_TYPE, + description: 'Attribute to sort on. Options are frequency, first_seen, last_seen. last_seen is default', + required: false + + delegate :project, to: :object def resolve(**args) args[:cursor] = args.delete(:after) - project = object.project - result = ::ErrorTracking::ListIssuesService.new( - project, - context[:current_user], - args - ).execute + result = ::ErrorTracking::ListIssuesService.new(project, current_user, args).execute - next_cursor = result[:pagination]&.dig('next', 'cursor') - previous_cursor = result[:pagination]&.dig('previous', 'cursor') + next_cursor = result.dig(:pagination, 'next', 'cursor') + previous_cursor = result.dig(:pagination, 'previous', 'cursor') issues = result[:issues] # ReactiveCache is still fetching data @@ -24,6 +31,10 @@ module Resolvers Gitlab::Graphql::ExternallyPaginatedArray.new(previous_cursor, next_cursor, *issues) end + + def self.field_options + super.merge(connection: false) # we manage the pagination manually, so opt out of the connection field extension + end end end end diff --git a/app/graphql/resolvers/group_members_resolver.rb b/app/graphql/resolvers/group_members_resolver.rb index d3aa376c29c..fcdf7c01d8b 100644 --- a/app/graphql/resolvers/group_members_resolver.rb +++ b/app/graphql/resolvers/group_members_resolver.rb @@ -6,6 +6,11 @@ module Resolvers authorize :read_group_member + argument :relations, [Types::GroupMemberRelationEnum], + description: 'Filter members by the given member relations', + required: false, + default_value: GroupMembersFinder::DEFAULT_RELATIONS + private def preloads diff --git a/app/graphql/resolvers/issue_status_counts_resolver.rb b/app/graphql/resolvers/issue_status_counts_resolver.rb index 5d0d5693244..58cff559d0d 100644 --- a/app/graphql/resolvers/issue_status_counts_resolver.rb +++ b/app/graphql/resolvers/issue_status_counts_resolver.rb @@ -6,6 +6,8 @@ module Resolvers type Types::IssueStatusCountsType, null: true + extras [:lookahead] + def continue_issue_resolve(parent, finder, **args) finder.parent_param = parent apply_lookahead(Gitlab::IssuablesCountForState.new(finder, parent)) diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb index dd35219454f..ae27cce9113 100644 --- a/app/graphql/resolvers/issues_resolver.rb +++ b/app/graphql/resolvers/issues_resolver.rb @@ -10,7 +10,7 @@ module Resolvers argument :sort, Types::IssueSortEnum, description: 'Sort issues by this criteria', required: false, - default_value: 'created_desc' + default_value: :created_desc type Types::IssueType.connection_type, null: true @@ -24,7 +24,7 @@ module Resolvers if non_stable_cursor_sort?(args[:sort]) # Certain complex sorts are not supported by the stable cursor pagination yet. # In these cases, we use offset pagination, so we return the correct connection. - Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection.new(issues) + offset_pagination(issues) else issues end diff --git a/app/graphql/resolvers/members_resolver.rb b/app/graphql/resolvers/members_resolver.rb index 523642e912f..cf51fd298bd 100644 --- a/app/graphql/resolvers/members_resolver.rb +++ b/app/graphql/resolvers/members_resolver.rb @@ -14,7 +14,9 @@ module Resolvers def resolve_with_lookahead(**args) authorize!(object) - apply_lookahead(finder_class.new(object, current_user, params: args).execute) + relations = args.delete(:relations) + + apply_lookahead(finder_class.new(object, current_user, params: args).execute(include_relations: relations)) end private diff --git a/app/graphql/resolvers/merge_request_pipelines_resolver.rb b/app/graphql/resolvers/merge_request_pipelines_resolver.rb index 6590dfdc78c..f84eedb4c3b 100644 --- a/app/graphql/resolvers/merge_request_pipelines_resolver.rb +++ b/app/graphql/resolvers/merge_request_pipelines_resolver.rb @@ -5,14 +5,32 @@ module Resolvers class MergeRequestPipelinesResolver < BaseResolver # The GraphQL type here gets defined in this include include ::ResolvesPipelines + include ::CachingArrayResolver alias_method :merge_request, :object + # Return at most 500 pipelines for each MR. + # Merge requests generally have many fewer pipelines than this. + def self.field_options + super.merge(max_page_size: 500) + end + def resolve(**args) return unless project - resolve_pipelines(project, args) - .merge(merge_request.all_pipelines) + super + end + + def query_for(args) + resolve_pipelines(project, args).merge(merge_request.all_pipelines) + end + + def model_class + ::Ci::Pipeline + end + + def query_input(**args) + args end def project diff --git a/app/graphql/resolvers/merge_requests_resolver.rb b/app/graphql/resolvers/merge_requests_resolver.rb index cb4a76243ae..98c95565778 100644 --- a/app/graphql/resolvers/merge_requests_resolver.rb +++ b/app/graphql/resolvers/merge_requests_resolver.rb @@ -4,6 +4,8 @@ module Resolvers class MergeRequestsResolver < BaseResolver include ResolvesMergeRequests + type ::Types::MergeRequestType.connection_type, null: true + alias_method :project, :synchronized_object def self.accept_assignee @@ -18,6 +20,12 @@ module Resolvers description: 'Username of the author' end + def self.accept_reviewer + argument :reviewer_username, GraphQL::STRING_TYPE, + required: false, + description: 'Username of the reviewer' + end + argument :iids, [GraphQL::STRING_TYPE], required: false, description: 'Array of IIDs of merge requests, for example `[1, 2]`' @@ -52,7 +60,7 @@ module Resolvers argument :sort, Types::MergeRequestSortEnum, description: 'Sort merge requests by this criteria', required: false, - default_value: 'created_desc' + default_value: :created_desc def self.single ::Resolvers::MergeRequestResolver diff --git a/app/graphql/resolvers/project_members_resolver.rb b/app/graphql/resolvers/project_members_resolver.rb index e64e8b845a5..659b12c2563 100644 --- a/app/graphql/resolvers/project_members_resolver.rb +++ b/app/graphql/resolvers/project_members_resolver.rb @@ -5,6 +5,11 @@ module Resolvers class ProjectMembersResolver < MembersResolver authorize :read_project_member + argument :relations, [Types::ProjectMemberRelationEnum], + description: 'Filter members by the given member relations', + required: false, + default_value: MembersFinder::DEFAULT_RELATIONS + private def finder_class diff --git a/app/graphql/resolvers/project_merge_requests_resolver.rb b/app/graphql/resolvers/project_merge_requests_resolver.rb index bf082c0b182..830649d5e52 100644 --- a/app/graphql/resolvers/project_merge_requests_resolver.rb +++ b/app/graphql/resolvers/project_merge_requests_resolver.rb @@ -5,5 +5,6 @@ module Resolvers type ::Types::MergeRequestType.connection_type, null: true accept_assignee accept_author + accept_reviewer end end diff --git a/app/graphql/resolvers/project_pipeline_resolver.rb b/app/graphql/resolvers/project_pipeline_resolver.rb index 4cf47dbdc60..8bf4e0b08ef 100644 --- a/app/graphql/resolvers/project_pipeline_resolver.rb +++ b/app/graphql/resolvers/project_pipeline_resolver.rb @@ -12,7 +12,9 @@ module Resolvers def resolve(iid:) BatchLoader::GraphQL.for(iid).batch(key: project) do |iids, loader, args| - args[:key].all_pipelines.for_iid(iids).each { |pl| loader.call(pl.iid.to_s, pl) } + finder = ::Ci::PipelinesFinder.new(project, context[:current_user], iids: iids) + + finder.execute.each { |pipeline| loader.call(pipeline.iid.to_s, pipeline) } end end end diff --git a/app/graphql/resolvers/project_pipeline_statistics_resolver.rb b/app/graphql/resolvers/project_pipeline_statistics_resolver.rb new file mode 100644 index 00000000000..29ab9402f5b --- /dev/null +++ b/app/graphql/resolvers/project_pipeline_statistics_resolver.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Resolvers + class ProjectPipelineStatisticsResolver < BaseResolver + type Types::Ci::AnalyticsType, null: true + + def resolve + weekly_stats = Gitlab::Ci::Charts::WeekChart.new(object) + monthly_stats = Gitlab::Ci::Charts::MonthChart.new(object) + yearly_stats = Gitlab::Ci::Charts::YearChart.new(object) + pipeline_times = Gitlab::Ci::Charts::PipelineTime.new(object) + + { + week_pipelines_labels: weekly_stats.labels, + week_pipelines_totals: weekly_stats.total, + week_pipelines_successful: weekly_stats.success, + month_pipelines_labels: monthly_stats.labels, + month_pipelines_totals: monthly_stats.total, + month_pipelines_successful: monthly_stats.success, + year_pipelines_labels: yearly_stats.labels, + year_pipelines_totals: yearly_stats.total, + year_pipelines_successful: yearly_stats.success, + pipeline_times_labels: pipeline_times.labels, + pipeline_times_values: pipeline_times.pipeline_times + } + end + end +end diff --git a/app/graphql/resolvers/projects/jira_imports_resolver.rb b/app/graphql/resolvers/projects/jira_imports_resolver.rb deleted file mode 100644 index efd45c2c465..00000000000 --- a/app/graphql/resolvers/projects/jira_imports_resolver.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module Resolvers - module Projects - class JiraImportsResolver < BaseResolver - type Types::JiraImportType.connection_type, null: true - - include Gitlab::Graphql::Authorize::AuthorizeResource - - alias_method :project, :object - - def resolve(**args) - authorize!(project) - - project.jira_imports - end - - def authorized_resource?(project) - context[:current_user].present? && Ability.allowed?(context[:current_user], :read_project, project) - end - end - end -end diff --git a/app/graphql/resolvers/projects/services_resolver.rb b/app/graphql/resolvers/projects/services_resolver.rb index 17d81e21c28..4f5a6cddbb3 100644 --- a/app/graphql/resolvers/projects/services_resolver.rb +++ b/app/graphql/resolvers/projects/services_resolver.rb @@ -3,9 +3,11 @@ module Resolvers module Projects class ServicesResolver < BaseResolver + prepend ManualAuthorization include Gitlab::Graphql::Authorize::AuthorizeResource type Types::Projects::ServiceType.connection_type, null: true + authorize :admin_project argument :active, GraphQL::BOOLEAN_TYPE, @@ -24,10 +26,6 @@ module Resolvers services(args[:active], args[:type]) end - def authorized_resource?(project) - Ability.allowed?(context[:current_user], :admin_project, project) - end - private def services(active, type) diff --git a/app/graphql/resolvers/review_requested_merge_requests_resolver.rb b/app/graphql/resolvers/review_requested_merge_requests_resolver.rb new file mode 100644 index 00000000000..e0ab7b5b600 --- /dev/null +++ b/app/graphql/resolvers/review_requested_merge_requests_resolver.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Resolvers + class ReviewRequestedMergeRequestsResolver < UserMergeRequestsResolverBase + type ::Types::MergeRequestType.connection_type, null: true + accept_author + accept_assignee + + def user_role + :reviewer + end + end +end diff --git a/app/graphql/resolvers/snippets/blobs_resolver.rb b/app/graphql/resolvers/snippets/blobs_resolver.rb index 3a0dcb50faf..cfb1711aed4 100644 --- a/app/graphql/resolvers/snippets/blobs_resolver.rb +++ b/app/graphql/resolvers/snippets/blobs_resolver.rb @@ -3,9 +3,11 @@ module Resolvers module Snippets class BlobsResolver < BaseResolver + prepend ManualAuthorization include Gitlab::Graphql::Authorize::AuthorizeResource type Types::Snippets::BlobType.connection_type, null: true + authorize :read_snippet alias_method :snippet, :object @@ -27,10 +29,6 @@ module Resolvers end end - def authorized_resource?(snippet) - Ability.allowed?(context[:current_user], :read_snippet, snippet) - end - private def transformed_blob_paths(paths) diff --git a/app/graphql/resolvers/user_discussions_count_resolver.rb b/app/graphql/resolvers/user_discussions_count_resolver.rb new file mode 100644 index 00000000000..115997ec666 --- /dev/null +++ b/app/graphql/resolvers/user_discussions_count_resolver.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Resolvers + class UserDiscussionsCountResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + type GraphQL::INT_TYPE, null: true + + def resolve + authorize!(object) + + BatchLoader::GraphQL.for(object.id).batch do |ids, loader, args| + counts = Note.count_for_collection(ids, object.class.name, 'COUNT(DISTINCT discussion_id) as count').index_by(&:noteable_id) + + ids.each do |id| + loader.call(id, counts[id]&.count || 0) + end + end + end + + def authorized_resource?(object) + ability = "read_#{object.class.name.underscore}".to_sym + context[:current_user].present? && Ability.allowed?(context[:current_user], ability, object) + end + end +end diff --git a/app/graphql/resolvers/user_notes_count_resolver.rb b/app/graphql/resolvers/user_notes_count_resolver.rb new file mode 100644 index 00000000000..2cb61104c18 --- /dev/null +++ b/app/graphql/resolvers/user_notes_count_resolver.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Resolvers + class UserNotesCountResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + type GraphQL::INT_TYPE, null: true + + def resolve + authorize!(object) + + BatchLoader::GraphQL.for(object.id).batch(key: :user_notes_count) do |ids, loader, args| + counts = Note.count_for_collection(ids, object.class.name).index_by(&:noteable_id) + + ids.each do |id| + loader.call(id, counts[id]&.count || 0) + end + end + end + + def authorized_resource?(object) + ability = "read_#{object.class.name.underscore}".to_sym + context[:current_user].present? && Ability.allowed?(context[:current_user], ability, object) + end + end +end diff --git a/app/graphql/resolvers/users/group_count_resolver.rb b/app/graphql/resolvers/users/group_count_resolver.rb index 5033c26554a..ebfe594d31d 100644 --- a/app/graphql/resolvers/users/group_count_resolver.rb +++ b/app/graphql/resolvers/users/group_count_resolver.rb @@ -3,6 +3,8 @@ module Resolvers module Users class GroupCountResolver < BaseResolver + type GraphQL::INT_TYPE, null: true + alias_method :user, :object def resolve(**args) diff --git a/app/graphql/resolvers/users_resolver.rb b/app/graphql/resolvers/users_resolver.rb index f5838642141..a0ed076595d 100644 --- a/app/graphql/resolvers/users_resolver.rb +++ b/app/graphql/resolvers/users_resolver.rb @@ -17,7 +17,7 @@ module Resolvers argument :sort, Types::SortEnum, description: 'Sort users by this criteria', required: false, - default_value: 'created_desc' + default_value: :created_desc argument :search, GraphQL::STRING_TYPE, required: false, diff --git a/app/graphql/types/alert_management/domain_filter_enum.rb b/app/graphql/types/alert_management/domain_filter_enum.rb new file mode 100644 index 00000000000..58dbc8bb2cf --- /dev/null +++ b/app/graphql/types/alert_management/domain_filter_enum.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Types + module AlertManagement + class DomainFilterEnum < BaseEnum + graphql_name 'AlertManagementDomainFilter' + description 'Filters the alerts based on given domain' + + value 'operations', description: 'Alerts for operations domain ' + value 'threat_monitoring', description: 'Alerts for threat monitoring domain' + end + end +end diff --git a/app/graphql/types/alert_management/prometheus_integration_type.rb b/app/graphql/types/alert_management/prometheus_integration_type.rb index f605e325b8b..79f265f2f1e 100644 --- a/app/graphql/types/alert_management/prometheus_integration_type.rb +++ b/app/graphql/types/alert_management/prometheus_integration_type.rb @@ -2,7 +2,7 @@ module Types module AlertManagement - class PrometheusIntegrationType < BaseObject + class PrometheusIntegrationType < ::Types::BaseObject include ::Gitlab::Routing graphql_name 'AlertManagementPrometheusIntegration' diff --git a/app/graphql/types/award_emojis/award_emoji_type.rb b/app/graphql/types/award_emojis/award_emoji_type.rb index fe7affa50cc..cd7a2f34ba6 100644 --- a/app/graphql/types/award_emojis/award_emoji_type.rb +++ b/app/graphql/types/award_emojis/award_emoji_type.rb @@ -38,10 +38,11 @@ module Types field :user, Types::UserType, null: false, - description: 'The user who awarded the emoji', - resolve: -> (award_emoji, _args, _context) { - Gitlab::Graphql::Loaders::BatchModelLoader.new(User, award_emoji.user_id).find - } + description: 'The user who awarded the emoji' + + def user + Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.user_id).find + end end end end diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb index 5c8aabfe163..c4ce2cecd8b 100644 --- a/app/graphql/types/base_field.rb +++ b/app/graphql/types/base_field.rb @@ -12,6 +12,7 @@ module Types def initialize(*args, **kwargs, &block) @calls_gitaly = !!kwargs.delete(:calls_gitaly) @constant_complexity = !!kwargs[:complexity] + @requires_argument = !!kwargs.delete(:requires_argument) kwargs[:complexity] = field_complexity(kwargs[:resolver_class], kwargs[:complexity]) @feature_flag = kwargs[:feature_flag] kwargs = check_feature_flag(kwargs) @@ -20,6 +21,10 @@ module Types super(*args, **kwargs, &block) end + def requires_argument? + @requires_argument || arguments.values.any? { |argument| argument.type.non_null? } + end + # Based on https://github.com/rmosolgo/graphql-ruby/blob/v1.11.4/lib/graphql/schema/field.rb#L538-L563 # Modified to fix https://github.com/rmosolgo/graphql-ruby/issues/3113 def resolve_field(obj, args, ctx) @@ -73,7 +78,7 @@ module Types attr_reader :feature_flag def feature_documentation_message(key, description) - "#{description}. Available only when feature flag `#{key}` is enabled" + "#{description} Available only when feature flag `#{key}` is enabled." end def check_feature_flag(args) diff --git a/app/graphql/types/base_interface.rb b/app/graphql/types/base_interface.rb index 3451a195c33..4b1f3193136 100644 --- a/app/graphql/types/base_interface.rb +++ b/app/graphql/types/base_interface.rb @@ -3,5 +3,7 @@ module Types module BaseInterface include GraphQL::Schema::Interface + + field_class ::Types::BaseField end end diff --git a/app/graphql/types/board_list_type.rb b/app/graphql/types/board_list_type.rb index 6ee76b0d1f1..7999e77eb30 100644 --- a/app/graphql/types/board_list_type.rb +++ b/app/graphql/types/board_list_type.rb @@ -19,8 +19,7 @@ module Types field :label, Types::LabelType, null: true, description: 'Label of the list' field :collapsed, GraphQL::BOOLEAN_TYPE, null: true, - description: 'Indicates if list is collapsed for this user', - resolve: -> (list, _args, ctx) { list.collapsed?(ctx[:current_user]) } + description: 'Indicates if list is collapsed for this user' field :issues_count, GraphQL::INT_TYPE, null: true, description: 'Count of issues in the list' @@ -32,6 +31,10 @@ module Types metadata[:size] end + def collapsed + object.collapsed?(context[:current_user]) + end + def metadata strong_memoize(:metadata) do list = self.object diff --git a/app/graphql/types/board_type.rb b/app/graphql/types/board_type.rb index 2a7b318e283..f47c744d1bb 100644 --- a/app/graphql/types/board_type.rb +++ b/app/graphql/types/board_type.rb @@ -12,6 +12,12 @@ module Types field :name, type: GraphQL::STRING_TYPE, null: true, description: 'Name of the board' + field :hide_backlog_list, type: GraphQL::BOOLEAN_TYPE, null: true, + description: 'Whether or not backlog list is hidden' + + field :hide_closed_list, type: GraphQL::BOOLEAN_TYPE, null: true, + description: 'Whether or not closed list is hidden' + field :lists, Types::BoardListType.connection_type, null: true, diff --git a/app/graphql/types/ci/analytics_type.rb b/app/graphql/types/ci/analytics_type.rb new file mode 100644 index 00000000000..c8b12c6a9b8 --- /dev/null +++ b/app/graphql/types/ci/analytics_type.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + class AnalyticsType < BaseObject + graphql_name 'PipelineAnalytics' + + field :week_pipelines_totals, [GraphQL::INT_TYPE], null: true, + description: 'Total weekly pipeline count' + field :week_pipelines_successful, [GraphQL::INT_TYPE], null: true, + description: 'Total weekly successful pipeline count' + field :week_pipelines_labels, [GraphQL::STRING_TYPE], null: true, + description: 'Labels for the weekly pipeline count' + field :month_pipelines_totals, [GraphQL::INT_TYPE], null: true, + description: 'Total monthly pipeline count' + field :month_pipelines_successful, [GraphQL::INT_TYPE], null: true, + description: 'Total monthly successful pipeline count' + field :month_pipelines_labels, [GraphQL::STRING_TYPE], null: true, + description: 'Labels for the monthly pipeline count' + field :year_pipelines_totals, [GraphQL::INT_TYPE], null: true, + description: 'Total yearly pipeline count' + field :year_pipelines_successful, [GraphQL::INT_TYPE], null: true, + description: 'Total yearly successful pipeline count' + field :year_pipelines_labels, [GraphQL::STRING_TYPE], null: true, + description: 'Labels for the yearly pipeline count' + field :pipeline_times_values, [GraphQL::INT_TYPE], null: true, + description: 'Pipeline times' + field :pipeline_times_labels, [GraphQL::STRING_TYPE], null: true, + description: 'Pipeline times labels' + 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 new file mode 100644 index 00000000000..207c37f9538 --- /dev/null +++ b/app/graphql/types/ci/ci_cd_setting_type.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Types + module Ci + class CiCdSettingType < BaseObject + graphql_name 'ProjectCiCdSetting' + + authorize :admin_project + + field :merge_pipelines_enabled, GraphQL::BOOLEAN_TYPE, null: true, + description: 'Whether merge pipelines are enabled.', + method: :merge_pipelines_enabled? + field :merge_trains_enabled, GraphQL::BOOLEAN_TYPE, null: true, + description: 'Whether merge trains are enabled.', + method: :merge_trains_enabled? + field :project, Types::ProjectType, null: true, + description: 'Project the CI/CD settings belong to.' + end + end +end diff --git a/app/graphql/types/ci/config/config_type.rb b/app/graphql/types/ci/config/config_type.rb new file mode 100644 index 00000000000..e54b345f3d3 --- /dev/null +++ b/app/graphql/types/ci/config/config_type.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + module Config + class ConfigType < BaseObject + graphql_name 'CiConfig' + + field :errors, [GraphQL::STRING_TYPE], null: true, + description: 'Linting errors' + field :merged_yaml, GraphQL::STRING_TYPE, null: true, + description: 'Merged CI config YAML' + field :stages, [Types::Ci::Config::StageType], null: true, + description: 'Stages of the pipeline' + field :status, Types::Ci::Config::StatusEnum, null: true, + description: 'Status of linting, can be either valid or invalid' + end + end + end +end diff --git a/app/graphql/types/ci/config/group_type.rb b/app/graphql/types/ci/config/group_type.rb new file mode 100644 index 00000000000..8b0db2934a4 --- /dev/null +++ b/app/graphql/types/ci/config/group_type.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + module Config + class GroupType < BaseObject + graphql_name 'CiConfigGroup' + + field :name, GraphQL::STRING_TYPE, null: true, + description: 'Name of the job group' + field :jobs, [Types::Ci::Config::JobType], null: true, + description: 'Jobs in group' + field :size, GraphQL::INT_TYPE, null: true, + description: 'Size of the job group' + end + end + end +end diff --git a/app/graphql/types/ci/config/job_type.rb b/app/graphql/types/ci/config/job_type.rb new file mode 100644 index 00000000000..59bcbd9ef49 --- /dev/null +++ b/app/graphql/types/ci/config/job_type.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + module Config + class JobType < BaseObject + graphql_name 'CiConfigJob' + + field :name, GraphQL::STRING_TYPE, null: true, + description: 'Name of the job' + field :group_name, GraphQL::STRING_TYPE, null: true, + description: 'Name of the job group' + field :stage, GraphQL::STRING_TYPE, null: true, + description: 'Name of the job stage' + field :needs, [Types::Ci::Config::NeedType], null: true, + description: 'Builds that must complete before the jobs run' + end + end + end +end diff --git a/app/graphql/types/ci/config/need_type.rb b/app/graphql/types/ci/config/need_type.rb new file mode 100644 index 00000000000..a442450b9ae --- /dev/null +++ b/app/graphql/types/ci/config/need_type.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + module Config + class NeedType < BaseObject + graphql_name 'CiConfigNeed' + + field :name, GraphQL::STRING_TYPE, null: true, + description: 'Name of the need' + end + end + end +end diff --git a/app/graphql/types/ci/config/stage_type.rb b/app/graphql/types/ci/config/stage_type.rb new file mode 100644 index 00000000000..20618bc41f8 --- /dev/null +++ b/app/graphql/types/ci/config/stage_type.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + module Config + class StageType < BaseObject + graphql_name 'CiConfigStage' + + field :name, GraphQL::STRING_TYPE, null: true, + description: 'Name of the stage' + field :groups, [Types::Ci::Config::GroupType], null: true, + description: 'Groups of jobs for the stage' + end + end + end +end diff --git a/app/graphql/types/ci/config/status_enum.rb b/app/graphql/types/ci/config/status_enum.rb new file mode 100644 index 00000000000..92b04c61679 --- /dev/null +++ b/app/graphql/types/ci/config/status_enum.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Types + module Ci + module Config + class StatusEnum < BaseEnum + graphql_name 'CiConfigStatus' + description 'Values for YAML processor result' + + value 'VALID', 'The configuration file is valid', value: :valid + value 'INVALID', 'The configuration file is not valid', value: :invalid + end + end + end +end diff --git a/app/graphql/types/ci/detailed_status_type.rb b/app/graphql/types/ci/detailed_status_type.rb index 6d8af400ac4..80d73e9b174 100644 --- a/app/graphql/types/ci/detailed_status_type.rb +++ b/app/graphql/types/ci/detailed_status_type.rb @@ -25,20 +25,22 @@ module Types description: 'Tooltip associated with the status', method: :status_tooltip field :action, Types::Ci::StatusActionType, null: true, - description: 'Action information for the status. This includes method, button title, icon, path, and title', - resolve: -> (obj, _args, _ctx) { - if obj.has_action? - { - button_title: obj.action_button_title, - icon: obj.action_icon, - method: obj.action_method, - path: obj.action_path, - title: obj.action_title - } - else - nil - end - } + calls_gitaly: true, + description: 'Action information for the status. This includes method, button title, icon, path, and title' + + def action + if object.has_action? + { + button_title: object.action_button_title, + icon: object.action_icon, + method: object.action_method, + path: object.action_path, + title: object.action_title + } + else + nil + end + end end # rubocop: enable Graphql/AuthorizeTypes end diff --git a/app/graphql/types/ci/group_type.rb b/app/graphql/types/ci/group_type.rb index d930ae311b7..03fd50d5dbb 100644 --- a/app/graphql/types/ci/group_type.rb +++ b/app/graphql/types/ci/group_type.rb @@ -13,8 +13,11 @@ module Types field :jobs, Ci::JobType.connection_type, null: true, description: 'Jobs in group' field :detailed_status, Types::Ci::DetailedStatusType, null: true, - description: 'Detailed status of the group', - resolve: -> (obj, _args, ctx) { obj.detailed_status(ctx[:current_user]) } + description: 'Detailed status of the group' + + def detailed_status + object.detailed_status(context[:current_user]) + end end end end diff --git a/app/graphql/types/ci/job_artifact_file_type_enum.rb b/app/graphql/types/ci/job_artifact_file_type_enum.rb new file mode 100644 index 00000000000..4b484dec590 --- /dev/null +++ b/app/graphql/types/ci/job_artifact_file_type_enum.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Types + module Ci + class JobArtifactFileTypeEnum < BaseEnum + graphql_name 'JobArtifactFileType' + + ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.keys.each do |file_type| + value file_type.to_s.upcase, value: file_type.to_s + end + end + end +end diff --git a/app/graphql/types/ci/job_artifact_type.rb b/app/graphql/types/ci/job_artifact_type.rb new file mode 100644 index 00000000000..c34a12dcc61 --- /dev/null +++ b/app/graphql/types/ci/job_artifact_type.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + class JobArtifactType < BaseObject + graphql_name 'CiJobArtifact' + + field :download_path, GraphQL::STRING_TYPE, null: true, + description: "URL for downloading the artifact's file" + + field :file_type, ::Types::Ci::JobArtifactFileTypeEnum, null: true, + description: 'File type of the artifact' + + def download_path + ::Gitlab::Routing.url_helpers.download_project_job_artifacts_path( + object.project, + object.job, + file_type: object.file_type + ) + end + end + end +end diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb index feaff4e81d8..5b6e8fe8567 100644 --- a/app/graphql/types/ci/job_type.rb +++ b/app/graphql/types/ci/job_type.rb @@ -6,18 +6,32 @@ module Types class JobType < BaseObject graphql_name 'CiJob' - field :pipeline, Types::Ci::PipelineType, null: false, - description: 'Pipeline the job belongs to', - resolve: -> (build, _, _) { Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Pipeline, build.pipeline_id).find } + field :pipeline, Types::Ci::PipelineType, null: true, + description: 'Pipeline the job belongs to' field :name, GraphQL::STRING_TYPE, null: true, - description: 'Name of the job' + description: 'Name of the job' field :needs, JobType.connection_type, null: true, - description: 'Builds that must complete before the jobs run' + description: 'Builds that must complete before the jobs run' field :detailed_status, Types::Ci::DetailedStatusType, null: true, - description: 'Detailed status of the job', - resolve: -> (obj, _args, ctx) { obj.detailed_status(ctx[:current_user]) } + description: 'Detailed status of the job' field :scheduled_at, Types::TimeType, null: true, - description: 'Schedule for the build' + description: 'Schedule for the build' + field :artifacts, Types::Ci::JobArtifactType.connection_type, null: true, + description: 'Artifacts generated by the job' + + def pipeline + Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Pipeline, object.pipeline_id).find + end + + def detailed_status + object.detailed_status(context[:current_user]) + end + + def artifacts + if object.is_a?(::Ci::Build) + object.job_artifacts + end + end end end end diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb index c25db39f600..4709d5e8dd6 100644 --- a/app/graphql/types/ci/pipeline_type.rb +++ b/app/graphql/types/ci/pipeline_type.rb @@ -27,8 +27,7 @@ module Types description: "Status of the pipeline (#{::Ci::Pipeline.all_state_names.compact.join(', ').upcase})" field :detailed_status, Types::Ci::DetailedStatusType, null: false, - description: 'Detailed status of the pipeline', - resolve: -> (obj, _args, ctx) { obj.detailed_status(ctx[:current_user]) } + description: 'Detailed status of the pipeline' field :config_source, PipelineConfigSourceEnum, null: true, description: "Config source of the pipeline (#{::Enums::Ci::Pipeline.config_sources.keys.join(', ').upcase})" @@ -60,8 +59,7 @@ module Types resolver: Resolvers::Ci::PipelineStagesResolver field :user, Types::UserType, null: true, - description: 'Pipeline user', - resolve: -> (pipeline, _args, _context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, pipeline.user_id).find } + description: 'Pipeline user' field :retryable, GraphQL::BOOLEAN_TYPE, description: 'Specifies if a pipeline can be retried', @@ -91,11 +89,25 @@ module Types method: :triggered_by_pipeline field :path, GraphQL::STRING_TYPE, null: true, - description: "Relative path to the pipeline's page", - resolve: -> (obj, _args, _ctx) { ::Gitlab::Routing.url_helpers.project_pipeline_path(obj.project, obj) } + description: "Relative path to the pipeline's page" field :project, Types::ProjectType, null: true, description: 'Project the pipeline belongs to' + + field :active, GraphQL::BOOLEAN_TYPE, null: false, method: :active?, + description: 'Indicates if the pipeline is active' + + def detailed_status + object.detailed_status(context[:current_user]) + end + + def user + Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.user_id).find + end + + def path + ::Gitlab::Routing.url_helpers.project_pipeline_path(object.project, object) + end end end end diff --git a/app/graphql/types/ci/stage_type.rb b/app/graphql/types/ci/stage_type.rb index fc2c72d0d06..fd0bde90836 100644 --- a/app/graphql/types/ci/stage_type.rb +++ b/app/graphql/types/ci/stage_type.rb @@ -11,8 +11,11 @@ module Types field :groups, Ci::GroupType.connection_type, null: true, description: 'Group of jobs for the stage' field :detailed_status, Types::Ci::DetailedStatusType, null: true, - description: 'Detailed status of the stage', - resolve: -> (obj, _args, ctx) { obj.detailed_status(ctx[:current_user]) } + description: 'Detailed status of the stage' + + def detailed_status + object.detailed_status(context[:current_user]) + end end end end diff --git a/app/graphql/types/commit_type.rb b/app/graphql/types/commit_type.rb index c24b47f08ef..37d19b4148b 100644 --- a/app/graphql/types/commit_type.rb +++ b/app/graphql/types/commit_type.rb @@ -12,6 +12,8 @@ module Types description: 'ID (global ID) of the commit' field :sha, type: GraphQL::STRING_TYPE, null: false, description: 'SHA1 ID of the commit' + field :short_id, type: GraphQL::STRING_TYPE, null: false, + description: 'Short SHA1 ID of the commit' field :title, type: GraphQL::STRING_TYPE, null: true, calls_gitaly: true, description: 'Title of the commit message' markdown_field :title_html, null: true @@ -31,10 +33,7 @@ module Types field :author_name, type: GraphQL::STRING_TYPE, null: true, description: 'Commit authors name' field :author_gravatar, type: GraphQL::STRING_TYPE, null: true, - description: 'Commit authors gravatar', - resolve: -> (commit, args, context) do - GravatarService.new.execute(commit.author_email, 40) - end + description: 'Commit authors gravatar' # models/commit lazy loads the author by email field :author, type: Types::UserType, null: true, @@ -44,5 +43,9 @@ module Types null: true, description: 'Pipelines of the commit ordered latest first', resolver: Resolvers::CommitPipelinesResolver + + def author_gravatar + GravatarService.new.execute(object.author_email, 40) + end end end diff --git a/app/graphql/types/concerns/gitlab_style_deprecations.rb b/app/graphql/types/concerns/gitlab_style_deprecations.rb index 2c932f4214b..9f087f3812d 100644 --- a/app/graphql/types/concerns/gitlab_style_deprecations.rb +++ b/app/graphql/types/concerns/gitlab_style_deprecations.rb @@ -23,8 +23,8 @@ module GitlabStyleDeprecations raise ArgumentError, '`milestone` must be a `String`' unless milestone.is_a?(String) deprecated_in = "Deprecated in #{milestone}" - kwargs[:deprecation_reason] = "#{reason}. #{deprecated_in}" - kwargs[:description] += ". #{deprecated_in}: #{reason}" if kwargs[:description] + kwargs[:deprecation_reason] = "#{reason}. #{deprecated_in}." + kwargs[:description] += " #{deprecated_in}: #{reason}." if kwargs[:description] kwargs end diff --git a/app/graphql/types/container_repository_type.rb b/app/graphql/types/container_repository_type.rb index 45d19fdbc50..8735f8a173d 100644 --- a/app/graphql/types/container_repository_type.rb +++ b/app/graphql/types/container_repository_type.rb @@ -19,9 +19,14 @@ module Types field :status, Types::ContainerRepositoryStatusEnum, null: true, description: 'Status of the container repository.' field :tags_count, GraphQL::INT_TYPE, null: false, description: 'Number of tags associated with this image.' field :can_delete, GraphQL::BOOLEAN_TYPE, null: false, description: 'Can the current user delete the container repository.' + field :project, Types::ProjectType, null: false, description: 'Project of the container registry' def can_delete Ability.allowed?(current_user, :update_container_image, object) end + + def project + Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find + end end end diff --git a/app/graphql/types/design_management/design_collection_type.rb b/app/graphql/types/design_management/design_collection_type.rb index 9af1f4db425..26fbac15b30 100644 --- a/app/graphql/types/design_management/design_collection_type.rb +++ b/app/graphql/types/design_management/design_collection_type.rb @@ -2,7 +2,7 @@ module Types module DesignManagement - class DesignCollectionType < BaseObject + class DesignCollectionType < ::Types::BaseObject graphql_name 'DesignCollection' description 'A collection of designs' diff --git a/app/graphql/types/error_tracking/sentry_error_collection_type.rb b/app/graphql/types/error_tracking/sentry_error_collection_type.rb index 798e0433d06..49d5d62c860 100644 --- a/app/graphql/types/error_tracking/sentry_error_collection_type.rb +++ b/app/graphql/types/error_tracking/sentry_error_collection_type.rb @@ -9,27 +9,12 @@ module Types authorize :read_sentry_issue field :errors, - Types::ErrorTracking::SentryErrorType.connection_type, - connection: false, - null: true, description: "Collection of Sentry Errors", - extensions: [Gitlab::Graphql::Extensions::ExternallyPaginatedArrayExtension], - resolver: Resolvers::ErrorTracking::SentryErrorsResolver do - argument :search_term, - String, - description: 'Search query for the Sentry error details', - required: false - argument :sort, - String, - description: 'Attribute to sort on. Options are frequency, first_seen, last_seen. last_seen is default', - required: false - end - field :detailed_error, Types::ErrorTracking::SentryDetailedErrorType, - null: true, + resolver: Resolvers::ErrorTracking::SentryErrorsResolver + field :detailed_error, description: 'Detailed version of a Sentry error on the project', resolver: Resolvers::ErrorTracking::SentryDetailedErrorResolver - field :error_stack_trace, Types::ErrorTracking::SentryErrorStackTraceType, - null: true, + field :error_stack_trace, description: 'Stack Trace of Sentry Error', resolver: Resolvers::ErrorTracking::SentryErrorStackTraceResolver field :external_url, diff --git a/app/graphql/types/group_invitation_type.rb b/app/graphql/types/group_invitation_type.rb index 0372ce178ff..efb0c8a41c8 100644 --- a/app/graphql/types/group_invitation_type.rb +++ b/app/graphql/types/group_invitation_type.rb @@ -11,7 +11,10 @@ module Types description 'Represents a Group Invitation' field :group, Types::GroupType, null: true, - description: 'Group that a User is invited to', - resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.source_id).find } + description: 'Group that a User is invited to' + + def group + Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, object.source_id).find + end end end diff --git a/app/graphql/types/group_member_relation_enum.rb b/app/graphql/types/group_member_relation_enum.rb new file mode 100644 index 00000000000..aa2e73d4944 --- /dev/null +++ b/app/graphql/types/group_member_relation_enum.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Types + class GroupMemberRelationEnum < BaseEnum + graphql_name 'GroupMemberRelation' + description 'Group member relation' + + ::GroupMembersFinder::RELATIONS.each do |member_relation| + value member_relation.to_s.upcase, value: member_relation, description: "#{member_relation.to_s.titleize} members" + end + end +end diff --git a/app/graphql/types/group_member_type.rb b/app/graphql/types/group_member_type.rb index 6cca0a50647..204da5a302a 100644 --- a/app/graphql/types/group_member_type.rb +++ b/app/graphql/types/group_member_type.rb @@ -11,7 +11,10 @@ module Types description 'Represents a Group Membership' field :group, Types::GroupType, null: true, - description: 'Group that a User is a member of', - resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.source_id).find } + description: 'Group that a User is a member of' + + def group + Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, object.source_id).find + end end end diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb index fb028184488..0ee8a19c1a3 100644 --- a/app/graphql/types/group_type.rb +++ b/app/graphql/types/group_type.rb @@ -12,10 +12,7 @@ module Types description: 'Web URL of the group' field :avatar_url, GraphQL::STRING_TYPE, null: true, - description: 'Avatar URL of the group', - resolve: -> (group, args, ctx) do - group.avatar_url(only_path: false) - end + description: 'Avatar URL of the group' field :custom_emoji, Types::CustomEmojiType.connection_type, null: true, description: 'Custom emoji within this namespace', @@ -44,8 +41,7 @@ module Types description: 'Indicates if a group is disabled from getting mentioned' field :parent, GroupType, null: true, - description: 'Parent group', - resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.parent_id).find } + description: 'Parent group' field :issues, Types::IssueType.connection_type, @@ -92,10 +88,13 @@ module Types field :container_repositories, Types::ContainerRepositoryType.connection_type, null: true, - description: 'Container repositories of the project', + description: 'Container repositories of the group', resolver: Resolvers::ContainerRepositoriesResolver, authorize: :read_container_image + field :container_repositories_count, GraphQL::INT_TYPE, null: false, + description: 'Number of container repositories in the group' + def label(title:) BatchLoader::GraphQL.for(title).batch(key: group) do |titles, loader, args| LabelsFinder @@ -120,6 +119,18 @@ module Types .execute end + def avatar_url + object.avatar_url(only_path: false) + end + + def parent + Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, object.parent_id).find + end + + def container_repositories_count + group.container_repositories.size + end + private def group diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index 49c84f75e1a..83b8a834801 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -61,9 +61,11 @@ module Types field :downvotes, GraphQL::INT_TYPE, null: false, description: 'Number of downvotes the issue has received' field :user_notes_count, GraphQL::INT_TYPE, null: false, - description: 'Number of user notes of the issue' + description: 'Number of user notes of the issue', + resolver: Resolvers::UserNotesCountResolver field :user_discussions_count, GraphQL::INT_TYPE, null: false, - description: 'Number of user discussions in the issue' + description: 'Number of user discussions in the issue', + resolver: Resolvers::UserDiscussionsCountResolver field :web_path, GraphQL::STRING_TYPE, null: false, method: :issue_path, description: 'Web path of the issue' field :web_url, GraphQL::STRING_TYPE, null: false, @@ -119,26 +121,6 @@ module Types field :moved_to, Types::IssueType, null: true, description: 'Updated Issue after it got moved to another project' - def user_notes_count - BatchLoader::GraphQL.for(object.id).batch(key: :issue_user_notes_count) do |ids, loader, args| - counts = Note.count_for_collection(ids, 'Issue').index_by(&:noteable_id) - - ids.each do |id| - loader.call(id, counts[id]&.count || 0) - end - end - end - - def user_discussions_count - BatchLoader::GraphQL.for(object.id).batch(key: :issue_user_discussions_count) do |ids, loader, args| - counts = Note.count_for_collection(ids, 'Issue', 'COUNT(DISTINCT discussion_id) as count').index_by(&:noteable_id) - - ids.each do |id| - loader.call(id, counts[id]&.count || 0) - end - end - end - def author Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find end diff --git a/app/graphql/types/jira_import_type.rb b/app/graphql/types/jira_import_type.rb index cf58a53b40d..b3854487cec 100644 --- a/app/graphql/types/jira_import_type.rb +++ b/app/graphql/types/jira_import_type.rb @@ -2,8 +2,7 @@ module Types # rubocop: disable Graphql/AuthorizeTypes - # Authorization is at project level for owners or admins, - # so it is added directly to the Resolvers::JiraImportsResolver + # Authorization is at project level for owners or admins class JiraImportType < BaseObject graphql_name 'JiraImport' diff --git a/app/graphql/types/jira_users_mapping_input_type.rb b/app/graphql/types/jira_users_mapping_input_type.rb index 61cf1474493..d5b4b2f618a 100644 --- a/app/graphql/types/jira_users_mapping_input_type.rb +++ b/app/graphql/types/jira_users_mapping_input_type.rb @@ -8,7 +8,7 @@ module Types argument :jira_account_id, GraphQL::STRING_TYPE, required: true, - description: 'Jira account id of the user' + description: 'Jira account ID of the user' argument :gitlab_id, GraphQL::INT_TYPE, required: false, diff --git a/app/graphql/types/merge_request_connection_type.rb b/app/graphql/types/merge_request_connection_type.rb new file mode 100644 index 00000000000..da06bb86929 --- /dev/null +++ b/app/graphql/types/merge_request_connection_type.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Types + # rubocop: disable Graphql/AuthorizeTypes + class MergeRequestConnectionType < Types::CountableConnectionType + field :total_time_to_merge, GraphQL::FLOAT_TYPE, null: true, + description: 'Total sum of time to merge, in seconds, for the collection of merge requests' + + # rubocop: disable CodeReuse/ActiveRecord + def total_time_to_merge + object.items.reorder(nil).total_time_to_merge + end + # rubocop: enable CodeReuse/ActiveRecord + end +end diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index e68d6706c43..816160e58f7 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -4,7 +4,7 @@ module Types class MergeRequestType < BaseObject graphql_name 'MergeRequest' - connection_type_class(Types::CountableConnectionType) + connection_type_class(Types::MergeRequestConnectionType) implements(Types::Notes::NoteableType) implements(Types::CurrentUserTodos) @@ -49,6 +49,8 @@ module Types description: 'ID of the merge request target project' field :source_branch, GraphQL::STRING_TYPE, null: false, description: 'Source branch of the merge request' + field :source_branch_protected, GraphQL::BOOLEAN_TYPE, null: false, calls_gitaly: true, + description: 'Indicates if the source branch is protected' field :target_branch, GraphQL::STRING_TYPE, null: false, description: 'Target branch of the merge request' field :work_in_progress, GraphQL::BOOLEAN_TYPE, method: :work_in_progress?, null: false, @@ -67,9 +69,11 @@ module Types field :merge_commit_sha, GraphQL::STRING_TYPE, null: true, description: 'SHA of the merge request commit (set once merged)' field :user_notes_count, GraphQL::INT_TYPE, null: true, - description: 'User notes count of the merge request' + description: 'User notes count of the merge request', + resolver: Resolvers::UserNotesCountResolver field :user_discussions_count, GraphQL::INT_TYPE, null: true, - description: 'Number of user discussions in the merge request' + description: 'Number of user discussions in the merge request', + resolver: Resolvers::UserDiscussionsCountResolver field :should_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :should_remove_source_branch?, null: true, description: 'Indicates if the source branch of the merge request will be deleted after merge' field :force_remove_source_branch, GraphQL::BOOLEAN_TYPE, method: :force_remove_source_branch?, null: true, @@ -90,6 +94,8 @@ module Types description: 'Indicates if there is a rebase currently in progress for the merge request' field :default_merge_commit_message, GraphQL::STRING_TYPE, null: true, description: 'Default merge commit message of the merge request' + field :default_merge_commit_message_with_description, GraphQL::STRING_TYPE, null: true, + description: 'Default merge commit message of the merge request with description' field :merge_ongoing, GraphQL::BOOLEAN_TYPE, method: :merge_ongoing?, null: false, description: 'Indicates if a merge is currently occurring' field :source_branch_exists, GraphQL::BOOLEAN_TYPE, @@ -113,7 +119,7 @@ module Types description: 'The pipeline running on the branch HEAD of the merge request' field :pipelines, null: true, - description: 'Pipelines for the merge request', + description: 'Pipelines for the merge request. Note: for performance reasons, no more than the most recent 500 pipelines will be returned.', resolver: Resolvers::MergeRequestPipelinesResolver field :milestone, Types::MilestoneType, null: true, @@ -130,8 +136,7 @@ module Types description: 'Labels of the merge request' field :discussion_locked, GraphQL::BOOLEAN_TYPE, description: 'Indicates if comments on the merge request are locked to members only', - null: false, - resolve: -> (obj, _args, _ctx) { !!obj.discussion_locked } + null: false field :time_estimate, GraphQL::INT_TYPE, null: false, description: 'Time estimate of the merge request' field :total_time_spent, GraphQL::INT_TYPE, null: false, @@ -152,6 +157,18 @@ module Types field :approved_by, Types::UserType.connection_type, null: true, description: 'Users who approved the merge request' + field :squash_on_merge, GraphQL::BOOLEAN_TYPE, null: false, method: :squash_on_merge?, + description: 'Indicates if squash on merge is enabled' + field :available_auto_merge_strategies, [GraphQL::STRING_TYPE], null: true, calls_gitaly: true, + description: 'Array of available auto merge strategies' + field :has_ci, GraphQL::BOOLEAN_TYPE, null: false, method: :has_ci?, + description: 'Indicates if the merge request has CI' + field :mergeable, GraphQL::BOOLEAN_TYPE, null: false, method: :mergeable?, calls_gitaly: true, + description: 'Indicates if the merge request is mergeable' + field :commits_without_merge_commits, Types::CommitType.connection_type, null: true, + calls_gitaly: true, description: 'Merge request commits excluding merge commits' + field :security_auto_fix, GraphQL::BOOLEAN_TYPE, null: true, + description: 'Indicates if the merge request is created by @GitLab-Security-Bot.' def approved_by object.approved_by_users @@ -194,6 +211,31 @@ module Types def commit_count object&.metrics&.commits_count end + + def source_branch_protected + object.source_project.present? && ProtectedBranch.protected?(object.source_project, object.source_branch) + end + + def discussion_locked + !!object.discussion_locked + end + + def default_merge_commit_message_with_description + object.default_merge_commit_message(include_description: true) + end + + def available_auto_merge_strategies + AutoMergeService.new(object.project, current_user).available_strategies(object) + end + + def commits_without_merge_commits + object.recent_commits.without_merge_commits + end + + def security_auto_fix + object.author == User.security_bot + end end end + Types::MergeRequestType.prepend_if_ee('::EE::Types::MergeRequestType') diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 75ccac6d590..9eea81c9d3e 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -31,6 +31,7 @@ module Types mount_mutation Mutations::Commits::Create, calls_gitaly: true mount_mutation Mutations::CustomEmoji::Create, feature_flag: :custom_emoji mount_mutation Mutations::Discussions::ToggleResolve + mount_mutation Mutations::Environments::CanaryIngress::Update mount_mutation Mutations::Issues::Create mount_mutation Mutations::Issues::SetAssignees mount_mutation Mutations::Issues::SetConfidential @@ -65,6 +66,8 @@ module Types mount_mutation Mutations::Notes::RepositionImageDiffNote mount_mutation Mutations::Notes::Destroy mount_mutation Mutations::Releases::Create + mount_mutation Mutations::Releases::Update + mount_mutation Mutations::Releases::Delete mount_mutation Mutations::Terraform::State::Delete mount_mutation Mutations::Terraform::State::Lock mount_mutation Mutations::Terraform::State::Unlock @@ -84,6 +87,7 @@ module Types mount_mutation Mutations::DesignManagement::Move mount_mutation Mutations::ContainerExpirationPolicies::Update mount_mutation Mutations::ContainerRepositories::Destroy + mount_mutation Mutations::ContainerRepositories::DestroyTags mount_mutation Mutations::Ci::PipelineCancel mount_mutation Mutations::Ci::PipelineDestroy mount_mutation Mutations::Ci::PipelineRetry diff --git a/app/graphql/types/namespace_type.rb b/app/graphql/types/namespace_type.rb index fbdf049b755..4dec6f4c5e6 100644 --- a/app/graphql/types/namespace_type.rb +++ b/app/graphql/types/namespace_type.rb @@ -21,6 +21,7 @@ module Types field :description, GraphQL::STRING_TYPE, null: true, description: 'Description of the namespace' markdown_field :description_html, null: true + field :visibility, GraphQL::STRING_TYPE, null: true, description: 'Visibility of the namespace' field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, method: :lfs_enabled?, @@ -30,12 +31,15 @@ module Types field :root_storage_statistics, Types::RootStorageStatisticsType, null: true, - description: 'Aggregated storage statistics of the namespace. Only available for root namespaces', - resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader.new(obj.id).find } + description: 'Aggregated storage statistics of the namespace. Only available for root namespaces' field :projects, Types::ProjectType.connection_type, null: false, description: 'Projects within this namespace', resolver: ::Resolvers::NamespaceProjectsResolver + + def root_storage_statistics + Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader.new(object.id).find + end end end diff --git a/app/graphql/types/notes/diff_position_type.rb b/app/graphql/types/notes/diff_position_type.rb index cc00feba2e6..13d9be49484 100644 --- a/app/graphql/types/notes/diff_position_type.rb +++ b/app/graphql/types/notes/diff_position_type.rb @@ -21,25 +21,43 @@ module Types # Fields for text positions field :old_line, GraphQL::INT_TYPE, null: true, - description: 'Line on start SHA that was changed', - resolve: -> (position, _args, _ctx) { position.old_line if position.on_text? } + description: 'Line on start SHA that was changed' field :new_line, GraphQL::INT_TYPE, null: true, - description: 'Line on HEAD SHA that was changed', - resolve: -> (position, _args, _ctx) { position.new_line if position.on_text? } + description: 'Line on HEAD SHA that was changed' # Fields for image positions field :x, GraphQL::INT_TYPE, null: true, - description: 'X position of the note', - resolve: -> (position, _args, _ctx) { position.x if position.on_image? } + description: 'X position of the note' field :y, GraphQL::INT_TYPE, null: true, - description: 'Y position of the note', - resolve: -> (position, _args, _ctx) { position.y if position.on_image? } + description: 'Y position of the note' field :width, GraphQL::INT_TYPE, null: true, - description: 'Total width of the image', - resolve: -> (position, _args, _ctx) { position.width if position.on_image? } + description: 'Total width of the image' field :height, GraphQL::INT_TYPE, null: true, - description: 'Total height of the image', - resolve: -> (position, _args, _ctx) { position.height if position.on_image? } + description: 'Total height of the image' + + def old_line + object.old_line if object.on_text? + end + + def new_line + object.new_line if object.on_text? + end + + def x + object.x if object.on_image? + end + + def y + object.y if object.on_image? + end + + def width + object.width if object.on_image? + end + + def height + object.height if object.on_image? + end end # rubocop: enable Graphql/AuthorizeTypes end diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb index 5d41f0032bd..f4e05e19eca 100644 --- a/app/graphql/types/notes/note_type.rb +++ b/app/graphql/types/notes/note_type.rb @@ -16,13 +16,11 @@ module Types field :project, Types::ProjectType, null: true, - description: 'Project associated with the note', - resolve: -> (note, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, note.project_id).find } + description: 'Project associated with the note' field :author, Types::UserType, null: false, - description: 'User who wrote this note', - resolve: -> (note, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, note.author_id).find } + description: 'User who wrote this note' field :system, GraphQL::BOOLEAN_TYPE, null: false, @@ -52,6 +50,14 @@ module Types def system_note_icon_name SystemNoteHelper.system_note_icon_name(object) if object.system? end + + def project + Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find + end + + def author + Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find + end end end end diff --git a/app/graphql/types/permission_types/merge_request.rb b/app/graphql/types/permission_types/merge_request.rb index e9c89b0c92e..52c11fe5588 100644 --- a/app/graphql/types/permission_types/merge_request.rb +++ b/app/graphql/types/permission_types/merge_request.rb @@ -19,7 +19,9 @@ module Types permission_field field_name, method: :"can_#{field_name}?", calls_gitaly: true end - permission_field :can_merge, calls_gitaly: true, resolve: -> (object, args, context) do + permission_field :can_merge, calls_gitaly: true + + def can_merge object.can_be_merged_by?(context[:current_user]) end end diff --git a/app/graphql/types/project_member_relation_enum.rb b/app/graphql/types/project_member_relation_enum.rb new file mode 100644 index 00000000000..fbad23b956f --- /dev/null +++ b/app/graphql/types/project_member_relation_enum.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Types + class ProjectMemberRelationEnum < BaseEnum + graphql_name 'ProjectMemberRelation' + description 'Project member relation' + + ::MembersFinder::RELATIONS.each do |member_relation| + value member_relation.to_s.upcase, value: member_relation, description: "#{member_relation.to_s.titleize} members" + end + end +end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 5a436886117..a7d9548610e 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -67,33 +67,25 @@ module Types description: 'E-mail address of the service desk.' field :avatar_url, GraphQL::STRING_TYPE, null: true, calls_gitaly: true, - description: 'URL to avatar image file of the project', - resolve: -> (project, args, ctx) do - project.avatar_url(only_path: false) - end + description: 'URL to avatar image file of the project' %i[issues merge_requests wiki snippets].each do |feature| field "#{feature}_enabled", GraphQL::BOOLEAN_TYPE, null: true, - description: "Indicates if #{feature.to_s.titleize.pluralize} are enabled for the current user", - resolve: -> (project, args, ctx) do - project.feature_available?(feature, ctx[:current_user]) - end + description: "Indicates if #{feature.to_s.titleize.pluralize} are enabled for the current user" + + define_method "#{feature}_enabled" do + object.feature_available?(feature, context[:current_user]) + end end field :jobs_enabled, GraphQL::BOOLEAN_TYPE, null: true, - description: 'Indicates if CI/CD pipeline jobs are enabled for the current user', - resolve: -> (project, args, ctx) do - project.feature_available?(:builds, ctx[:current_user]) - end + description: 'Indicates if CI/CD pipeline jobs are enabled for the current user' field :public_jobs, GraphQL::BOOLEAN_TYPE, method: :public_builds, null: true, description: 'Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts' field :open_issues_count, GraphQL::INT_TYPE, null: true, - description: 'Number of open issues for the project', - resolve: -> (project, args, ctx) do - project.open_issues_count if project.feature_available?(:issues, ctx[:current_user]) - end + description: 'Number of open issues for the project' field :import_status, GraphQL::STRING_TYPE, null: true, description: 'Status of import background job of the project' @@ -115,6 +107,8 @@ module Types description: 'Indicates if issues referenced by merge requests and commits within the default branch are closed automatically' field :suggestion_commit_message, GraphQL::STRING_TYPE, null: true, description: 'The commit message used to apply merge request suggestions' + field :squash_read_only, GraphQL::BOOLEAN_TYPE, null: false, method: :squash_readonly?, + description: 'Indicates if squash readonly is enabled' field :namespace, Types::NamespaceType, null: true, description: 'Namespace of the project' @@ -123,8 +117,7 @@ module Types field :statistics, Types::ProjectStatisticsType, null: true, - description: 'Statistics of the project', - resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader.new(obj.id).find } + description: 'Statistics of the project' field :repository, Types::RepositoryType, null: true, description: 'Git repository of the project' @@ -198,6 +191,11 @@ module Types description: 'Build pipeline of the project', resolver: Resolvers::ProjectPipelineResolver + field :ci_cd_settings, + Types::Ci::CiCdSettingType, + null: true, + description: 'CI/CD settings for the project' + field :sentry_detailed_error, Types::ErrorTracking::SentryDetailedErrorType, null: true, @@ -238,8 +236,7 @@ module Types field :jira_imports, Types::JiraImportType.connection_type, null: true, - description: 'Jira imports into the project', - resolver: Resolvers::Projects::JiraImportsResolver + description: 'Jira imports into the project' field :services, Types::Projects::ServiceType.connection_type, @@ -296,6 +293,9 @@ module Types description: 'Container repositories of the project', resolver: Resolvers::ContainerRepositoriesResolver + field :container_repositories_count, GraphQL::INT_TYPE, null: false, + description: 'Number of container repositories in the project' + field :label, Types::LabelType, null: true, @@ -311,6 +311,13 @@ module Types description: 'Terraform states associated with the project', resolver: Resolvers::Terraform::StatesResolver + field :pipeline_analytics, Types::Ci::AnalyticsType, null: true, + description: 'Pipeline analytics', + resolver: Resolvers::ProjectPipelineStatisticsResolver + + field :total_pipeline_duration, GraphQL::INT_TYPE, null: true, + description: 'Total pipeline duration for all of the pipelines in a project' + def label(title:) BatchLoader::GraphQL.for(title).batch(key: project) do |titles, loader, args| LabelsFinder @@ -335,6 +342,30 @@ module Types .execute end + def avatar_url + object.avatar_url(only_path: false) + end + + def jobs_enabled + object.feature_available?(:builds, context[:current_user]) + end + + def open_issues_count + object.open_issues_count if object.feature_available?(:issues, context[:current_user]) + end + + def statistics + Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader.new(object.id).find + end + + def container_repositories_count + project.container_repositories.size + end + + def total_pipeline_duration + object.all_pipelines.total_duration + end + private def project diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index d194b0979b3..05bb371088c 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -24,7 +24,6 @@ module Types field :current_user, Types::UserType, null: true, - resolve: -> (_obj, _args, context) { context[:current_user] }, description: "Get information about current user" field :namespace, Types::NamespaceType, @@ -92,6 +91,11 @@ module Types description: 'Get runner setup instructions', resolver: Resolvers::Ci::RunnerSetupResolver + field :ci_config, Types::Ci::Config::ConfigType, null: true, + description: 'Get linted and processed contents of a CI config. Should not be requested more than once per request.', + resolver: Resolvers::Ci::ConfigResolver, + complexity: 126 # AUTHENTICATED_COMPLEXITY / 2 + 1 + def design_management DesignManagementObject.new(nil) end @@ -116,6 +120,10 @@ module Types id = ::Types::GlobalIDType[::ContainerRepository].coerce_isolated_input(id) GitlabSchema.find_by_gid(id) end + + def current_user + context[:current_user] + end end end diff --git a/app/graphql/types/snippets/blob_viewer_type.rb b/app/graphql/types/snippets/blob_viewer_type.rb index 50d0b0522d6..a2ffa144066 100644 --- a/app/graphql/types/snippets/blob_viewer_type.rb +++ b/app/graphql/types/snippets/blob_viewer_type.rb @@ -17,14 +17,12 @@ module Types field :collapsed, GraphQL::BOOLEAN_TYPE, description: 'Shows whether the blob should be displayed collapsed', method: :collapsed?, - null: false, - resolve: -> (viewer, _args, _ctx) { !!viewer&.collapsed? } + null: false field :too_large, GraphQL::BOOLEAN_TYPE, description: 'Shows whether the blob too large to be displayed', method: :too_large?, - null: false, - resolve: -> (viewer, _args, _ctx) { !!viewer&.too_large? } + null: false field :render_error, GraphQL::STRING_TYPE, description: 'Error rendering the blob content', @@ -38,6 +36,14 @@ module Types field :loading_partial_name, GraphQL::STRING_TYPE, description: 'Loading partial name', null: false + + def collapsed + !!object&.collapsed? + end + + def too_large + !!object&.too_large? + end end end end diff --git a/app/graphql/types/sort_enum.rb b/app/graphql/types/sort_enum.rb index d0a6eecb672..c3a76330fe9 100644 --- a/app/graphql/types/sort_enum.rb +++ b/app/graphql/types/sort_enum.rb @@ -7,10 +7,10 @@ module Types # Deprecated, as we prefer uppercase enums # https://gitlab.com/groups/gitlab-org/-/epics/1838 - value 'updated_desc', 'Updated at descending order', deprecated: { reason: 'Use UPDATED_DESC', milestone: '13.5' } - value 'updated_asc', 'Updated at ascending order', deprecated: { reason: 'Use UPDATED_ASC', milestone: '13.5' } - value 'created_desc', 'Created at descending order', deprecated: { reason: 'Use CREATED_DESC', milestone: '13.5' } - value 'created_asc', 'Created at ascending order', deprecated: { reason: 'Use CREATED_ASC', milestone: '13.5' } + value 'updated_desc', 'Updated at descending order', value: :updated_desc, deprecated: { reason: 'Use UPDATED_DESC', milestone: '13.5' } + value 'updated_asc', 'Updated at ascending order', value: :updated_asc, deprecated: { reason: 'Use UPDATED_ASC', milestone: '13.5' } + value 'created_desc', 'Created at descending order', value: :created_desc, deprecated: { reason: 'Use CREATED_DESC', milestone: '13.5' } + value 'created_asc', 'Created at ascending order', value: :created_asc, deprecated: { reason: 'Use CREATED_ASC', milestone: '13.5' } value 'UPDATED_DESC', 'Updated at descending order', value: :updated_desc value 'UPDATED_ASC', 'Updated at ascending order', value: :updated_asc diff --git a/app/graphql/types/terraform/state_type.rb b/app/graphql/types/terraform/state_type.rb index 05b6d130f19..d97e673bf31 100644 --- a/app/graphql/types/terraform/state_type.rb +++ b/app/graphql/types/terraform/state_type.rb @@ -19,9 +19,7 @@ module Types field :locked_by_user, Types::UserType, null: true, - authorize: :read_user, - description: 'The user currently holding a lock on the Terraform state', - resolve: -> (state, _, _) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, state.locked_by_user_id).find } + description: 'The user currently holding a lock on the Terraform state' field :locked_at, Types::TimeType, null: true, @@ -39,6 +37,10 @@ module Types field :updated_at, Types::TimeType, null: false, description: 'Timestamp the Terraform state was updated' + + def locked_by_user + Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.locked_by_user_id).find + end end end end diff --git a/app/graphql/types/terraform/state_version_type.rb b/app/graphql/types/terraform/state_version_type.rb index b1fbe42ecaf..a3af5c876ca 100644 --- a/app/graphql/types/terraform/state_version_type.rb +++ b/app/graphql/types/terraform/state_version_type.rb @@ -3,6 +3,8 @@ module Types module Terraform class StateVersionType < BaseObject + include ::API::Helpers::RelatedResourcesHelpers + graphql_name 'TerraformStateVersion' authorize :read_terraform_state @@ -13,15 +15,20 @@ module Types field :created_by_user, Types::UserType, null: true, - authorize: :read_user, - description: 'The user that created this version', - resolve: -> (version, _, _) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, version.created_by_user_id).find } + description: 'The user that created this version' + + field :download_path, GraphQL::STRING_TYPE, + null: true, + description: "URL for downloading the version's JSON file" field :job, Types::Ci::JobType, null: true, - authorize: :read_build, - description: 'The job that created this version', - resolve: -> (version, _, _) { Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Build, version.ci_build_id).find } + description: 'The job that created this version' + + field :serial, GraphQL::INT_TYPE, + null: true, + description: 'Serial number of the version', + method: :version field :created_at, Types::TimeType, null: false, @@ -30,6 +37,22 @@ module Types field :updated_at, Types::TimeType, null: false, description: 'Timestamp the version was updated' + + def created_by_user + Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.created_by_user_id).find + end + + def download_path + expose_path api_v4_projects_terraform_state_versions_path( + id: object.project_id, + name: object.terraform_state.name, + serial: object.version + ) + end + + def job + Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Build, object.ci_build_id).find + end end end end diff --git a/app/graphql/types/todo_type.rb b/app/graphql/types/todo_type.rb index 4f21da3d897..3694980ef93 100644 --- a/app/graphql/types/todo_type.rb +++ b/app/graphql/types/todo_type.rb @@ -16,19 +16,16 @@ module Types field :project, Types::ProjectType, description: 'The project this todo is associated with', null: true, - authorize: :read_project, - resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, todo.project_id).find } + authorize: :read_project field :group, Types::GroupType, description: 'Group this todo is associated with', null: true, - authorize: :read_group, - resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, todo.group_id).find } + authorize: :read_group field :author, Types::UserType, description: 'The author of this todo', - null: false, - resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, todo.author_id).find } + null: false field :action, Types::TodoActionEnum, description: 'Action of the todo', @@ -50,5 +47,17 @@ module Types field :created_at, Types::TimeType, description: 'Timestamp this todo was created', null: false + + def project + Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find + end + + def group + Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, object.group_id).find + end + + def author + Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find + end end end diff --git a/app/graphql/types/tree/blob_type.rb b/app/graphql/types/tree/blob_type.rb index cc6bf7b4f00..a7b90d2533b 100644 --- a/app/graphql/types/tree/blob_type.rb +++ b/app/graphql/types/tree/blob_type.rb @@ -15,13 +15,14 @@ module Types field :web_path, GraphQL::STRING_TYPE, null: true, description: 'Web path of the blob' field :lfs_oid, GraphQL::STRING_TYPE, null: true, - description: 'LFS ID of the blob', - resolve: -> (blob, args, ctx) do - Gitlab::Graphql::Loaders::BatchLfsOidLoader.new(blob.repository, blob.id).find - end + description: 'LFS ID of the blob' field :mode, GraphQL::STRING_TYPE, null: true, description: 'Blob mode in numeric format' - # rubocop: enable Graphql/AuthorizeTypes + + def lfs_oid + Gitlab::Graphql::Loaders::BatchLfsOidLoader.new(object.repository, object.id).find + end end + # rubocop: enable Graphql/AuthorizeTypes end end diff --git a/app/graphql/types/tree/tree_type.rb b/app/graphql/types/tree/tree_type.rb index b9fb6b28e71..fecd6c0f309 100644 --- a/app/graphql/types/tree/tree_type.rb +++ b/app/graphql/types/tree/tree_type.rb @@ -8,27 +8,32 @@ module Types # Complexity 10 as it triggers a Gitaly call on each render field :last_commit, Types::CommitType, - null: true, complexity: 10, calls_gitaly: true, resolver: Resolvers::LastCommitResolver, - description: 'Last commit for the tree' + null: true, complexity: 10, calls_gitaly: true, resolver: Resolvers::LastCommitResolver, + description: 'Last commit for the tree' field :trees, Types::Tree::TreeEntryType.connection_type, null: false, - description: 'Trees of the tree', - resolve: -> (obj, args, ctx) do - Gitlab::Graphql::Representation::TreeEntry.decorate(obj.trees, obj.repository) - end + description: 'Trees of the tree' field :submodules, Types::Tree::SubmoduleType.connection_type, null: false, description: 'Sub-modules of the tree', - calls_gitaly: true, resolve: -> (obj, args, ctx) do - Gitlab::Graphql::Representation::SubmoduleTreeEntry.decorate(obj.submodules, obj) - end + calls_gitaly: true field :blobs, Types::Tree::BlobType.connection_type, null: false, description: 'Blobs of the tree', - calls_gitaly: true, resolve: -> (obj, args, ctx) do - Gitlab::Graphql::Representation::TreeEntry.decorate(obj.blobs, obj.repository) - end - # rubocop: enable Graphql/AuthorizeTypes + calls_gitaly: true + + def trees + Gitlab::Graphql::Representation::TreeEntry.decorate(object.trees, object.repository) + end + + def submodules + Gitlab::Graphql::Representation::SubmoduleTreeEntry.decorate(object.submodules, object) + end + + def blobs + Gitlab::Graphql::Representation::TreeEntry.decorate(object.blobs, object.repository) + end end + # rubocop: enable Graphql/AuthorizeTypes end end diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb index 783a0d8425a..93503268319 100644 --- a/app/graphql/types/user_type.rb +++ b/app/graphql/types/user_type.rb @@ -19,7 +19,10 @@ module Types field :state, Types::UserStateEnum, null: false, description: 'State of the user' field :email, GraphQL::STRING_TYPE, null: true, - description: 'User email', method: :public_email + description: 'User email', method: :public_email, + deprecated: { reason: 'Use public_email', milestone: '13.7' } + field :public_email, GraphQL::STRING_TYPE, null: true, + description: "User's public email" field :avatar_url, GraphQL::STRING_TYPE, null: true, description: "URL of the user's avatar" field :web_url, GraphQL::STRING_TYPE, null: false, @@ -37,19 +40,24 @@ module Types feature_flag: :user_group_counts field :status, Types::UserStatusType, null: true, description: 'User status' + field :location, ::GraphQL::STRING_TYPE, null: true, + description: 'The location of the user.' field :project_memberships, Types::ProjectMemberType.connection_type, null: true, description: 'Project memberships of the user' field :starred_projects, Types::ProjectType.connection_type, null: true, description: 'Projects starred by the user', resolver: Resolvers::UserStarredProjectsResolver - # Merge request field: MRs can be either authored or assigned: + # Merge request field: MRs can be authored, assigned, or assigned-for-review: field :authored_merge_requests, resolver: Resolvers::AuthoredMergeRequestsResolver, description: 'Merge Requests authored by the user' field :assigned_merge_requests, resolver: Resolvers::AssignedMergeRequestsResolver, description: 'Merge Requests assigned to the user' + field :review_requested_merge_requests, + resolver: Resolvers::ReviewRequestedMergeRequestsResolver, + description: 'Merge Requests assigned to the user for review' field :snippets, Types::SnippetType.connection_type, -- cgit v1.2.1