From 9dc93a4519d9d5d7be48ff274127136236a3adb3 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 20 Apr 2021 23:50:22 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-11-stable-ee --- app/graphql/types/base_argument.rb | 4 +- app/graphql/types/base_enum.rb | 27 +++++- app/graphql/types/base_field.rb | 41 ++++++++- app/graphql/types/base_interface.rb | 6 ++ app/graphql/types/base_object.rb | 8 ++ app/graphql/types/base_union.rb | 3 + app/graphql/types/board_type.rb | 6 ++ .../types/boards/assignee_wildcard_id_enum.rb | 13 +++ .../types/boards/board_issuable_input_base_type.rb | 20 +++++ .../types/boards/board_issue_input_base_type.rb | 17 +--- app/graphql/types/boards/board_issue_input_type.rb | 13 ++- .../types/boards/negated_board_issue_input_type.rb | 10 +++ app/graphql/types/ci/job_status_enum.rb | 15 ++++ app/graphql/types/ci/job_type.rb | 99 ++++++++++++++++++++-- .../types/ci/pipeline_config_source_enum.rb | 3 +- app/graphql/types/ci/pipeline_status_enum.rb | 4 +- app/graphql/types/ci/pipeline_type.rb | 45 +++++++++- app/graphql/types/ci/recent_failures_type.rb | 20 +++++ app/graphql/types/ci/stage_type.rb | 35 +++++++- app/graphql/types/ci/test_case_status_enum.rb | 15 ++++ app/graphql/types/ci/test_case_type.rb | 41 +++++++++ app/graphql/types/ci/test_report_summary_type.rb | 19 +++++ app/graphql/types/ci/test_report_total_type.rb | 33 ++++++++ app/graphql/types/ci/test_suite_summary_type.rb | 41 +++++++++ app/graphql/types/ci/test_suite_type.rb | 41 +++++++++ app/graphql/types/concerns/find_closest.rb | 11 +++ .../types/concerns/gitlab_style_deprecations.rb | 18 ++-- app/graphql/types/global_id_type.rb | 13 ++- app/graphql/types/group_type.rb | 69 +++++++++++---- app/graphql/types/issue_type.rb | 3 + .../issues/negated_issue_filter_input_type.rb | 27 ++++++ app/graphql/types/jira_users_mapping_input_type.rb | 12 +-- .../types/merge_request_review_state_enum.rb | 11 +++ app/graphql/types/merge_request_state_enum.rb | 2 +- app/graphql/types/merge_request_type.rb | 7 +- app/graphql/types/merge_requests/reviewer_type.rb | 26 ++++++ app/graphql/types/milestone_type.rb | 3 + app/graphql/types/mutation_operation_mode_enum.rb | 8 ++ app/graphql/types/mutation_type.rb | 1 + .../types/packages/conan/file_metadatum_type.rb | 22 +++++ .../packages/conan/metadatum_file_type_enum.rb | 16 ++++ app/graphql/types/packages/conan/metadatum_type.rb | 22 +++++ app/graphql/types/packages/file_metadata_type.rb | 27 ++++++ app/graphql/types/packages/metadata_type.rb | 4 +- app/graphql/types/packages/package_details_type.rb | 20 +++++ app/graphql/types/packages/package_file_type.rb | 36 ++++++++ app/graphql/types/packages/package_type.rb | 47 +++++++++- .../packages/package_without_versions_type.rb | 44 ---------- app/graphql/types/project_type.rb | 6 ++ app/graphql/types/query_type.rb | 34 ++++---- app/graphql/types/repository/blob_type.rb | 40 +++++++++ app/graphql/types/repository_type.rb | 5 ++ app/graphql/types/sort_enum.rb | 32 ++++++- app/graphql/types/timelog_type.rb | 42 +++++++++ .../types/user_merge_request_interaction_type.rb | 47 ++++++++++ app/graphql/types/user_type.rb | 75 ++++++++++------ 56 files changed, 1142 insertions(+), 167 deletions(-) create mode 100644 app/graphql/types/boards/assignee_wildcard_id_enum.rb create mode 100644 app/graphql/types/boards/board_issuable_input_base_type.rb create mode 100644 app/graphql/types/boards/negated_board_issue_input_type.rb create mode 100644 app/graphql/types/ci/job_status_enum.rb create mode 100644 app/graphql/types/ci/recent_failures_type.rb create mode 100644 app/graphql/types/ci/test_case_status_enum.rb create mode 100644 app/graphql/types/ci/test_case_type.rb create mode 100644 app/graphql/types/ci/test_report_summary_type.rb create mode 100644 app/graphql/types/ci/test_report_total_type.rb create mode 100644 app/graphql/types/ci/test_suite_summary_type.rb create mode 100644 app/graphql/types/ci/test_suite_type.rb create mode 100644 app/graphql/types/concerns/find_closest.rb create mode 100644 app/graphql/types/issues/negated_issue_filter_input_type.rb create mode 100644 app/graphql/types/merge_request_review_state_enum.rb create mode 100644 app/graphql/types/merge_requests/reviewer_type.rb create mode 100644 app/graphql/types/packages/conan/file_metadatum_type.rb create mode 100644 app/graphql/types/packages/conan/metadatum_file_type_enum.rb create mode 100644 app/graphql/types/packages/conan/metadatum_type.rb create mode 100644 app/graphql/types/packages/file_metadata_type.rb create mode 100644 app/graphql/types/packages/package_details_type.rb create mode 100644 app/graphql/types/packages/package_file_type.rb delete mode 100644 app/graphql/types/packages/package_without_versions_type.rb create mode 100644 app/graphql/types/repository/blob_type.rb create mode 100644 app/graphql/types/timelog_type.rb create mode 100644 app/graphql/types/user_merge_request_interaction_type.rb (limited to 'app/graphql/types') diff --git a/app/graphql/types/base_argument.rb b/app/graphql/types/base_argument.rb index 4ad9e8c0e40..ff9a5a0611d 100644 --- a/app/graphql/types/base_argument.rb +++ b/app/graphql/types/base_argument.rb @@ -4,8 +4,10 @@ module Types class BaseArgument < GraphQL::Schema::Argument include GitlabStyleDeprecations + attr_reader :deprecation + def initialize(*args, **kwargs, &block) - kwargs = gitlab_deprecation(kwargs) + @deprecation = gitlab_deprecation(kwargs) super(*args, **kwargs, &block) end diff --git a/app/graphql/types/base_enum.rb b/app/graphql/types/base_enum.rb index 4d470aceca4..518a902a5d7 100644 --- a/app/graphql/types/base_enum.rb +++ b/app/graphql/types/base_enum.rb @@ -21,12 +21,23 @@ module Types graphql_name(enum_mod.name) if use_name description(enum_mod.description) if use_description - enum_mod.definition.each { |key, content| value(key.to_s.upcase, **content) } + enum_mod.definition.each do |key, content| + value(key.to_s.upcase, **content) + end + end + + # Helper to define an enum member for each element of a Rails AR enum + def from_rails_enum(enum, description:) + enum.each_key do |name| + value name.to_s.upcase, + value: name, + description: format(description, name: name) + end end def value(*args, **kwargs, &block) enum[args[0].downcase] = kwargs[:value] || args[0] - kwargs = gitlab_deprecation(kwargs) + gitlab_deprecation(kwargs) super(*args, **kwargs, &block) end @@ -36,6 +47,18 @@ module Types def enum @enum_values ||= {}.with_indifferent_access end + + def authorization + @authorization ||= ::Gitlab::Graphql::Authorize::ObjectAuthorization.new(authorize) + end + + def authorize(*abilities) + @abilities = abilities + end + + def authorized?(object, context) + authorization.ok?(object, context[:current_user]) + end end end end diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb index 78ab6890923..7c939f94dde 100644 --- a/app/graphql/types/base_field.rb +++ b/app/graphql/types/base_field.rb @@ -2,28 +2,30 @@ module Types class BaseField < GraphQL::Schema::Field - prepend Gitlab::Graphql::Authorize include GitlabStyleDeprecations argument_class ::Types::BaseArgument DEFAULT_COMPLEXITY = 1 + attr_reader :deprecation + def initialize(**kwargs, &block) @calls_gitaly = !!kwargs.delete(:calls_gitaly) @constant_complexity = kwargs[:complexity].is_a?(Integer) && kwargs[:complexity] > 0 @requires_argument = !!kwargs.delete(:requires_argument) + @authorize = Array.wrap(kwargs.delete(:authorize)) kwargs[:complexity] = field_complexity(kwargs[:resolver_class], kwargs[:complexity]) @feature_flag = kwargs[:feature_flag] kwargs = check_feature_flag(kwargs) - kwargs = gitlab_deprecation(kwargs) + @deprecation = gitlab_deprecation(kwargs) super(**kwargs, &block) # We want to avoid the overhead of this in prod extension ::Gitlab::Graphql::CallsGitaly::FieldExtension if Gitlab.dev_or_test_env? - extension ::Gitlab::Graphql::Present::FieldExtension + extension ::Gitlab::Graphql::Authorize::ConnectionFilterExtension end def may_call_gitaly? @@ -34,6 +36,19 @@ module Types @requires_argument || arguments.values.any? { |argument| argument.type.non_null? } end + # By default fields authorize against the current object, but that is not how our + # resolvers work - they use declarative permissions to authorize fields + # manually (so we make them opt in). + # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/300922 + # (separate out authorize into permissions on the object, and on the + # resolved values) + # We do not support argument authorization in our schema. If/when we do, + # we should call `super` here, to apply argument authorization checks. + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/324647 + def authorized?(object, args, ctx) + field_authorized?(object, ctx) && resolver_authorized?(object, ctx) + end + def base_complexity complexity = DEFAULT_COMPLEXITY complexity += 1 if calls_gitaly? @@ -58,6 +73,26 @@ module Types attr_reader :feature_flag + def field_authorized?(object, ctx) + authorization.ok?(object, ctx[:current_user]) + end + + # Historically our resolvers have used declarative permission checks only + # for _what they resolved_, not the _object they resolved these things from_ + # We preserve these semantics here, and only apply resolver authorization + # if the resolver has opted in. + def resolver_authorized?(object, ctx) + if @resolver_class && @resolver_class.try(:authorizes_object?) + @resolver_class.authorized?(object, ctx) + else + true + end + end + + def authorization + @authorization ||= ::Gitlab::Graphql::Authorize::ObjectAuthorization.new(@authorize) + end + def feature_documentation_message(key, description) "#{description} Available only when feature flag `#{key}` is enabled." end diff --git a/app/graphql/types/base_interface.rb b/app/graphql/types/base_interface.rb index 4b1f3193136..c21c95876be 100644 --- a/app/graphql/types/base_interface.rb +++ b/app/graphql/types/base_interface.rb @@ -5,5 +5,11 @@ module Types include GraphQL::Schema::Interface field_class ::Types::BaseField + + definition_methods do + def authorized?(object, context) + resolve_type(object, context).authorized?(object, context) + end + end end end diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb index 9c36c83d4a3..cd677e50d28 100644 --- a/app/graphql/types/base_object.rb +++ b/app/graphql/types/base_object.rb @@ -19,6 +19,14 @@ module Types GitlabSchema.id_from_object(object) end + def self.authorization + @authorization ||= ::Gitlab::Graphql::Authorize::ObjectAuthorization.new(authorize) + end + + def self.authorized?(object, context) + authorization.ok?(object, context[:current_user]) + end + def current_user context[:current_user] end diff --git a/app/graphql/types/base_union.rb b/app/graphql/types/base_union.rb index 30a5668c0bb..aeafbf85020 100644 --- a/app/graphql/types/base_union.rb +++ b/app/graphql/types/base_union.rb @@ -2,5 +2,8 @@ module Types class BaseUnion < GraphQL::Schema::Union + def self.authorized?(object, context) + resolve_type(object, context).authorized?(object, context) + end end end diff --git a/app/graphql/types/board_type.rb b/app/graphql/types/board_type.rb index f33f3f5e537..42d8eecc366 100644 --- a/app/graphql/types/board_type.rb +++ b/app/graphql/types/board_type.rb @@ -20,6 +20,12 @@ module Types field :hide_closed_list, type: GraphQL::BOOLEAN_TYPE, null: true, description: 'Whether or not closed list is hidden.' + field :created_at, Types::TimeType, null: false, + description: 'Timestamp of when the board was created.' + + field :updated_at, Types::TimeType, null: false, + description: 'Timestamp of when the board was last updated.' + field :lists, Types::BoardListType.connection_type, null: true, diff --git a/app/graphql/types/boards/assignee_wildcard_id_enum.rb b/app/graphql/types/boards/assignee_wildcard_id_enum.rb new file mode 100644 index 00000000000..ba9058a78d9 --- /dev/null +++ b/app/graphql/types/boards/assignee_wildcard_id_enum.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Types + module Boards + class AssigneeWildcardIdEnum < BaseEnum + graphql_name 'AssigneeWildcardId' + description 'Assignee ID wildcard values' + + value 'NONE', 'No assignee is assigned.' + value 'ANY', 'An assignee is assigned.' + end + end +end diff --git a/app/graphql/types/boards/board_issuable_input_base_type.rb b/app/graphql/types/boards/board_issuable_input_base_type.rb new file mode 100644 index 00000000000..2cd057347d6 --- /dev/null +++ b/app/graphql/types/boards/board_issuable_input_base_type.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Types + module Boards + # Common arguments that we can be used to filter boards epics and issues + class BoardIssuableInputBaseType < BaseInputObject + argument :label_name, [GraphQL::STRING_TYPE, null: true], + required: false, + description: 'Filter by label name.' + + argument :author_username, GraphQL::STRING_TYPE, + required: false, + description: 'Filter by author username.' + + argument :my_reaction_emoji, GraphQL::STRING_TYPE, + required: false, + description: 'Filter by reaction emoji applied by the current user.' + end + end +end diff --git a/app/graphql/types/boards/board_issue_input_base_type.rb b/app/graphql/types/boards/board_issue_input_base_type.rb index b762cef6e58..7cf2dcb9c82 100644 --- a/app/graphql/types/boards/board_issue_input_base_type.rb +++ b/app/graphql/types/boards/board_issue_input_base_type.rb @@ -2,30 +2,19 @@ module Types module Boards - class BoardIssueInputBaseType < BaseInputObject - argument :label_name, GraphQL::STRING_TYPE.to_list_type, - required: false, - description: 'Filter by label name.' - + # rubocop: disable Graphql/AuthorizeTypes + class BoardIssueInputBaseType < BoardIssuableInputBaseType argument :milestone_title, GraphQL::STRING_TYPE, required: false, description: 'Filter by milestone title.' - argument :assignee_username, GraphQL::STRING_TYPE.to_list_type, + argument :assignee_username, [GraphQL::STRING_TYPE, null: true], required: false, description: 'Filter by assignee username.' - argument :author_username, GraphQL::STRING_TYPE, - required: false, - description: 'Filter by author username.' - argument :release_tag, GraphQL::STRING_TYPE, required: false, description: 'Filter by release tag.' - - argument :my_reaction_emoji, GraphQL::STRING_TYPE, - required: false, - description: 'Filter by reaction emoji.' end end end diff --git a/app/graphql/types/boards/board_issue_input_type.rb b/app/graphql/types/boards/board_issue_input_type.rb index 9cc0f484a16..8c0e37e5cb7 100644 --- a/app/graphql/types/boards/board_issue_input_type.rb +++ b/app/graphql/types/boards/board_issue_input_type.rb @@ -2,19 +2,24 @@ module Types module Boards - class NegatedBoardIssueInputType < BoardIssueInputBaseType - end - class BoardIssueInputType < BoardIssueInputBaseType graphql_name 'BoardIssueInput' argument :not, NegatedBoardIssueInputType, required: false, - description: 'List of negated params. Warning: this argument is experimental and a subject to change in future.' + prepare: ->(negated_args, ctx) { negated_args.to_h }, + description: <<~MD + List of negated arguments. + Warning: this argument is experimental and a subject to change in future. + MD argument :search, GraphQL::STRING_TYPE, required: false, description: 'Search query for issue title or description.' + + argument :assignee_wildcard_id, ::Types::Boards::AssigneeWildcardIdEnum, + required: false, + description: 'Filter by assignee wildcard. Incompatible with assigneeUsername.' end end end diff --git a/app/graphql/types/boards/negated_board_issue_input_type.rb b/app/graphql/types/boards/negated_board_issue_input_type.rb new file mode 100644 index 00000000000..a0fab2ae969 --- /dev/null +++ b/app/graphql/types/boards/negated_board_issue_input_type.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Types + module Boards + class NegatedBoardIssueInputType < BoardIssueInputBaseType + end + end +end + +Types::Boards::NegatedBoardIssueInputType.prepend_if_ee('::EE::Types::Boards::NegatedBoardIssueInputType') diff --git a/app/graphql/types/ci/job_status_enum.rb b/app/graphql/types/ci/job_status_enum.rb new file mode 100644 index 00000000000..ec80b1f4776 --- /dev/null +++ b/app/graphql/types/ci/job_status_enum.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Types + module Ci + class JobStatusEnum < BaseEnum + graphql_name 'CiJobStatus' + + ::Ci::HasStatus::AVAILABLE_STATUSES.each do |status| + value status.upcase, + description: "A job that is #{status.tr('_', ' ')}.", + value: status + end + end + end +end diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb index c86337eea89..94a256fed3d 100644 --- a/app/graphql/types/ci/job_type.rb +++ b/app/graphql/types/ci/job_type.rb @@ -6,27 +6,74 @@ module Types graphql_name 'CiJob' authorize :read_commit_status + connection_type_class(Types::CountableConnectionType) + + field :id, ::Types::GlobalIDType[::CommitStatus].as('JobID'), null: true, + description: 'ID of the job.' 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.' field :needs, BuildNeedType.connection_type, null: true, description: 'References to builds that must complete before the jobs run.' - field :detailed_status, Types::Ci::DetailedStatusType, null: true, - description: 'Detailed status of the job.' + field :status, + type: ::Types::Ci::JobStatusEnum, + null: true, + description: "Status of the job." + field :stage, Types::Ci::StageType, null: true, + description: 'Stage of the job.' + field :allow_failure, ::GraphQL::BOOLEAN_TYPE, null: false, + description: 'Whether this job is allowed to fail.' + field :duration, GraphQL::INT_TYPE, null: true, + description: 'Duration of the job in seconds.' + field :tags, [GraphQL::STRING_TYPE], null: true, + description: 'Tags for the current job.' + + # Life-cycle timestamps: + field :created_at, Types::TimeType, null: false, + description: "When the job was created." + field :queued_at, Types::TimeType, null: true, + description: 'When the job was enqueued and marked as pending.' + field :started_at, Types::TimeType, null: true, + description: 'When the job was started.' + field :finished_at, Types::TimeType, null: true, + description: 'When a job has finished running.' field :scheduled_at, Types::TimeType, null: true, description: 'Schedule for the build.' + + field :detailed_status, Types::Ci::DetailedStatusType, null: true, + description: 'Detailed status of the job.' field :artifacts, Types::Ci::JobArtifactType.connection_type, null: true, description: 'Artifacts generated by the job.' - field :finished_at, Types::TimeType, null: true, - description: 'When a job has finished running.' - field :duration, GraphQL::INT_TYPE, null: true, - description: 'Duration of the job in seconds.' + field :short_sha, type: GraphQL::STRING_TYPE, null: false, + description: 'Short SHA1 ID of the commit.' + field :scheduling_type, GraphQL::STRING_TYPE, null: true, + description: 'Type of pipeline scheduling. Value is `dag` if the pipeline uses the `needs` keyword, and `stage` otherwise.' + field :commit_path, GraphQL::STRING_TYPE, null: true, + description: 'Path to the commit that triggered the job.' + field :ref_name, GraphQL::STRING_TYPE, null: true, + description: 'Ref name of the job.' + field :ref_path, GraphQL::STRING_TYPE, null: true, + description: 'Path to the ref.' + field :playable, GraphQL::BOOLEAN_TYPE, null: false, method: :playable?, + description: 'Indicates the job can be played.' + field :retryable, GraphQL::BOOLEAN_TYPE, null: false, method: :retryable?, + description: 'Indicates the job can be retried.' + field :cancelable, GraphQL::BOOLEAN_TYPE, null: false, method: :cancelable?, + description: 'Indicates the job can be canceled.' + field :active, GraphQL::BOOLEAN_TYPE, null: false, method: :active?, + description: 'Indicates the job is active.' + field :coverage, GraphQL::FLOAT_TYPE, null: true, + description: 'Coverage level of the job.' def pipeline Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Pipeline, object.pipeline_id).find end + def tags + object.tags.map(&:name) if object.is_a?(::Ci::Build) + end + def detailed_status object.detailed_status(context[:current_user]) end @@ -36,6 +83,46 @@ module Types object.job_artifacts end end + + def stage + ::Gitlab::Graphql::Lazy.with_value(pipeline) do |pl| + BatchLoader::GraphQL.for([pl, object.stage]).batch do |ids, loader| + by_pipeline = ids + .group_by(&:first) + .transform_values { |grp| grp.map(&:second) } + + by_pipeline.each do |p, names| + p.stages.by_name(names).each { |s| loader.call([p, s.name], s) } + end + end + end + end + + # This class is a secret union! + # TODO: turn this into an actual union, so that fields can be referenced safely! + def id + return unless object.id.present? + + model_name = object.type || ::CommitStatus.name + id = object.id + Gitlab::GlobalId.build(model_name: model_name, id: id) + end + + def commit_path + ::Gitlab::Routing.url_helpers.project_commit_path(object.project, object.sha) + end + + def ref_name + object&.ref + end + + def ref_path + ::Gitlab::Routing.url_helpers.project_commits_path(object.project, ref_name) + end + + def coverage + object&.coverage + end end end end diff --git a/app/graphql/types/ci/pipeline_config_source_enum.rb b/app/graphql/types/ci/pipeline_config_source_enum.rb index e1575cb2f99..96c8a5f2941 100644 --- a/app/graphql/types/ci/pipeline_config_source_enum.rb +++ b/app/graphql/types/ci/pipeline_config_source_enum.rb @@ -4,7 +4,8 @@ module Types module Ci class PipelineConfigSourceEnum < BaseEnum ::Enums::Ci::Pipeline.config_sources.keys.each do |state_symbol| - value state_symbol.to_s.upcase, value: state_symbol.to_s + description = state_symbol == :auto_devops_source ? "Auto DevOps source." : "#{state_symbol.to_s.titleize.capitalize}." # This is needed to avoid failure in doc lint + value state_symbol.to_s.upcase, value: state_symbol.to_s, description: description end end end diff --git a/app/graphql/types/ci/pipeline_status_enum.rb b/app/graphql/types/ci/pipeline_status_enum.rb index c19ddf5bb25..e0b2020dcc1 100644 --- a/app/graphql/types/ci/pipeline_status_enum.rb +++ b/app/graphql/types/ci/pipeline_status_enum.rb @@ -4,7 +4,9 @@ module Types module Ci class PipelineStatusEnum < BaseEnum ::Ci::Pipeline.all_state_names.each do |state_symbol| - value state_symbol.to_s.upcase, value: state_symbol.to_s + value state_symbol.to_s.upcase, + description: ::Ci::Pipeline::STATUSES_DESCRIPTION[state_symbol], + value: state_symbol.to_s end end end diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb index 49be200a788..2e83f6c1f5a 100644 --- a/app/graphql/types/ci/pipeline_type.rb +++ b/app/graphql/types/ci/pipeline_type.rb @@ -81,6 +81,20 @@ module Types description: 'Jobs belonging to the pipeline.', resolver: ::Resolvers::Ci::JobsResolver + field :job, + type: ::Types::Ci::JobType, + null: true, + description: 'A specific job in this pipeline, either by name or ID.' do + argument :id, + type: ::Types::GlobalIDType[::CommitStatus], + required: false, + description: 'ID of the job.' + argument :name, + type: ::GraphQL::STRING_TYPE, + required: false, + description: 'Name of the job.' + end + field :source_job, Types::Ci::JobType, null: true, description: 'Job where pipeline was triggered from.' @@ -104,8 +118,24 @@ module Types field :active, GraphQL::BOOLEAN_TYPE, null: false, method: :active?, description: 'Indicates if the pipeline is active.' + field :uses_needs, GraphQL::BOOLEAN_TYPE, null: true, + method: :uses_needs?, + description: 'Indicates if the pipeline has jobs with `needs` dependencies.' + + field :test_report_summary, + Types::Ci::TestReportSummaryType, + null: false, + description: 'Summary of the test report generated by the pipeline.', + resolver: Resolvers::Ci::TestReportSummaryResolver + + field :test_suite, + Types::Ci::TestSuiteType, + null: true, + description: 'A specific test suite in a pipeline test report.', + resolver: Resolvers::Ci::TestSuiteResolver + def detailed_status - object.detailed_status(context[:current_user]) + object.detailed_status(current_user) end def user @@ -119,6 +149,19 @@ module Types def path ::Gitlab::Routing.url_helpers.project_pipeline_path(object.project, object) end + + def job(id: nil, name: nil) + raise ::Gitlab::Graphql::Errors::ArgumentError, 'One of id or name is required' unless id || name + + if id + id = ::Types::GlobalIDType[::CommitStatus].coerce_isolated_input(id) if id + pipeline.statuses.id_in(id.model_id) + else + pipeline.statuses.by_name(name) + end.take # rubocop: disable CodeReuse/ActiveRecord + end + + alias_method :pipeline, :object end end end diff --git a/app/graphql/types/ci/recent_failures_type.rb b/app/graphql/types/ci/recent_failures_type.rb new file mode 100644 index 00000000000..eeff7222762 --- /dev/null +++ b/app/graphql/types/ci/recent_failures_type.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + class RecentFailuresType < BaseObject + graphql_name 'RecentFailures' + description 'Recent failure history of a test case.' + + connection_type_class(Types::CountableConnectionType) + + field :count, GraphQL::INT_TYPE, null: true, + description: 'Number of times the test case has failed in the past 14 days.' + + field :base_branch, GraphQL::STRING_TYPE, null: true, + description: 'Name of the base branch of the project.' + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/ci/stage_type.rb b/app/graphql/types/ci/stage_type.rb index 836f2430890..56b4f248697 100644 --- a/app/graphql/types/ci/stage_type.rb +++ b/app/graphql/types/ci/stage_type.rb @@ -12,10 +12,13 @@ module Types extras: [:lookahead], description: 'Group of jobs for the stage.' field :detailed_status, Types::Ci::DetailedStatusType, null: true, - description: 'Detailed status of the stage.' + description: 'Detailed status of the stage.' + field :jobs, Ci::JobType.connection_type, null: true, + description: 'Jobs for the stage.', + method: 'latest_statuses' def detailed_status - object.detailed_status(context[:current_user]) + object.detailed_status(current_user) end # Issues one query per pipeline @@ -33,6 +36,34 @@ module Types jobs_for_pipeline(pl, indexed.keys, include_needs).each do |stage_id, statuses| key = indexed[stage_id] groups = ::Ci::Group.fabricate(project, key.stage, statuses) + + if Feature.enabled?(:ci_no_empty_groups, project) + groups.each do |group| + rejected = group.jobs.reject { |job| Ability.allowed?(current_user, :read_commit_status, job) } + group.jobs.select! { |job| Ability.allowed?(current_user, :read_commit_status, job) } + next unless group.jobs.empty? + + exc = StandardError.new('Empty Ci::Group') + traces = rejected.map do |job| + trace = [] + policy = Ability.policy_for(current_user, job) + policy.debug(:read_commit_status, trace) + trace + end + extra = { + current_user_id: current_user&.id, + project_id: project.id, + pipeline_id: pl.id, + stage_id: stage_id, + group_name: group.name, + rejected_job_ids: rejected.map(&:id), + rejected_traces: traces + } + Gitlab::ErrorTracking.track_exception(exc, extra) + end + groups.reject! { |group| group.jobs.empty? } + end + loader.call(key, groups) end end diff --git a/app/graphql/types/ci/test_case_status_enum.rb b/app/graphql/types/ci/test_case_status_enum.rb new file mode 100644 index 00000000000..6a5f8bc2a59 --- /dev/null +++ b/app/graphql/types/ci/test_case_status_enum.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Types + module Ci + class TestCaseStatusEnum < BaseEnum + graphql_name 'TestCaseStatus' + + ::Gitlab::Ci::Reports::TestCase::STATUS_TYPES.each do |status| + value status, + description: "Test case that has a status of #{status}.", + value: status + end + end + end +end diff --git a/app/graphql/types/ci/test_case_type.rb b/app/graphql/types/ci/test_case_type.rb new file mode 100644 index 00000000000..9cc3f918125 --- /dev/null +++ b/app/graphql/types/ci/test_case_type.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + class TestCaseType < BaseObject + graphql_name 'TestCase' + description 'Test case in pipeline test report.' + + connection_type_class(Types::CountableConnectionType) + + field :status, Types::Ci::TestCaseStatusEnum, null: true, + description: "Status of the test case (#{::Gitlab::Ci::Reports::TestCase::STATUS_TYPES.join(', ')})." + + field :name, GraphQL::STRING_TYPE, null: true, + description: 'Name of the test case.' + + field :classname, GraphQL::STRING_TYPE, null: true, + description: 'Classname of the test case.' + + field :execution_time, GraphQL::FLOAT_TYPE, null: true, + description: 'Test case execution time in seconds.' + + field :file, GraphQL::STRING_TYPE, null: true, + description: 'Path to the file of the test case.' + + field :attachment_url, GraphQL::STRING_TYPE, null: true, + description: 'URL of the test case attachment file.' + + field :system_output, GraphQL::STRING_TYPE, null: true, + description: 'System output of the test case.' + + field :stack_trace, GraphQL::STRING_TYPE, null: true, + description: 'Stack trace of the test case.' + + field :recent_failures, Types::Ci::RecentFailuresType, null: true, + description: 'Recent failure history of the test case on the base branch.' + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/ci/test_report_summary_type.rb b/app/graphql/types/ci/test_report_summary_type.rb new file mode 100644 index 00000000000..87207c8a765 --- /dev/null +++ b/app/graphql/types/ci/test_report_summary_type.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + # This is presented through `PipelineType` that has its own authorization + class TestReportSummaryType < BaseObject + graphql_name 'TestReportSummary' + description 'Test report for a pipeline' + + field :total, Types::Ci::TestReportTotalType, null: false, + description: 'Total report statistics for a pipeline test report.' + + field :test_suites, Types::Ci::TestSuiteSummaryType.connection_type, null: false, + description: 'Test suites belonging to a pipeline test report.' + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/ci/test_report_total_type.rb b/app/graphql/types/ci/test_report_total_type.rb new file mode 100644 index 00000000000..1123734adc3 --- /dev/null +++ b/app/graphql/types/ci/test_report_total_type.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + class TestReportTotalType < BaseObject + graphql_name 'TestReportTotal' + description 'Total test report statistics.' + + field :time, GraphQL::FLOAT_TYPE, null: true, + description: 'Total duration of the tests.' + + field :count, GraphQL::INT_TYPE, null: true, + description: 'Total number of the test cases.' + + field :success, GraphQL::INT_TYPE, null: true, + description: 'Total number of test cases that succeeded.' + + field :failed, GraphQL::INT_TYPE, null: true, + description: 'Total number of test cases that failed.' + + field :skipped, GraphQL::INT_TYPE, null: true, + description: 'Total number of test cases that were skipped.' + + field :error, GraphQL::INT_TYPE, null: true, + description: 'Total number of test cases that had an error.' + + field :suite_error, GraphQL::STRING_TYPE, null: true, + description: 'Test suite error message.' + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/ci/test_suite_summary_type.rb b/app/graphql/types/ci/test_suite_summary_type.rb new file mode 100644 index 00000000000..a80a9179cb4 --- /dev/null +++ b/app/graphql/types/ci/test_suite_summary_type.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + class TestSuiteSummaryType < BaseObject + graphql_name 'TestSuiteSummary' + description 'Test suite summary in a pipeline test report.' + + connection_type_class(Types::CountableConnectionType) + + field :name, GraphQL::STRING_TYPE, null: true, + description: 'Name of the test suite.' + + field :total_time, GraphQL::FLOAT_TYPE, null: true, + description: 'Total duration of the tests in the test suite.' + + field :total_count, GraphQL::INT_TYPE, null: true, + description: 'Total number of the test cases in the test suite.' + + field :success_count, GraphQL::INT_TYPE, null: true, + description: 'Total number of test cases that succeeded in the test suite.' + + field :failed_count, GraphQL::INT_TYPE, null: true, + description: 'Total number of test cases that failed in the test suite.' + + field :skipped_count, GraphQL::INT_TYPE, null: true, + description: 'Total number of test cases that were skipped in the test suite.' + + field :error_count, GraphQL::INT_TYPE, null: true, + description: 'Total number of test cases that had an error.' + + field :suite_error, GraphQL::STRING_TYPE, null: true, + description: 'Test suite error message.' + + field :build_ids, [GraphQL::ID_TYPE], null: true, + description: 'IDs of the builds used to run the test suite.' + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/ci/test_suite_type.rb b/app/graphql/types/ci/test_suite_type.rb new file mode 100644 index 00000000000..7d4c01da81b --- /dev/null +++ b/app/graphql/types/ci/test_suite_type.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + class TestSuiteType < BaseObject + graphql_name 'TestSuite' + description 'Test suite in a pipeline test report.' + + connection_type_class(Types::CountableConnectionType) + + field :name, GraphQL::STRING_TYPE, null: true, + description: 'Name of the test suite.' + + field :total_time, GraphQL::FLOAT_TYPE, null: true, + description: 'Total duration of the tests in the test suite.' + + field :total_count, GraphQL::INT_TYPE, null: true, + description: 'Total number of the test cases in the test suite.' + + field :success_count, GraphQL::INT_TYPE, null: true, + description: 'Total number of test cases that succeeded in the test suite.' + + field :failed_count, GraphQL::INT_TYPE, null: true, + description: 'Total number of test cases that failed in the test suite.' + + field :skipped_count, GraphQL::INT_TYPE, null: true, + description: 'Total number of test cases that were skipped in the test suite.' + + field :error_count, GraphQL::INT_TYPE, null: true, + description: 'Total number of test cases that had an error.' + + field :suite_error, GraphQL::STRING_TYPE, null: true, + description: 'Test suite error message.' + + field :test_cases, Types::Ci::TestCaseType.connection_type, null: true, + description: 'Test cases in the test suite.' + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/concerns/find_closest.rb b/app/graphql/types/concerns/find_closest.rb new file mode 100644 index 00000000000..1d76e872364 --- /dev/null +++ b/app/graphql/types/concerns/find_closest.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module FindClosest + # Find the closest node of a given type above this node, and return the domain object + def closest_parent(type, parent) + parent = parent.try(:parent) while parent && parent.object.class != type + return unless parent + + parent.object.object + end +end diff --git a/app/graphql/types/concerns/gitlab_style_deprecations.rb b/app/graphql/types/concerns/gitlab_style_deprecations.rb index ad195354930..802562ed958 100644 --- a/app/graphql/types/concerns/gitlab_style_deprecations.rb +++ b/app/graphql/types/concerns/gitlab_style_deprecations.rb @@ -7,25 +7,21 @@ module GitlabStyleDeprecations private + # Mutate the arguments, returns the deprecation def gitlab_deprecation(kwargs) if kwargs[:deprecation_reason].present? raise ArgumentError, 'Use `deprecated` property instead of `deprecation_reason`. ' \ 'See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#deprecating-fields-arguments-and-enum-values' end - deprecation = kwargs.delete(:deprecated) - return kwargs unless deprecation + deprecation = ::Gitlab::Graphql::Deprecation.parse(kwargs.delete(:deprecated)) + return unless deprecation - milestone, reason = deprecation.values_at(:milestone, :reason).map(&:presence) + raise ArgumentError, "Bad deprecation. #{deprecation.errors.full_messages.to_sentence}" unless deprecation.valid? - raise ArgumentError, 'Please provide a `milestone` within `deprecated`' unless milestone - raise ArgumentError, 'Please provide a `reason` within `deprecated`' unless reason - raise ArgumentError, '`milestone` must be a `String`' unless milestone.is_a?(String) + kwargs[:deprecation_reason] = deprecation.deprecation_reason + kwargs[:description] = deprecation.edit_description(kwargs[:description]) - deprecated_in = "Deprecated in #{milestone}" - kwargs[:deprecation_reason] = "#{reason}. #{deprecated_in}." - kwargs[:description] += " #{deprecated_in}: #{reason}." if kwargs[:description] - - kwargs + deprecation end end diff --git a/app/graphql/types/global_id_type.rb b/app/graphql/types/global_id_type.rb index 750bd1bfe8d..79061df7282 100644 --- a/app/graphql/types/global_id_type.rb +++ b/app/graphql/types/global_id_type.rb @@ -23,7 +23,7 @@ module Types A global identifier. A global identifier represents an object uniquely across the application. - An example of such an identifier is "gid://gitlab/User/1". + An example of such an identifier is `"gid://gitlab/User/1"`. Global identifiers are encoded as strings. DESC @@ -67,6 +67,17 @@ module Types graphql_name end + define_singleton_method(:as) do |new_name| + if @renamed && graphql_name != new_name + raise "Conflicting names for ID of #{model_class.name}: " \ + "#{graphql_name} and #{new_name}" + end + + @renamed = true + graphql_name(new_name) + self + end + define_singleton_method(:coerce_result) do |gid, ctx| global_id = ::Gitlab::GlobalId.as_global_id(gid, model_name: model_class.name) diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb index 7a84e76657b..a44281b2bdf 100644 --- a/app/graphql/types/group_type.rb +++ b/app/graphql/types/group_type.rb @@ -8,39 +8,65 @@ module Types expose_permissions Types::PermissionTypes::Group - field :web_url, GraphQL::STRING_TYPE, null: false, + field :web_url, + type: GraphQL::STRING_TYPE, + null: false, description: 'Web URL of the group.' - field :avatar_url, GraphQL::STRING_TYPE, null: true, + field :avatar_url, + type: GraphQL::STRING_TYPE, + null: true, description: 'Avatar URL of the group.' - field :custom_emoji, Types::CustomEmojiType.connection_type, null: true, + field :custom_emoji, + type: Types::CustomEmojiType.connection_type, + null: true, description: 'Custom emoji within this namespace.', feature_flag: :custom_emoji - field :share_with_group_lock, GraphQL::BOOLEAN_TYPE, null: true, + field :share_with_group_lock, + type: GraphQL::BOOLEAN_TYPE, + null: true, description: 'Indicates if sharing a project with another group within this group is prevented.' - field :project_creation_level, GraphQL::STRING_TYPE, null: true, method: :project_creation_level_str, + field :project_creation_level, + type: GraphQL::STRING_TYPE, + null: true, + method: :project_creation_level_str, description: 'The permission level required to create projects in the group.' - field :subgroup_creation_level, GraphQL::STRING_TYPE, null: true, method: :subgroup_creation_level_str, + field :subgroup_creation_level, + type: GraphQL::STRING_TYPE, + null: true, + method: :subgroup_creation_level_str, description: 'The permission level required to create subgroups within the group.' - field :require_two_factor_authentication, GraphQL::BOOLEAN_TYPE, null: true, + field :require_two_factor_authentication, + type: GraphQL::BOOLEAN_TYPE, + null: true, description: 'Indicates if all users in this group are required to set up two-factor authentication.' - field :two_factor_grace_period, GraphQL::INT_TYPE, null: true, + field :two_factor_grace_period, + type: GraphQL::INT_TYPE, + null: true, description: 'Time before two-factor authentication is enforced.' - field :auto_devops_enabled, GraphQL::BOOLEAN_TYPE, null: true, + field :auto_devops_enabled, + type: GraphQL::BOOLEAN_TYPE, + null: true, description: 'Indicates whether Auto DevOps is enabled for all projects within this group.' - field :emails_disabled, GraphQL::BOOLEAN_TYPE, null: true, + field :emails_disabled, + type: GraphQL::BOOLEAN_TYPE, + null: true, description: 'Indicates if a group has email notifications disabled.' - field :mentions_disabled, GraphQL::BOOLEAN_TYPE, null: true, + field :mentions_disabled, + type: GraphQL::BOOLEAN_TYPE, + null: true, description: 'Indicates if a group is disabled from getting mentioned.' - field :parent, GroupType, null: true, + field :parent, + type: GroupType, + null: true, description: 'Parent group.' field :issues, @@ -55,7 +81,7 @@ module Types description: 'Merge requests for projects in this group.', resolver: Resolvers::GroupMergeRequestsResolver - field :milestones, Types::MilestoneType.connection_type, null: true, + field :milestones, description: 'Milestones of the group.', resolver: Resolvers::GroupMilestonesResolver @@ -76,9 +102,10 @@ module Types Types::LabelType, null: true, description: 'A label available on this group.' do - argument :title, GraphQL::STRING_TYPE, - required: true, - description: 'Title of the label.' + argument :title, + type: GraphQL::STRING_TYPE, + required: true, + description: 'Title of the label.' end field :group_members, @@ -92,7 +119,9 @@ module Types resolver: Resolvers::ContainerRepositoriesResolver, authorize: :read_container_image - field :container_repositories_count, GraphQL::INT_TYPE, null: false, + field :container_repositories_count, + type: GraphQL::INT_TYPE, + null: false, description: 'Number of container repositories in the group.' field :packages, @@ -114,6 +143,12 @@ module Types description: 'Labels available on this group.', resolver: Resolvers::GroupLabelsResolver + field :timelogs, ::Types::TimelogType.connection_type, null: false, + description: 'Time logged on issues in the group and its subgroups.', + extras: [:lookahead], + complexity: 5, + resolver: ::Resolvers::TimelogResolver + def avatar_url object.avatar_url(only_path: false) end diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index f15ab69f2d4..34c824fe9fb 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -124,6 +124,9 @@ module Types field :create_note_email, GraphQL::STRING_TYPE, null: true, description: 'User specific email address for the issue.' + field :timelogs, Types::TimelogType.connection_type, null: false, + description: 'Timelogs on the issue.' + def author Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find end diff --git a/app/graphql/types/issues/negated_issue_filter_input_type.rb b/app/graphql/types/issues/negated_issue_filter_input_type.rb new file mode 100644 index 00000000000..10bf6f21792 --- /dev/null +++ b/app/graphql/types/issues/negated_issue_filter_input_type.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Types + module Issues + class NegatedIssueFilterInputType < BaseInputObject + graphql_name 'NegatedIssueFilterInput' + + argument :iids, [GraphQL::STRING_TYPE], + required: false, + description: 'List of IIDs of issues to exclude. For example, [1, 2].' + argument :label_name, [GraphQL::STRING_TYPE], + required: false, + description: 'Labels not applied to this issue.' + argument :milestone_title, [GraphQL::STRING_TYPE], + required: false, + description: 'Milestone not applied to this issue.' + argument :assignee_usernames, [GraphQL::STRING_TYPE], + required: false, + description: 'Usernames of users not assigned to the issue.' + argument :assignee_id, GraphQL::STRING_TYPE, + required: false, + description: 'ID of a user not assigned to the issues.' + end + end +end + +Types::Issues::NegatedIssueFilterInputType.prepend_if_ee('::EE::Types::Issues::NegatedIssueFilterInputType') diff --git a/app/graphql/types/jira_users_mapping_input_type.rb b/app/graphql/types/jira_users_mapping_input_type.rb index 61e3240ecf3..32640b9cb17 100644 --- a/app/graphql/types/jira_users_mapping_input_type.rb +++ b/app/graphql/types/jira_users_mapping_input_type.rb @@ -5,12 +5,12 @@ module Types graphql_name 'JiraUsersMappingInputType' argument :jira_account_id, - GraphQL::STRING_TYPE, - required: true, - description: 'Jira account ID of the user.' + GraphQL::STRING_TYPE, + required: true, + description: 'Jira account ID of the user.' argument :gitlab_id, - GraphQL::INT_TYPE, - required: false, - description: 'Id of the GitLab user.' + GraphQL::INT_TYPE, + required: false, + description: 'ID of the GitLab user.' end end diff --git a/app/graphql/types/merge_request_review_state_enum.rb b/app/graphql/types/merge_request_review_state_enum.rb new file mode 100644 index 00000000000..45f97758425 --- /dev/null +++ b/app/graphql/types/merge_request_review_state_enum.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Types + class MergeRequestReviewStateEnum < BaseEnum + graphql_name 'MergeRequestReviewState' + description 'State of a review of a GitLab merge request.' + + from_rails_enum(::MergeRequestReviewer.states, + description: "The merge request is %{name}.") + end +end diff --git a/app/graphql/types/merge_request_state_enum.rb b/app/graphql/types/merge_request_state_enum.rb index a2d7bd0306c..bcf18b836de 100644 --- a/app/graphql/types/merge_request_state_enum.rb +++ b/app/graphql/types/merge_request_state_enum.rb @@ -5,6 +5,6 @@ module Types graphql_name 'MergeRequestState' description 'State of a GitLab merge request' - value 'merged', description: "Merge Request has been merged." + value 'merged', description: "Merge request has been merged." end end diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index 449286915f2..c8ccf9d8aff 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -132,7 +132,10 @@ module Types description: 'The milestone of the merge request.' field :assignees, Types::UserType.connection_type, null: true, complexity: 5, description: 'Assignees of the merge request.' - field :reviewers, Types::UserType.connection_type, null: true, complexity: 5, + field :reviewers, + type: Types::MergeRequests::ReviewerType.connection_type, + null: true, + complexity: 5, description: 'Users from whom a review has been requested.' field :author, Types::UserType, null: true, description: 'User who created this merge request.' @@ -183,6 +186,8 @@ module Types description: 'Selected auto merge strategy.' field :merge_user, Types::UserType, null: true, description: 'User who merged this merge request.' + field :timelogs, Types::TimelogType.connection_type, null: false, + description: 'Timelogs on the merge request.' def approved_by object.approved_by_users diff --git a/app/graphql/types/merge_requests/reviewer_type.rb b/app/graphql/types/merge_requests/reviewer_type.rb new file mode 100644 index 00000000000..09ced39844a --- /dev/null +++ b/app/graphql/types/merge_requests/reviewer_type.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Types + module MergeRequests + class ReviewerType < ::Types::UserType + include FindClosest + + graphql_name 'MergeRequestReviewer' + description 'A user from whom a merge request review has been requested.' + authorize :read_user + + field :merge_request_interaction, + type: ::Types::UserMergeRequestInteractionType, + null: true, + extras: [:parent], + description: "Details of this user's interactions with the merge request." + + def merge_request_interaction(parent:) + merge_request = closest_parent(::Types::MergeRequestType, parent) + return unless merge_request + + Users::MergeRequestInteraction.new(user: object, merge_request: merge_request) + end + end + end +end diff --git a/app/graphql/types/milestone_type.rb b/app/graphql/types/milestone_type.rb index c3816116e2b..91a5109c748 100644 --- a/app/graphql/types/milestone_type.rb +++ b/app/graphql/types/milestone_type.rb @@ -14,6 +14,9 @@ module Types field :id, GraphQL::ID_TYPE, null: false, description: 'ID of the milestone.' + field :iid, GraphQL::ID_TYPE, null: false, + description: "Internal ID of the milestone." + field :title, GraphQL::STRING_TYPE, null: false, description: 'Title of the milestone.' diff --git a/app/graphql/types/mutation_operation_mode_enum.rb b/app/graphql/types/mutation_operation_mode_enum.rb index 75c1d7cd4a6..08214eebc7e 100644 --- a/app/graphql/types/mutation_operation_mode_enum.rb +++ b/app/graphql/types/mutation_operation_mode_enum.rb @@ -10,5 +10,13 @@ module Types value 'REPLACE', 'Performs a replace operation.' value 'APPEND', 'Performs an append operation.' value 'REMOVE', 'Performs a removal operation.' + + def self.default_mode + enum[:replace] + end + + def self.transform_modes + enum.values_at(:remove, :append) + end end end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 76ffddf416f..5a9c7b32deb 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -68,6 +68,7 @@ module Types mount_mutation Mutations::Releases::Delete mount_mutation Mutations::ReleaseAssetLinks::Create mount_mutation Mutations::ReleaseAssetLinks::Update + mount_mutation Mutations::ReleaseAssetLinks::Delete mount_mutation Mutations::Terraform::State::Delete mount_mutation Mutations::Terraform::State::Lock mount_mutation Mutations::Terraform::State::Unlock diff --git a/app/graphql/types/packages/conan/file_metadatum_type.rb b/app/graphql/types/packages/conan/file_metadatum_type.rb new file mode 100644 index 00000000000..97d5abe6ba4 --- /dev/null +++ b/app/graphql/types/packages/conan/file_metadatum_type.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Types + module Packages + module Conan + class FileMetadatumType < BaseObject + graphql_name 'ConanFileMetadata' + description 'Conan file metadata' + + implements Types::Packages::FileMetadataType + + authorize :read_package + + field :id, ::Types::GlobalIDType[::Packages::Conan::FileMetadatum], null: false, description: 'ID of the metadatum.' + field :recipe_revision, GraphQL::STRING_TYPE, null: false, description: 'Revision of the Conan recipe.' + field :package_revision, GraphQL::STRING_TYPE, null: true, description: 'Revision of the package.' + field :conan_package_reference, GraphQL::STRING_TYPE, null: true, description: 'Reference of the Conan package.' + field :conan_file_type, ::Types::Packages::Conan::MetadatumFileTypeEnum, null: false, description: 'Type of the Conan file.' + end + end + end +end diff --git a/app/graphql/types/packages/conan/metadatum_file_type_enum.rb b/app/graphql/types/packages/conan/metadatum_file_type_enum.rb new file mode 100644 index 00000000000..d8ec3a44d4d --- /dev/null +++ b/app/graphql/types/packages/conan/metadatum_file_type_enum.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Types + module Packages + module Conan + class MetadatumFileTypeEnum < BaseEnum + graphql_name 'ConanMetadatumFileTypeEnum' + description 'Conan file types' + + ::Packages::Conan::FileMetadatum.conan_file_types.keys.each do |file| + value file.upcase, value: file, description: "A #{file.humanize(capitalize: false)} type." + end + end + end + end +end diff --git a/app/graphql/types/packages/conan/metadatum_type.rb b/app/graphql/types/packages/conan/metadatum_type.rb new file mode 100644 index 00000000000..00b84235d27 --- /dev/null +++ b/app/graphql/types/packages/conan/metadatum_type.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Types + module Packages + module Conan + class MetadatumType < BaseObject + graphql_name 'ConanMetadata' + description 'Conan metadata' + + authorize :read_package + + field :id, ::Types::GlobalIDType[::Packages::Conan::Metadatum], null: false, description: 'ID of the metadatum.' + field :created_at, Types::TimeType, null: false, description: 'Date of creation.' + field :updated_at, Types::TimeType, null: false, description: 'Date of most recent update.' + field :package_username, GraphQL::STRING_TYPE, null: false, description: 'Username of the Conan package.' + field :package_channel, GraphQL::STRING_TYPE, null: false, description: 'Channel of the Conan package.' + field :recipe, GraphQL::STRING_TYPE, null: false, description: 'Recipe of the Conan package.' + field :recipe_path, GraphQL::STRING_TYPE, null: false, description: 'Recipe path of the Conan package.' + end + end + end +end diff --git a/app/graphql/types/packages/file_metadata_type.rb b/app/graphql/types/packages/file_metadata_type.rb new file mode 100644 index 00000000000..46ccb424218 --- /dev/null +++ b/app/graphql/types/packages/file_metadata_type.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Types + module Packages + module FileMetadataType + include ::Types::BaseInterface + graphql_name 'PackageFileMetadata' + description 'Represents metadata associated with a Package file' + + field :created_at, ::Types::TimeType, null: false, description: 'Date of creation.' + field :updated_at, ::Types::TimeType, null: false, description: 'Date of most recent update.' + + def self.resolve_type(object, context) + case object + when ::Packages::Conan::FileMetadatum + ::Types::Packages::Conan::FileMetadatumType + else + # NOTE: This method must be kept in sync with `PackageFileType#file_metadata`, + # which must never produce data that this discriminator cannot handle. + raise 'Unsupported file metadata type' + end + end + + orphan_types Types::Packages::Conan::FileMetadatumType + end + end +end diff --git a/app/graphql/types/packages/metadata_type.rb b/app/graphql/types/packages/metadata_type.rb index 26c43b51a69..4ab6707df88 100644 --- a/app/graphql/types/packages/metadata_type.rb +++ b/app/graphql/types/packages/metadata_type.rb @@ -6,12 +6,14 @@ module Types graphql_name 'PackageMetadata' description 'Represents metadata associated with a Package' - possible_types ::Types::Packages::Composer::MetadatumType + possible_types ::Types::Packages::Composer::MetadatumType, ::Types::Packages::Conan::MetadatumType def self.resolve_type(object, context) case object when ::Packages::Composer::Metadatum ::Types::Packages::Composer::MetadatumType + when ::Packages::Conan::Metadatum + ::Types::Packages::Conan::MetadatumType else # NOTE: This method must be kept in sync with `PackageWithoutVersionsType#metadata`, # which must never produce data that this discriminator cannot handle. diff --git a/app/graphql/types/packages/package_details_type.rb b/app/graphql/types/packages/package_details_type.rb new file mode 100644 index 00000000000..510b7e2ba41 --- /dev/null +++ b/app/graphql/types/packages/package_details_type.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Types + module Packages + class PackageDetailsType < PackageType + graphql_name 'PackageDetailsType' + description 'Represents a package details in the Package Registry. Note that this type is in beta and susceptible to changes' + authorize :read_package + + field :versions, ::Types::Packages::PackageType.connection_type, null: true, + description: 'The other versions of the package.' + + field :package_files, Types::Packages::PackageFileType.connection_type, null: true, description: 'Package files.' + + def versions + object.versions + end + end + end +end diff --git a/app/graphql/types/packages/package_file_type.rb b/app/graphql/types/packages/package_file_type.rb new file mode 100644 index 00000000000..e9e38559626 --- /dev/null +++ b/app/graphql/types/packages/package_file_type.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Types + module Packages + class PackageFileType < BaseObject + graphql_name 'PackageFile' + description 'Represents a package file' + authorize :read_package + + field :id, ::Types::GlobalIDType[::Packages::PackageFile], null: false, description: 'ID of the file.' + field :created_at, Types::TimeType, null: false, description: 'The created date.' + field :updated_at, Types::TimeType, null: false, description: 'The updated date.' + field :size, GraphQL::STRING_TYPE, null: false, description: 'Size of the package file.' + field :file_name, GraphQL::STRING_TYPE, null: false, description: 'Name of the package file.' + field :download_path, GraphQL::STRING_TYPE, null: false, description: 'Download path of the package file.' + field :file_md5, GraphQL::STRING_TYPE, null: true, description: 'Md5 of the package file.' + field :file_sha1, GraphQL::STRING_TYPE, null: true, description: 'Sha1 of the package file.' + field :file_sha256, GraphQL::STRING_TYPE, null: true, description: 'Sha256 of the package file.' + field :file_metadata, Types::Packages::FileMetadataType, null: true, + description: 'File metadata.' + + # NOTE: This method must be kept in sync with the union + # type: `Types::Packages::FileMetadataType`. + # + # `Types::Packages::FileMetadataType.resolve_type(metadata, ctx)` must never raise. + def file_metadata + case object.package.package_type + when 'conan' + object.conan_file_metadatum + else + nil + end + end + end + end +end diff --git a/app/graphql/types/packages/package_type.rb b/app/graphql/types/packages/package_type.rb index 331898a1e84..a263ca1577a 100644 --- a/app/graphql/types/packages/package_type.rb +++ b/app/graphql/types/packages/package_type.rb @@ -2,13 +2,52 @@ module Types module Packages - class PackageType < PackageWithoutVersionsType + class PackageType < ::Types::BaseObject graphql_name 'Package' - description 'Represents a package in the Package Registry' + description 'Represents a package in the Package Registry. Note that this type is in beta and susceptible to changes' + authorize :read_package - field :versions, ::Types::Packages::PackageWithoutVersionsType.connection_type, null: true, - description: 'The other versions of the package.' + field :id, ::Types::GlobalIDType[::Packages::Package], null: false, + description: 'ID of the package.' + + field :name, GraphQL::STRING_TYPE, null: false, description: 'Name of the package.' + field :created_at, Types::TimeType, null: false, description: 'Date of creation.' + field :updated_at, Types::TimeType, null: false, description: 'Date of most recent update.' + field :version, GraphQL::STRING_TYPE, null: true, description: 'Version string.' + field :package_type, Types::Packages::PackageTypeEnum, null: false, description: 'Package type.' + field :tags, Types::Packages::PackageTagType.connection_type, null: true, description: 'Package tags.' + field :project, Types::ProjectType, null: false, description: 'Project where the package is stored.' + field :pipelines, Types::Ci::PipelineType.connection_type, null: true, + description: 'Pipelines that built the package.' + field :metadata, Types::Packages::MetadataType, null: true, + description: 'Package metadata.' + field :versions, ::Types::Packages::PackageType.connection_type, null: true, + description: 'The other versions of the package.', + deprecated: { reason: 'This field is now only returned in the PackageDetailsType', milestone: '13.11' } + + def project + Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find + end + + def versions + [] + end + + # NOTE: This method must be kept in sync with the union + # type: `Types::Packages::MetadataType`. + # + # `Types::Packages::MetadataType.resolve_type(metadata, ctx)` must never raise. + def metadata + case object.package_type + when 'composer' + object.composer_metadatum + when 'conan' + object.conan_metadatum + else + nil + end + end end end end diff --git a/app/graphql/types/packages/package_without_versions_type.rb b/app/graphql/types/packages/package_without_versions_type.rb deleted file mode 100644 index 9c6bb37e6cc..00000000000 --- a/app/graphql/types/packages/package_without_versions_type.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -module Types - module Packages - class PackageWithoutVersionsType < ::Types::BaseObject - graphql_name 'PackageWithoutVersions' - description 'Represents a version of a package in the Package Registry' - - authorize :read_package - - field :id, ::Types::GlobalIDType[::Packages::Package], null: false, - description: 'ID of the package.' - - field :name, GraphQL::STRING_TYPE, null: false, description: 'Name of the package.' - field :created_at, Types::TimeType, null: false, description: 'Date of creation.' - field :updated_at, Types::TimeType, null: false, description: 'Date of most recent update.' - field :version, GraphQL::STRING_TYPE, null: true, description: 'Version string.' - field :package_type, Types::Packages::PackageTypeEnum, null: false, description: 'Package type.' - field :tags, Types::Packages::PackageTagType.connection_type, null: true, description: 'Package tags.' - field :project, Types::ProjectType, null: false, description: 'Project where the package is stored.' - field :pipelines, Types::Ci::PipelineType.connection_type, null: true, - description: 'Pipelines that built the package.' - field :metadata, Types::Packages::MetadataType, null: true, - description: 'Package metadata.' - - def project - Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find - end - - # NOTE: This method must be kept in sync with the union - # type: `Types::Packages::MetadataType`. - # - # `Types::Packages::MetadataType.resolve_type(metadata, ctx)` must never raise. - def metadata - case object.package_type - when 'composer' - object.composer_metadatum - else - nil - end - end - end - end -end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 9a3f2e311e6..21534f40499 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -183,6 +183,12 @@ module Types description: 'Packages of the project.', resolver: Resolvers::ProjectPackagesResolver + field :jobs, + Types::Ci::JobType.connection_type, + null: true, + description: 'Jobs of a project. This field can only be resolved for one project in any single request.', + resolver: Resolvers::ProjectJobsResolver + field :pipelines, null: true, description: 'Build pipelines of the project.', diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 74818bfcd42..8af0db644dd 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -55,7 +55,10 @@ module Types field :container_repository, Types::ContainerRepositoryDetailsType, null: true, description: 'Find a container repository.' do - argument :id, ::Types::GlobalIDType[::ContainerRepository], required: true, description: 'The global ID of the container repository.' + argument :id, + type: ::Types::GlobalIDType[::ContainerRepository], + required: true, + description: 'The global ID of the container repository.' end field :package, @@ -72,9 +75,7 @@ module Types description: 'Find users.', resolver: Resolvers::UsersResolver - field :echo, GraphQL::STRING_TYPE, null: false, - description: 'Text to echo back.', - resolver: Resolvers::EchoResolver + field :echo, resolver: Resolvers::EchoResolver field :issue, Types::IssueType, null: true, @@ -82,11 +83,16 @@ module Types argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'The global ID of the Issue.' end - field :instance_statistics_measurements, Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type, + field :instance_statistics_measurements, + type: Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type, null: true, description: 'Get statistics on the instance.', - deprecated: { reason: 'This field was renamed. Use the `usageTrendsMeasurements` field instead', milestone: '13.10' }, - resolver: Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver + resolver: Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver, + deprecated: { + reason: :renamed, + replacement: 'Query.usageTrendsMeasurements', + milestone: '13.10' + } field :usage_trends_measurements, Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type, null: true, @@ -97,18 +103,10 @@ module Types null: true, description: 'CI related settings that apply to the entire instance.' - field :runner_platforms, Types::Ci::RunnerPlatformType.connection_type, - null: true, description: 'Supported runner platforms.', - resolver: Resolvers::Ci::RunnerPlatformsResolver - - field :runner_setup, Types::Ci::RunnerSetupType, null: true, - description: 'Get runner setup instructions.', - resolver: Resolvers::Ci::RunnerSetupResolver + field :runner_platforms, resolver: Resolvers::Ci::RunnerPlatformsResolver + field :runner_setup, 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 + field :ci_config, resolver: Resolvers::Ci::ConfigResolver, complexity: 126 # AUTHENTICATED_COMPLEXITY / 2 + 1 def design_management DesignManagementObject.new(nil) diff --git a/app/graphql/types/repository/blob_type.rb b/app/graphql/types/repository/blob_type.rb new file mode 100644 index 00000000000..912fc5f643a --- /dev/null +++ b/app/graphql/types/repository/blob_type.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true +module Types + module Repository + # rubocop: disable Graphql/AuthorizeTypes + # This is presented through `Repository` that has its own authorization + class BlobType < BaseObject + present_using BlobPresenter + + graphql_name 'RepositoryBlob' + + field :id, GraphQL::ID_TYPE, null: false, + description: 'ID of the blob.' + + field :oid, GraphQL::STRING_TYPE, null: false, method: :id, + description: 'OID of the blob.' + + field :path, GraphQL::STRING_TYPE, null: false, + description: 'Path of the blob.' + + field :name, GraphQL::STRING_TYPE, + description: 'Blob name.', + null: true + + field :mode, type: GraphQL::STRING_TYPE, + description: 'Blob mode.', + null: true + + field :lfs_oid, GraphQL::STRING_TYPE, null: true, + calls_gitaly: true, + description: 'LFS OID of the blob.' + + field :web_path, GraphQL::STRING_TYPE, null: true, + description: 'Web path of the blob.' + + def lfs_oid + Gitlab::Graphql::Loaders::BatchLfsOidLoader.new(object.repository, object.id).find + end + end + end +end diff --git a/app/graphql/types/repository_type.rb b/app/graphql/types/repository_type.rb index e319a5f3124..963a4296c4f 100644 --- a/app/graphql/types/repository_type.rb +++ b/app/graphql/types/repository_type.rb @@ -14,5 +14,10 @@ module Types description: 'Indicates a corresponding Git repository exists on disk.' field :tree, Types::Tree::TreeType, null: true, resolver: Resolvers::TreeResolver, calls_gitaly: true, description: 'Tree of the repository.' + field :blobs, Types::Repository::BlobType.connection_type, null: true, resolver: Resolvers::BlobsResolver, calls_gitaly: true, + description: 'Blobs contained within the repository' + field :branch_names, [GraphQL::STRING_TYPE], null: true, calls_gitaly: true, + complexity: 170, description: 'Names of branches available in this repository that match the search pattern.', + resolver: Resolvers::RepositoryBranchNamesResolver end end diff --git a/app/graphql/types/sort_enum.rb b/app/graphql/types/sort_enum.rb index ff994039b6d..cc04394004d 100644 --- a/app/graphql/types/sort_enum.rb +++ b/app/graphql/types/sort_enum.rb @@ -7,10 +7,34 @@ module Types # Deprecated, as we prefer uppercase enums # https://gitlab.com/groups/gitlab-org/-/epics/1838 - 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, + deprecated: { + reason: :renamed, + replacement: 'UPDATED_DESC', + milestone: '13.5' + } + value 'updated_asc', 'Updated at ascending order.', + value: :updated_asc, + deprecated: { + reason: :renamed, + replacement: 'UPDATED_ASC', + milestone: '13.5' + } + value 'created_desc', 'Created at descending order.', + value: :created_desc, + deprecated: { + reason: :renamed, + replacement: 'CREATED_DESC', + milestone: '13.5' + } + value 'created_asc', 'Created at ascending order.', + value: :created_asc, + deprecated: { + reason: :renamed, + replacement: '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/timelog_type.rb b/app/graphql/types/timelog_type.rb new file mode 100644 index 00000000000..465e3c492bc --- /dev/null +++ b/app/graphql/types/timelog_type.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Types + class TimelogType < BaseObject + graphql_name 'Timelog' + + authorize :read_group_timelogs + + field :spent_at, + Types::TimeType, + null: true, + description: 'Timestamp of when the time tracked was spent at.' + + field :time_spent, + GraphQL::INT_TYPE, + null: false, + description: 'The time spent displayed in seconds.' + + field :user, + Types::UserType, + null: false, + description: 'The user that logged the time.' + + field :issue, + Types::IssueType, + null: true, + description: 'The issue that logged time was added to.' + + field :note, + Types::Notes::NoteType, + null: true, + description: 'The note where the quick action to add the logged time was executed.' + + def user + Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.user_id).find + end + + def issue + Gitlab::Graphql::Loaders::BatchModelLoader.new(Issue, object.issue_id).find + end + end +end diff --git a/app/graphql/types/user_merge_request_interaction_type.rb b/app/graphql/types/user_merge_request_interaction_type.rb new file mode 100644 index 00000000000..5ff0d79f13e --- /dev/null +++ b/app/graphql/types/user_merge_request_interaction_type.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Types + class UserMergeRequestInteractionType < BaseObject + graphql_name 'UserMergeRequestInteraction' + description <<~MD + Information about a merge request given a specific user. + + This object has two parts to its state: a `User` and a `MergeRequest`. All + fields relate to interactions between the two entities. + MD + + authorize :read_merge_request + + field :can_merge, + type: ::GraphQL::BOOLEAN_TYPE, + null: false, + calls_gitaly: true, + method: :can_merge?, + description: 'Whether this user can merge this merge request.' + + field :can_update, + type: ::GraphQL::BOOLEAN_TYPE, + null: false, + method: :can_update?, + description: 'Whether this user can update this merge request.' + + field :review_state, + ::Types::MergeRequestReviewStateEnum, + null: true, + description: 'The state of the review by this user.' + + field :reviewed, + type: ::GraphQL::BOOLEAN_TYPE, + null: false, + method: :reviewed?, + description: 'Whether this user has provided a review for this merge request.' + + field :approved, + type: ::GraphQL::BOOLEAN_TYPE, + null: false, + method: :approved?, + description: 'Whether this user has approved this merge request.' + end +end + +::Types::UserMergeRequestInteractionType.prepend_if_ee('EE::Types::UserMergeRequestInteractionType') diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb index 2cc7d379240..3d7db80ae11 100644 --- a/app/graphql/types/user_type.rb +++ b/app/graphql/types/user_type.rb @@ -3,6 +3,7 @@ module Types class UserType < BaseObject graphql_name 'User' + description 'Representation of a GitLab user.' authorize :read_user @@ -10,61 +11,87 @@ module Types expose_permissions Types::PermissionTypes::User - field :id, GraphQL::ID_TYPE, null: false, + field :id, + type: GraphQL::ID_TYPE, + null: false, description: 'ID of the user.' - field :bot, GraphQL::BOOLEAN_TYPE, null: false, + field :bot, + type: GraphQL::BOOLEAN_TYPE, + null: false, description: 'Indicates if the user is a bot.', method: :bot? - field :username, GraphQL::STRING_TYPE, null: false, + field :username, + type: GraphQL::STRING_TYPE, + null: false, description: 'Username of the user. Unique within this instance of GitLab.' - field :name, GraphQL::STRING_TYPE, null: false, + field :name, + type: GraphQL::STRING_TYPE, + null: false, description: 'Human-readable name of the user.' - field :state, Types::UserStateEnum, null: false, + field :state, + type: Types::UserStateEnum, + null: false, description: 'State of the user.' - field :email, GraphQL::STRING_TYPE, null: true, + field :email, + type: GraphQL::STRING_TYPE, + null: true, description: 'User email.', method: :public_email, - deprecated: { reason: 'Use public_email', milestone: '13.7' } - field :public_email, GraphQL::STRING_TYPE, null: true, + deprecated: { reason: :renamed, replacement: 'User.publicEmail', milestone: '13.7' } + field :public_email, + type: GraphQL::STRING_TYPE, + null: true, description: "User's public email." - field :avatar_url, GraphQL::STRING_TYPE, null: true, + field :avatar_url, + type: GraphQL::STRING_TYPE, + null: true, description: "URL of the user's avatar." - field :web_url, GraphQL::STRING_TYPE, null: false, + field :web_url, + type: GraphQL::STRING_TYPE, + null: false, description: 'Web URL of the user.' - field :web_path, GraphQL::STRING_TYPE, null: false, + field :web_path, + type: GraphQL::STRING_TYPE, + null: false, description: 'Web path of the user.' - field :todos, Types::TodoType.connection_type, null: false, + field :todos, resolver: Resolvers::TodoResolver, description: 'To-do items of the user.' - field :group_memberships, Types::GroupMemberType.connection_type, null: true, + field :group_memberships, + type: Types::GroupMemberType.connection_type, + null: true, description: 'Group memberships of the user.' - field :group_count, GraphQL::INT_TYPE, null: true, + field :group_count, resolver: Resolvers::Users::GroupCountResolver, description: 'Group count for the user.', feature_flag: :user_group_counts - field :status, Types::UserStatusType, null: true, - description: 'User status.' - field :location, ::GraphQL::STRING_TYPE, null: true, + field :status, + type: Types::UserStatusType, + null: true, + description: 'User status.' + field :location, + type: ::GraphQL::STRING_TYPE, + null: true, description: 'The location of the user.' - field :project_memberships, Types::ProjectMemberType.connection_type, null: true, + field :project_memberships, + type: Types::ProjectMemberType.connection_type, + null: true, description: 'Project memberships of the user.' - field :starred_projects, Types::ProjectType.connection_type, null: true, + field :starred_projects, description: 'Projects starred by the user.', resolver: Resolvers::UserStarredProjectsResolver # 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.' + description: 'Merge requests authored by the user.' field :assigned_merge_requests, resolver: Resolvers::AssignedMergeRequestsResolver, - description: 'Merge Requests assigned to the user.' + description: 'Merge requests assigned to the user.' field :review_requested_merge_requests, resolver: Resolvers::ReviewRequestedMergeRequestsResolver, - description: 'Merge Requests assigned to the user for review.' + description: 'Merge requests assigned to the user for review.' field :snippets, - Types::SnippetType.connection_type, - null: true, description: 'Snippets authored by the user.', resolver: Resolvers::Users::SnippetsResolver field :callouts, -- cgit v1.2.3