diff options
Diffstat (limited to 'app/graphql')
52 files changed, 712 insertions, 213 deletions
diff --git a/app/graphql/mutations/achievements/create.rb b/app/graphql/mutations/achievements/create.rb new file mode 100644 index 00000000000..6cfe6c0e643 --- /dev/null +++ b/app/graphql/mutations/achievements/create.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module Mutations + module Achievements + class Create < BaseMutation + graphql_name 'AchievementsCreate' + + include Gitlab::Graphql::Authorize::AuthorizeResource + + field :achievement, + ::Types::Achievements::AchievementType, + null: true, + description: 'Achievement created.' + + argument :namespace_id, ::Types::GlobalIDType[::Namespace], + required: true, + description: 'Namespace for the achievement.' + + argument :name, GraphQL::Types::String, + required: true, + description: 'Name for the achievement.' + + argument :avatar, ApolloUploadServer::Upload, + required: false, + description: 'Avatar for the achievement.' + + argument :description, GraphQL::Types::String, + required: false, + description: 'Description of or notes for the achievement.' + + argument :revokeable, GraphQL::Types::Boolean, + required: true, + description: 'Revokeability for the achievement.' + + authorize :admin_achievement + + def resolve(args) + namespace = authorized_find!(id: args[:namespace_id]) + + raise Gitlab::Graphql::Errors::ResourceNotAvailable, '`achievements` feature flag is disabled.' \ + if Feature.disabled?(:achievements, namespace) + + result = ::Achievements::CreateService.new(namespace: namespace, + current_user: current_user, + params: args).execute + { achievement: result.payload, errors: result.errors } + end + + def find_object(id:) + GitlabSchema.object_from_id(id, expected_type: ::Namespace) + end + end + end +end diff --git a/app/graphql/mutations/boards/lists/base_update.rb b/app/graphql/mutations/boards/lists/base_update.rb index 7962d9c85d4..c6e6b1c9bfe 100644 --- a/app/graphql/mutations/boards/lists/base_update.rb +++ b/app/graphql/mutations/boards/lists/base_update.rb @@ -10,7 +10,7 @@ module Mutations argument :collapsed, GraphQL::Types::Boolean, required: false, - description: 'Indicates if the list is collapsed for this user.' + description: 'Indicates if the list is collapsed for the user.' def resolve(list: nil, **args) if list.nil? || !can_read_list?(list) diff --git a/app/graphql/mutations/ci/job/play.rb b/app/graphql/mutations/ci/job/play.rb index 99f62ea3e70..8bb69119a44 100644 --- a/app/graphql/mutations/ci/job/play.rb +++ b/app/graphql/mutations/ci/job/play.rb @@ -11,13 +11,21 @@ module Mutations null: true, description: 'Job after the mutation.' + argument :variables, [::Types::Ci::VariableInputType], + required: false, + default_value: [], + replace_null_with_default: true, + description: 'Variables to use when playing a manual job.' + authorize :update_build - def resolve(id:) + def resolve(id:, variables:) job = authorized_find!(id: id) project = job.project + variables = variables.map(&:to_h) + + ::Ci::PlayBuildService.new(project, current_user).execute(job, variables) - ::Ci::PlayBuildService.new(project, current_user).execute(job) { job: job, errors: errors_on_object(job) diff --git a/app/graphql/mutations/ci/project_ci_cd_settings_update.rb b/app/graphql/mutations/ci/project_ci_cd_settings_update.rb index 27b066ffcf6..934d62e92cf 100644 --- a/app/graphql/mutations/ci/project_ci_cd_settings_update.rb +++ b/app/graphql/mutations/ci/project_ci_cd_settings_update.rb @@ -15,7 +15,7 @@ module Mutations argument :keep_latest_artifact, GraphQL::Types::Boolean, required: false, - description: 'Indicates if the latest artifact should be kept for this project.' + description: 'Indicates if the latest artifact should be kept for the project.' argument :job_token_scope_enabled, GraphQL::Types::Boolean, required: false, diff --git a/app/graphql/mutations/clusters/agents/create.rb b/app/graphql/mutations/clusters/agents/create.rb index deaa9c2d656..8be1e0e524a 100644 --- a/app/graphql/mutations/clusters/agents/create.rb +++ b/app/graphql/mutations/clusters/agents/create.rb @@ -12,7 +12,7 @@ module Mutations argument :project_path, GraphQL::Types::ID, required: true, - description: 'Full path of the associated project for this cluster agent.' + description: 'Full path of the associated project for the cluster agent.' argument :name, GraphQL::Types::String, required: true, diff --git a/app/graphql/mutations/commits/create.rb b/app/graphql/mutations/commits/create.rb index 00ec64becc8..02e1e4c78bf 100644 --- a/app/graphql/mutations/commits/create.rb +++ b/app/graphql/mutations/commits/create.rb @@ -58,7 +58,7 @@ module Mutations commit_message: message, branch_name: branch, start_branch: args[:start_branch] || branch, - actions: actions.map { |action| action.to_h } + actions: actions.map(&:to_h) } result = ::Files::MultiService.new(project, current_user, attributes).execute diff --git a/app/graphql/mutations/concerns/mutations/work_items/widgetable.rb b/app/graphql/mutations/concerns/mutations/work_items/widgetable.rb index 445b2eb6441..508e1627032 100644 --- a/app/graphql/mutations/concerns/mutations/work_items/widgetable.rb +++ b/app/graphql/mutations/concerns/mutations/work_items/widgetable.rb @@ -18,7 +18,7 @@ module Mutations # Cannot use prepare to use `.to_h` on each input due to # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87472#note_945199865 - widget_params.transform_values { |values| values.to_h } + widget_params.transform_values(&:to_h) end end end diff --git a/app/graphql/mutations/jira_import/start.rb b/app/graphql/mutations/jira_import/start.rb index ea071c45bcf..2ba20e163a5 100644 --- a/app/graphql/mutations/jira_import/start.rb +++ b/app/graphql/mutations/jira_import/start.rb @@ -30,7 +30,7 @@ module Mutations def resolve(project_path:, jira_project_key:, users_mapping:) project = authorized_find!(project_path) - mapping = users_mapping.to_ary.map { |map| map.to_hash } + mapping = users_mapping.to_ary.map(&:to_hash) service_response = ::JiraImport::StartImportService .new(context[:current_user], project, jira_project_key, mapping) diff --git a/app/graphql/mutations/members/groups/bulk_update.rb b/app/graphql/mutations/members/groups/bulk_update.rb new file mode 100644 index 00000000000..d0b19bd9634 --- /dev/null +++ b/app/graphql/mutations/members/groups/bulk_update.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +module Mutations + module Members + module Groups + class BulkUpdate < ::Mutations::BaseMutation + graphql_name 'GroupMemberBulkUpdate' + + include Gitlab::Utils::StrongMemoize + + authorize :admin_group_member + + field :group_members, + [Types::GroupMemberType], + null: true, + description: 'Group members after mutation.' + + argument :group_id, + ::Types::GlobalIDType[::Group], + required: true, + description: 'Global ID of the group.' + + argument :user_ids, + [::Types::GlobalIDType[::User]], + required: true, + description: 'Global IDs of the group members.' + + argument :access_level, + ::Types::MemberAccessLevelEnum, + required: true, + description: 'Access level to update the members to.' + + argument :expires_at, + Types::TimeType, + required: false, + description: 'Date and time the membership expires.' + + MAX_MEMBERS_UPDATE_LIMIT = 50 + MAX_MEMBERS_UPDATE_ERROR = "Count of members to be updated should be less than #{MAX_MEMBERS_UPDATE_LIMIT}." + INVALID_MEMBERS_ERROR = 'Only access level of direct members can be updated.' + + def resolve(group_id:, **args) + result = ::Members::UpdateService.new(current_user, args.except(:user_ids)).execute(@updatable_group_members) + + { + group_members: result[:members], + errors: Array.wrap(result[:message]) + } + rescue Gitlab::Access::AccessDeniedError + { + errors: ["Unable to update members, please check user permissions."] + } + end + + private + + def ready?(**args) + group = authorized_find!(group_id: args[:group_id]) + user_ids = args.fetch(:user_ids, {}).map(&:model_id) + @updatable_group_members = only_direct_group_members(group, user_ids) + + if @updatable_group_members.size > MAX_MEMBERS_UPDATE_LIMIT + raise Gitlab::Graphql::Errors::InvalidMemberCountError, MAX_MEMBERS_UPDATE_ERROR + end + + if @updatable_group_members.size != user_ids.size + raise Gitlab::Graphql::Errors::InvalidMembersError, INVALID_MEMBERS_ERROR + end + + super + end + + def find_object(group_id:) + GitlabSchema.object_from_id(group_id, expected_type: ::Group) + end + + def only_direct_group_members(group, user_ids) + group + .members + .with_user(user_ids).to_a + end + end + end + end +end diff --git a/app/graphql/mutations/merge_requests/accept.rb b/app/graphql/mutations/merge_requests/accept.rb index ebd9e2b8fdd..64572091379 100644 --- a/app/graphql/mutations/merge_requests/accept.rb +++ b/app/graphql/mutations/merge_requests/accept.rb @@ -21,14 +21,14 @@ module Mutations ::Types::MergeStrategyEnum, required: false, as: :auto_merge_strategy, - description: 'How to merge this merge request.' + description: 'How to merge the merge request.' argument :commit_message, ::GraphQL::Types::String, required: false, description: 'Custom merge commit message.' argument :sha, ::GraphQL::Types::String, required: true, - description: 'HEAD SHA at the time when this merge was requested.' + description: 'HEAD SHA at the time when the merge was requested.' argument :squash_commit_message, ::GraphQL::Types::String, required: false, description: 'Custom squash commit message (if squash is true).' diff --git a/app/graphql/mutations/notes/create/note.rb b/app/graphql/mutations/notes/create/note.rb index 9b105b7fe1c..0f1be32d088 100644 --- a/app/graphql/mutations/notes/create/note.rb +++ b/app/graphql/mutations/notes/create/note.rb @@ -10,7 +10,7 @@ module Mutations argument :discussion_id, ::Types::GlobalIDType[::Discussion], required: false, - description: 'Global ID of the discussion this note is in reply to.' + description: 'Global ID of the discussion the note is in reply to.' argument :merge_request_diff_head_sha, GraphQL::Types::String, diff --git a/app/graphql/mutations/packages/bulk_destroy.rb b/app/graphql/mutations/packages/bulk_destroy.rb index a0756d0c3f9..86f8de91e2b 100644 --- a/app/graphql/mutations/packages/bulk_destroy.rb +++ b/app/graphql/mutations/packages/bulk_destroy.rb @@ -16,11 +16,10 @@ module Mutations def resolve(ids:) raise_resource_not_available_error!(TOO_MANY_IDS_ERROR) if ids.size > MAX_PACKAGES - ids = GitlabSchema.parse_gids(ids, expected_type: ::Packages::Package) - .map(&:model_id) + model_ids = ids.map(&:model_id) service = ::Packages::MarkPackagesForDestructionService.new( - packages: packages_from(ids), + packages: packages_from(model_ids), current_user: current_user ) result = service.execute diff --git a/app/graphql/mutations/releases/create.rb b/app/graphql/mutations/releases/create.rb index ba1fa8d446c..15175aea9a5 100644 --- a/app/graphql/mutations/releases/create.rb +++ b/app/graphql/mutations/releases/create.rb @@ -30,7 +30,7 @@ module Mutations required: false, description: 'Description (also known as "release notes") of the release.' - argument :released_at, Types::TimeType, + argument :released_at, Types::TimeType, # rubocop:disable Graphql/Descriptions required: false, description: 'Date and time for the release. Defaults to the current time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). Only provide this field if creating an upcoming or historical release.' diff --git a/app/graphql/resolvers/ci/jobs_resolver.rb b/app/graphql/resolvers/ci/jobs_resolver.rb index 91f29948ad0..31cc350f331 100644 --- a/app/graphql/resolvers/ci/jobs_resolver.rb +++ b/app/graphql/resolvers/ci/jobs_resolver.rb @@ -19,10 +19,15 @@ module Resolvers required: false, description: 'Filter jobs by retry-status.' - def resolve(statuses: nil, security_report_types: [], retried: nil) + argument :when_executed, [::GraphQL::Types::String], + required: false, + description: 'Filter jobs by when they are executed.' + + def resolve(statuses: nil, security_report_types: [], retried: nil, when_executed: nil) jobs = init_collection(security_report_types) jobs = jobs.with_status(statuses) if statuses.present? jobs = jobs.retried if retried + jobs = jobs.with_when_executed(when_executed) if when_executed.present? jobs = jobs.latest if retried == false jobs diff --git a/app/graphql/resolvers/ci/runner_groups_resolver.rb b/app/graphql/resolvers/ci/runner_groups_resolver.rb index 3360e820bd2..c1d9bcbb9bb 100644 --- a/app/graphql/resolvers/ci/runner_groups_resolver.rb +++ b/app/graphql/resolvers/ci/runner_groups_resolver.rb @@ -6,7 +6,7 @@ module Resolvers include Gitlab::Graphql::Authorize::AuthorizeResource include ResolvesGroups - type Types::GroupConnection, null: true + type 'Types::GroupConnection', null: true authorize :read_runner authorizes_object! diff --git a/app/graphql/resolvers/concerns/board_item_filterable.rb b/app/graphql/resolvers/concerns/board_item_filterable.rb index 9c0ada4f72c..035cdbbd282 100644 --- a/app/graphql/resolvers/concerns/board_item_filterable.rb +++ b/app/graphql/resolvers/concerns/board_item_filterable.rb @@ -22,6 +22,7 @@ module BoardItemFilterable rewrite_param_name(filters[:or], :author_usernames, :author_username) rewrite_param_name(filters[:or], :assignee_usernames, :assignee_username) + rewrite_param_name(filters[:or], :label_names, :label_name) end filters diff --git a/app/graphql/resolvers/concerns/caching_array_resolver.rb b/app/graphql/resolvers/concerns/caching_array_resolver.rb index e7555dcf42c..62649518142 100644 --- a/app/graphql/resolvers/concerns/caching_array_resolver.rb +++ b/app/graphql/resolvers/concerns/caching_array_resolver.rb @@ -63,6 +63,7 @@ module CachingArrayResolver queries.in_groups_of(max_union_size, false).each do |group| by_id = model_class + .select(all_fields, :union_member_idx) .from_union(tag(group), remove_duplicates: false) .preload(preload) # rubocop: disable CodeReuse/ActiveRecord .group_by { |r| r[primary_key] } diff --git a/app/graphql/resolvers/concerns/issues/look_ahead_preloads.rb b/app/graphql/resolvers/concerns/issues/look_ahead_preloads.rb index c6e32be245d..2ea7a02bf15 100644 --- a/app/graphql/resolvers/concerns/issues/look_ahead_preloads.rb +++ b/app/graphql/resolvers/concerns/issues/look_ahead_preloads.rb @@ -20,7 +20,7 @@ module Issues end def preloads - { + preload_hash = { alert_management_alert: [:alert_management_alert], assignees: [:assignees], participants: Issue.participant_includes, @@ -28,6 +28,9 @@ module Issues customer_relations_contacts: { customer_relations_contacts: [:group] }, escalation_status: [:incident_management_issuable_escalation_status] } + preload_hash[:type] = :work_item_type if Feature.enabled?(:issue_type_uses_work_item_types_table) + + preload_hash end end end diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb index d56951bc821..c68e120ee24 100644 --- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb +++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb @@ -34,7 +34,7 @@ module ResolvesMergeRequests end def unconditional_includes - [:target_project] + [:target_project, :author] end def preloads diff --git a/app/graphql/resolvers/concerns/search_arguments.rb b/app/graphql/resolvers/concerns/search_arguments.rb index ccc012f2bf9..cc1a13fdf29 100644 --- a/app/graphql/resolvers/concerns/search_arguments.rb +++ b/app/graphql/resolvers/concerns/search_arguments.rb @@ -18,6 +18,7 @@ module SearchArguments def ready?(**args) validate_search_in_params!(args) validate_anonymous_search_access!(args) + validate_search_rate_limit!(args) super end @@ -39,6 +40,28 @@ module SearchArguments '`search` should be present when including the `in` argument' end + def validate_search_rate_limit!(args) + return if args[:search].blank? || context[:request].nil? || Feature.disabled?(:rate_limit_issuable_searches) + + if current_user.present? + rate_limiter_key = :search_rate_limit + rate_limiter_scope = [current_user] + else + rate_limiter_key = :search_rate_limit_unauthenticated + rate_limiter_scope = [context[:request].ip] + end + + if ::Gitlab::ApplicationRateLimiter.throttled_request?( + context[:request], + current_user, + rate_limiter_key, + scope: rate_limiter_scope + ) + raise Gitlab::Graphql::Errors::ResourceNotAvailable, + 'This endpoint has been requested with the search argument too many times. Try again later.' + end + end + def prepare_finder_params(args) prepare_search_params(args) end diff --git a/app/graphql/resolvers/issues/base_resolver.rb b/app/graphql/resolvers/issues/base_resolver.rb index 9a2c4572abb..fefd17d5e20 100644 --- a/app/graphql/resolvers/issues/base_resolver.rb +++ b/app/graphql/resolvers/issues/base_resolver.rb @@ -129,7 +129,8 @@ module Resolvers params[:or] = params[:or].to_h if params[:or] params[:iids] ||= [params.delete(:iid)].compact if params[:iid] - prepare_author_username_params(params) + rewrite_param_name(params[:or], :author_usernames, :author_username) + rewrite_param_name(params[:or], :label_names, :label_name) prepare_assignee_username_params(params) prepare_release_tag_params(params) @@ -143,20 +144,14 @@ module Resolvers args[:release_tag] ||= release_tag_wildcard end - def prepare_author_username_params(args) - args[:or][:author_username] = args[:or].delete(:author_usernames) if args.dig(:or, :author_usernames).present? - end - def prepare_assignee_username_params(args) - args[:assignee_username] = args.delete(:assignee_usernames) if args[:assignee_usernames].present? - - if args.dig(:or, :assignee_usernames).present? - args[:or][:assignee_username] = args[:or].delete(:assignee_usernames) - end - - return unless args.dig(:not, :assignee_usernames).present? + rewrite_param_name(args, :assignee_usernames, :assignee_username) + rewrite_param_name(args[:or], :assignee_usernames, :assignee_username) + rewrite_param_name(args[:not], :assignee_usernames, :assignee_username) + end - args[:not][:assignee_username] = args[:not].delete(:assignee_usernames) + def rewrite_param_name(params, old_name, new_name) + params[new_name] = params.delete(old_name) if params && params[old_name].present? end def mutually_exclusive_release_tag_args diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb index 3e61ba755d8..24009bf7e18 100644 --- a/app/graphql/resolvers/issues_resolver.rb +++ b/app/graphql/resolvers/issues_resolver.rb @@ -5,6 +5,8 @@ module Resolvers prepend ::Issues::LookAheadPreloads include ::Issues::SortArguments + NON_FILTER_ARGUMENTS = %i[sort lookahead].freeze + argument :state, Types::IssuableStateEnum, required: false, description: 'Current state of this issue.' @@ -17,6 +19,14 @@ module Resolvers ::Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects, current_user).execute end + def ready?(**args) + unless filter_provided?(args) + raise Gitlab::Graphql::Errors::ArgumentError, _('You must provide at least one filter argument for this query') + end + + super + end + def resolve_with_lookahead(**args) return unless Feature.enabled?(:root_level_issues_query) @@ -32,5 +42,11 @@ module Resolvers issues end end + + private + + def filter_provided?(args) + args.except(*NON_FILTER_ARGUMENTS).values.any?(&:present?) + end end end diff --git a/app/graphql/resolvers/projects/branch_rules_resolver.rb b/app/graphql/resolvers/projects/branch_rules_resolver.rb index e99d7ae4d5f..d1b39df602f 100644 --- a/app/graphql/resolvers/projects/branch_rules_resolver.rb +++ b/app/graphql/resolvers/projects/branch_rules_resolver.rb @@ -10,7 +10,15 @@ module Resolvers alias_method :project, :object def resolve_with_lookahead(**args) - apply_lookahead(project.protected_branches) + protected_branches.map do |protected_branch| + ::Projects::BranchRule.new(project, protected_branch) + end + end + + private + + def protected_branches + apply_lookahead(project.protected_branches.sorted_by_name) end end end diff --git a/app/graphql/resolvers/timelog_resolver.rb b/app/graphql/resolvers/timelog_resolver.rb index 52c4508003a..dc42a5f38c9 100644 --- a/app/graphql/resolvers/timelog_resolver.rb +++ b/app/graphql/resolvers/timelog_resolver.rb @@ -34,19 +34,23 @@ module Resolvers required: false, description: 'List timelogs for a user.' + argument :sort, Types::TimeTracking::TimelogSortEnum, + description: 'List timelogs in a particular order.', + required: false, + default_value: { field: 'spent_at', direction: :asc } + def resolve_with_lookahead(**args) validate_args!(object, args) - timelogs = object&.timelogs || Timelog.limit(GitlabSchema.default_max_page_size) + timelogs = object&.timelogs || Timelog.all - if args.any? - args = parse_datetime_args(args) + args = parse_datetime_args(args) - timelogs = apply_user_filter(timelogs, args) - timelogs = apply_project_filter(timelogs, args) - timelogs = apply_time_filter(timelogs, args) - timelogs = apply_group_filter(timelogs, args) - end + timelogs = apply_user_filter(timelogs, args) + timelogs = apply_project_filter(timelogs, args) + timelogs = apply_time_filter(timelogs, args) + timelogs = apply_group_filter(timelogs, args) + timelogs = apply_sorting(timelogs, args) apply_lookahead(timelogs) end @@ -60,7 +64,12 @@ module Resolvers end def validate_args!(object, args) - if args.empty? && object.nil? + # sort is always provided because of its default value so we + # should check the remaining args to make sure at least one filter + # argument was provided + cleaned_args = args.except(:sort) + + if cleaned_args.empty? && object.nil? raise_argument_error('Provide at least one argument') elsif args[:start_time] && args[:start_date] raise_argument_error('Provide either a start date or time, but not both') @@ -132,6 +141,15 @@ module Resolvers timelogs end + def apply_sorting(timelogs, args) + return timelogs unless args[:sort] + + field = args[:sort][:field] + direction = args[:sort][:direction] + + timelogs.sort_by_field(field, direction) + end + def raise_argument_error(message) raise Gitlab::Graphql::Errors::ArgumentError, message end diff --git a/app/graphql/resolvers/work_items_resolver.rb b/app/graphql/resolvers/work_items_resolver.rb index a3de875c196..83ed8c37250 100644 --- a/app/graphql/resolvers/work_items_resolver.rb +++ b/app/graphql/resolvers/work_items_resolver.rb @@ -55,7 +55,7 @@ module Resolvers last_edited_by: :last_edited_by, assignees: :assignees, parent: :work_item_parent, - children: { work_item_children: [:author, { project: :project_feature }] }, + children: { work_item_children_by_created_at: [:author, { project: :project_feature }] }, labels: :labels, milestone: :milestone } diff --git a/app/graphql/types/access_level_enum.rb b/app/graphql/types/access_level_enum.rb index 299952e4685..d58e7230a8e 100644 --- a/app/graphql/types/access_level_enum.rb +++ b/app/graphql/types/access_level_enum.rb @@ -14,3 +14,5 @@ module Types value 'OWNER', value: Gitlab::Access::OWNER, description: 'Owner access.' end end + +Types::AccessLevelEnum.prepend_mod_with('Types::AccessLevelEnum') diff --git a/app/graphql/types/achievements/achievement_type.rb b/app/graphql/types/achievements/achievement_type.rb new file mode 100644 index 00000000000..e2b9495c83d --- /dev/null +++ b/app/graphql/types/achievements/achievement_type.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module Types + module Achievements + class AchievementType < BaseObject + graphql_name 'Achievement' + + authorize :read_achievement + + field :id, + ::Types::GlobalIDType[::Achievements::Achievement], + null: false, + description: 'ID of the achievement.' + + field :namespace, + ::Types::NamespaceType, + null: false, + description: 'Namespace of the achievement.' + + field :name, + GraphQL::Types::String, + null: false, + description: 'Name of the achievement.' + + field :avatar_url, + GraphQL::Types::String, + null: true, + description: 'URL to avatar of the achievement.' + + field :description, + GraphQL::Types::String, + null: true, + description: 'Description or notes for the achievement.' + + field :revokeable, + GraphQL::Types::Boolean, + null: false, + description: 'Revokeability of the achievement.' + + field :created_at, + Types::TimeType, + null: false, + description: 'Timestamp the achievement was created.' + + field :updated_at, + Types::TimeType, + null: false, + description: 'Timestamp the achievement was last updated.' + + def avatar_url + object.avatar_url(only_path: false) + end + end + end +end diff --git a/app/graphql/types/ci/runner_countable_connection_type.rb b/app/graphql/types/ci/runner_countable_connection_type.rb new file mode 100644 index 00000000000..f5c3a2c1f5f --- /dev/null +++ b/app/graphql/types/ci/runner_countable_connection_type.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + class RunnerCountableConnectionType < ::Types::CountableConnectionType + end + # rubocop: enable Graphql/AuthorizeTypes + end +end + +Types::Ci::RunnerCountableConnectionType.prepend_mod diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb index 5d34906f7b8..35339624e37 100644 --- a/app/graphql/types/ci/runner_type.rb +++ b/app/graphql/types/ci/runner_type.rb @@ -6,7 +6,7 @@ module Types graphql_name 'CiRunner' edge_type_class(RunnerWebUrlEdge) - connection_type_class(Types::CountableConnectionType) + connection_type_class(RunnerCountableConnectionType) authorize :read_runner present_using ::Ci::RunnerPresenter @@ -38,10 +38,9 @@ module Types field :executor_name, GraphQL::Types::String, null: true, description: 'Executor last advertised by the runner.', method: :executor_name - field :groups, 'Types::GroupConnection', - null: true, - resolver: ::Resolvers::Ci::RunnerGroupsResolver, - description: 'Groups the runner is associated with. For group runners only.' + field :groups, null: true, + resolver: ::Resolvers::Ci::RunnerGroupsResolver, + description: 'Groups the runner is associated with. For group runners only.' field :id, ::Types::GlobalIDType[::Ci::Runner], null: false, description: 'ID of the runner.' field :ip_address, GraphQL::Types::String, null: true, diff --git a/app/graphql/types/description_version_type.rb b/app/graphql/types/description_version_type.rb new file mode 100644 index 00000000000..bee30597e4c --- /dev/null +++ b/app/graphql/types/description_version_type.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Types + class DescriptionVersionType < BaseObject + graphql_name 'DescriptionVersion' + + authorize :read_issuable + + field :id, ::Types::GlobalIDType[::DescriptionVersion], + null: false, + description: 'ID of the description version.' + + field :description, GraphQL::Types::String, + null: true, + description: 'Content of the given description version.' + end +end + +Types::DescriptionVersionType.prepend_mod diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index dd2ad26ce49..4948063610a 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -117,7 +117,6 @@ module Types description: 'Collection of design images associated with this issue.' field :type, Types::IssueTypeEnum, null: true, - method: :issue_type, description: 'Type of the issue.' field :alert_management_alert, @@ -198,6 +197,14 @@ module Types def escalation_status object.supports_escalation? ? object.escalation_status&.status_name : nil end + + def type + if Feature.enabled?(:issue_type_uses_work_item_types_table) + object.work_item_type.base_type + else + object.issue_type + end + end end end diff --git a/app/graphql/types/issues/unioned_issue_filter_input_type.rb b/app/graphql/types/issues/unioned_issue_filter_input_type.rb index 9c7261279c7..a9c5b3c24ce 100644 --- a/app/graphql/types/issues/unioned_issue_filter_input_type.rb +++ b/app/graphql/types/issues/unioned_issue_filter_input_type.rb @@ -11,6 +11,9 @@ module Types argument :author_usernames, [GraphQL::Types::String], required: false, description: 'Filters issues that are authored by one of the given users.' + argument :label_names, [GraphQL::Types::String], + required: false, + description: 'Filters issues that have at least one of the given labels.' end end end diff --git a/app/graphql/types/member_access_level_enum.rb b/app/graphql/types/member_access_level_enum.rb new file mode 100644 index 00000000000..8f89b882641 --- /dev/null +++ b/app/graphql/types/member_access_level_enum.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Types + class MemberAccessLevelEnum < BaseEnum + graphql_name 'MemberAccessLevel' + description 'Access level of a group or project member' + + value 'GUEST', value: Gitlab::Access::GUEST, description: 'Guest access.' + value 'REPORTER', value: Gitlab::Access::REPORTER, description: 'Reporter access.' + value 'DEVELOPER', value: Gitlab::Access::DEVELOPER, description: 'Developer access.' + value 'MAINTAINER', value: Gitlab::Access::MAINTAINER, description: 'Maintainer access.' + value 'OWNER', value: Gitlab::Access::OWNER, description: 'Owner access.' + end +end + +Types::MemberAccessLevelEnum.prepend_mod_with('Types::MemberAccessLevelEnum') diff --git a/app/graphql/types/member_interface.rb b/app/graphql/types/member_interface.rb index edadbcddfb3..4c9ee6246a3 100644 --- a/app/graphql/types/member_interface.rb +++ b/app/graphql/types/member_interface.rb @@ -46,7 +46,7 @@ module Types def merge_request_interaction(id: nil) Gitlab::Graphql::Lazy.with_value(GitlabSchema.object_from_id(id, expected_class: ::MergeRequest)) do |merge_request| - Users::MergeRequestInteraction.new(user: object.user, merge_request: merge_request) if merge_request + ::Users::MergeRequestInteraction.new(user: object.user, merge_request: merge_request) if merge_request end end end diff --git a/app/graphql/types/merge_requests/interacts_with_merge_request.rb b/app/graphql/types/merge_requests/interacts_with_merge_request.rb index bef2d39dc5c..672a2a315d4 100644 --- a/app/graphql/types/merge_requests/interacts_with_merge_request.rb +++ b/app/graphql/types/merge_requests/interacts_with_merge_request.rb @@ -16,7 +16,7 @@ module Types def merge_request_interaction(parent:, id: nil) # need the connection parent if called from a connection node: parent = parent.parent if parent.try(:field)&.connection? - Users::MergeRequestInteraction.new(user: object, merge_request: parent) + ::Users::MergeRequestInteraction.new(user: object, merge_request: parent) end end end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index b342e57804b..5a92ba754aa 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -6,6 +6,7 @@ module Types include Gitlab::Graphql::MountMutation + mount_mutation Mutations::Achievements::Create mount_mutation Mutations::Admin::SidekiqQueues::DeleteJobs mount_mutation Mutations::AlertManagement::CreateAlertIssue mount_mutation Mutations::AlertManagement::UpdateAlertStatus @@ -66,6 +67,7 @@ module Types mount_mutation Mutations::Issues::LinkAlerts mount_mutation Mutations::Issues::UnlinkAlert mount_mutation Mutations::Labels::Create + mount_mutation Mutations::Members::Groups::BulkUpdate mount_mutation Mutations::MergeRequests::Accept mount_mutation Mutations::MergeRequests::Create mount_mutation Mutations::MergeRequests::Update diff --git a/app/graphql/types/namespace/shared_runners_setting_enum.rb b/app/graphql/types/namespace/shared_runners_setting_enum.rb index 4773e414aeb..fd067c9d803 100644 --- a/app/graphql/types/namespace/shared_runners_setting_enum.rb +++ b/app/graphql/types/namespace/shared_runners_setting_enum.rb @@ -4,10 +4,21 @@ module Types class Namespace::SharedRunnersSettingEnum < BaseEnum graphql_name 'SharedRunnersSetting' - ::Namespace::SHARED_RUNNERS_SETTINGS.each do |type| + DEPRECATED_SETTINGS = [::Namespace::SR_DISABLED_WITH_OVERRIDE].freeze + + ::Namespace::SHARED_RUNNERS_SETTINGS.excluding(DEPRECATED_SETTINGS).each do |type| value type.upcase, description: "Sharing of runners is #{type.tr('_', ' ')}.", value: type end + + value ::Namespace::SR_DISABLED_WITH_OVERRIDE.upcase, + description: "Sharing of runners is disabled and overridable.", + value: ::Namespace::SR_DISABLED_WITH_OVERRIDE, + deprecated: { + reason: :renamed, + replacement: ::Namespace::SR_DISABLED_AND_OVERRIDABLE, + milestone: "17.0" + } end end diff --git a/app/graphql/types/namespace_type.rb b/app/graphql/types/namespace_type.rb index 0f634e7c2d3..fc55ff512b6 100644 --- a/app/graphql/types/namespace_type.rb +++ b/app/graphql/types/namespace_type.rb @@ -63,6 +63,13 @@ module Types description: "Timelog categories for the namespace.", alpha: { milestone: '15.3' } + field :achievements, + Types::Achievements::AchievementType.connection_type, + null: true, + alpha: { milestone: '15.8' }, + description: "Achievements for the namespace. " \ + "Returns `null` if the `achievements` feature flag is disabled." + markdown_field :description_html, null: true def timelog_categories @@ -76,6 +83,10 @@ module Types def root_storage_statistics Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader.new(object.id).find end + + def achievements + object.achievements if Feature.enabled?(:achievements, object) + end end end diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb index 05629ea9223..5055facb21b 100644 --- a/app/graphql/types/notes/note_type.rb +++ b/app/graphql/types/notes/note_type.rb @@ -11,54 +11,72 @@ module Types implements(Types::ResolvableInterface) - field :id, ::Types::GlobalIDType[::Note], null: false, - description: 'ID of the note.' + field :id, ::Types::GlobalIDType[::Note], + null: false, + description: 'ID of the note.' field :project, Types::ProjectType, - null: true, - description: 'Project associated with the note.' + null: true, + description: 'Project associated with the note.' field :author, Types::UserType, - null: false, - description: 'User who wrote this note.' + null: false, + description: 'User who wrote this note.' field :system, GraphQL::Types::Boolean, - null: false, - description: 'Indicates whether this note was created by the system or by a user.' + null: false, + description: 'Indicates whether this note was created by the system or by a user.' field :system_note_icon_name, - GraphQL::Types::String, - null: true, - description: 'Name of the icon corresponding to a system note.' + GraphQL::Types::String, + null: true, + description: 'Name of the icon corresponding to a system note.' field :body, GraphQL::Types::String, - null: false, - method: :note, - description: 'Content of the note.' - - field :confidential, GraphQL::Types::Boolean, null: true, - description: 'Indicates if this note is confidential.', - method: :confidential?, - deprecated: { - reason: :renamed, - replacement: 'internal', - milestone: '15.5' - } - - field :internal, GraphQL::Types::Boolean, null: true, - description: 'Indicates if this note is internal.', - method: :confidential? - - field :created_at, Types::TimeType, null: false, - description: 'Timestamp of the note creation.' - field :discussion, Types::Notes::DiscussionType, null: true, - description: 'Discussion this note is a part of.' - field :position, Types::Notes::DiffPositionType, null: true, - description: 'Position of this note on a diff.' - field :updated_at, Types::TimeType, null: false, - description: "Timestamp of the note's last activity." + null: false, + method: :note, + description: 'Content of the note.' + + field :confidential, GraphQL::Types::Boolean, + null: true, + description: 'Indicates if this note is confidential.', + method: :confidential?, + deprecated: { + reason: :renamed, + replacement: 'internal', + milestone: '15.5' + } + + field :internal, GraphQL::Types::Boolean, + null: true, + description: 'Indicates if this note is internal.', + method: :confidential? + + field :created_at, Types::TimeType, + null: false, + description: 'Timestamp of the note creation.' + field :discussion, Types::Notes::DiscussionType, + null: true, + description: 'Discussion this note is a part of.' + field :position, Types::Notes::DiffPositionType, + null: true, + description: 'Position of this note on a diff.' + field :updated_at, Types::TimeType, + null: false, + description: "Timestamp of the note's last activity." field :url, GraphQL::Types::String, - null: true, - description: 'URL to view this Note in the Web UI.' + null: true, + description: 'URL to view this Note in the Web UI.' + + field :last_edited_at, Types::TimeType, + null: true, + description: 'Timestamp when note was last edited.' + field :last_edited_by, Types::UserType, + null: true, + description: 'User who last edited the note.' + + field :system_note_metadata, Types::Notes::SystemNoteMetadataType, + null: true, + description: 'Metadata for the given note if it is a system note.' markdown_field :body_html, null: true, method: :note diff --git a/app/graphql/types/notes/noteable_interface.rb b/app/graphql/types/notes/noteable_interface.rb index bd22f12d6f0..537084dff62 100644 --- a/app/graphql/types/notes/noteable_interface.rb +++ b/app/graphql/types/notes/noteable_interface.rb @@ -7,6 +7,7 @@ module Types field :notes, Types::Notes::NoteType.connection_type, null: false, description: "All notes on this noteable." field :discussions, Types::Notes::DiscussionType.connection_type, null: false, description: "All discussions on this noteable." + field :commenters, Types::UserType.connection_type, null: false, description: "All commenters on this noteable." def self.resolve_type(object, context) case object @@ -24,6 +25,10 @@ module Types raise "Unknown GraphQL type for #{object}" end end + + def commenters + object.commenters(user: current_user) + end end end end diff --git a/app/graphql/types/notes/system_note_metadata_type.rb b/app/graphql/types/notes/system_note_metadata_type.rb new file mode 100644 index 00000000000..b3dd7e037f9 --- /dev/null +++ b/app/graphql/types/notes/system_note_metadata_type.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Types + module Notes + class SystemNoteMetadataType < BaseObject + graphql_name 'SystemNoteMetadata' + + authorize :read_note + + field :id, ::Types::GlobalIDType[::SystemNoteMetadata], + null: false, + description: 'Global ID of the specific system note metadata.' + + field :action, GraphQL::Types::String, + null: true, + description: 'System note metadata action.' + field :description_version, ::Types::DescriptionVersionType, + null: true, + description: 'Version of the changed description.' + end + end +end diff --git a/app/graphql/types/projects/branch_rule_type.rb b/app/graphql/types/projects/branch_rule_type.rb index 1afd2cc3fef..08b1203d4a3 100644 --- a/app/graphql/types/projects/branch_rule_type.rb +++ b/app/graphql/types/projects/branch_rule_type.rb @@ -5,7 +5,6 @@ module Types class BranchRuleType < BaseObject graphql_name 'BranchRule' description 'List of branch rules for a project, grouped by branch name.' - accepts ::ProtectedBranch authorize :read_protected_branch alias_method :branch_rule, :object @@ -22,6 +21,12 @@ module Types calls_gitaly: true, description: "Check if this branch rule protects the project's default branch." + field :is_protected, + type: GraphQL::Types::Boolean, + null: false, + method: :protected?, + description: "Check if this branch rule protects access for the branch." + field :matching_branches_count, type: GraphQL::Types::Int, null: false, @@ -30,9 +35,8 @@ module Types field :branch_protection, type: Types::BranchRules::BranchProtectionType, - null: false, - description: 'Branch protections configured for this branch rule.', - method: :itself + null: true, + description: 'Branch protections configured for this branch rule.' field :created_at, Types::TimeType, @@ -43,10 +47,6 @@ module Types Types::TimeType, null: false, description: 'Timestamp of when the branch rule was last updated.' - - def matching_branches_count - branch_rule.matching(branch_rule.project.repository.branch_names).count - end end end end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 7263f792bae..990ba1fb7fc 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -7,56 +7,17 @@ module Types # The design management context object needs to implement #issue DesignManagementObject = Struct.new(:issue) - field :project, Types::ProjectType, - null: true, - resolver: Resolvers::ProjectResolver, - description: "Find a project." - - field :projects, Types::ProjectType.connection_type, - null: true, - resolver: Resolvers::ProjectsResolver, - description: "Find projects visible to the current user." - - field :group, Types::GroupType, - null: true, - resolver: Resolvers::GroupResolver, - description: "Find a group." - - field :current_user, Types::UserType, - null: true, - description: "Get information about current user." - - field :namespace, Types::NamespaceType, - null: true, - resolver: Resolvers::NamespaceResolver, - description: "Find a namespace." - - field :metadata, Types::MetadataType, - null: true, - resolver: Resolvers::MetadataResolver, - description: 'Metadata about GitLab.' - - field :query_complexity, Types::QueryComplexityType, + field :board_list, ::Types::BoardListType, null: true, - description: 'Information about the complexity of the GraphQL query.' - - field :snippets, - Types::SnippetType.connection_type, + resolver: Resolvers::BoardListResolver + field :ci_application_settings, Types::Ci::ApplicationSettingType, null: true, - resolver: Resolvers::SnippetsResolver, - description: 'Find Snippets visible to the current user.' - - field :design_management, Types::DesignManagementType, - null: false, - description: 'Fields related to design management.' - - field :milestone, ::Types::MilestoneType, + description: 'CI related settings that apply to the entire instance.' + field :ci_config, resolver: Resolvers::Ci::ConfigResolver, complexity: 126 # AUTHENTICATED_MAX_COMPLEXITY / 2 + 1 + field :ci_variables, + Types::Ci::InstanceVariableType.connection_type, null: true, - extras: [:lookahead], - description: 'Find a milestone.' do - argument :id, ::Types::GlobalIDType[Milestone], required: true, description: 'Find a milestone by its ID.' - end - + description: "List of the instance's CI/CD variables." field :container_repository, Types::ContainerRepositoryDetailsType, null: true, description: 'Find a container repository.' do @@ -65,107 +26,116 @@ module Types required: true, description: 'Global ID of the container repository.' end - - field :package, - description: 'Find a package. This field can only be resolved for one query in any single request. Returns `null` if a package has no `default` status.', - resolver: Resolvers::PackageDetailsResolver - - field :user, Types::UserType, - null: true, - description: 'Find a user.', - resolver: Resolvers::UserResolver - - field :users, Types::UserType.connection_type, + field :current_user, Types::UserType, null: true, - description: 'Find users.', - resolver: Resolvers::UsersResolver - + description: "Get information about current user." + field :design_management, Types::DesignManagementType, + null: false, + description: 'Fields related to design management.' field :echo, resolver: Resolvers::EchoResolver - - field :issues, + field :gitpod_enabled, GraphQL::Types::Boolean, null: true, - alpha: { milestone: '15.6' }, - resolver: Resolvers::IssuesResolver, - description: 'Issues visible by the current user.' \ - ' Returns null if the `root_level_issues_query` feature flag is disabled.' - + description: "Whether Gitpod is enabled in application settings." + field :group, Types::GroupType, + null: true, + resolver: Resolvers::GroupResolver, + description: "Find a group." field :issue, Types::IssueType, null: true, description: 'Find an issue.' do argument :id, ::Types::GlobalIDType[::Issue], required: true, description: 'Global ID of the issue.' end - - field :work_item, Types::WorkItemType, + field :issues, null: true, - resolver: Resolvers::WorkItemResolver, - alpha: { milestone: '15.1' }, - description: 'Find a work item.' - + alpha: { milestone: '15.6' }, + resolver: Resolvers::IssuesResolver, + description: 'Find issues visible to the current user.' \ + ' At least one filter must be provided.' \ + ' Returns `null` if the `root_level_issues_query` feature flag is disabled.' + field :jobs, + ::Types::Ci::JobType.connection_type, + null: true, + description: 'All jobs on this GitLab instance.', + resolver: ::Resolvers::Ci::AllJobsResolver field :merge_request, Types::MergeRequestType, null: true, description: 'Find a merge request.' do argument :id, ::Types::GlobalIDType[::MergeRequest], required: true, description: 'Global ID of the merge request.' end - - field :usage_trends_measurements, Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type, + field :metadata, Types::MetadataType, null: true, - description: 'Get statistics on the instance.', - resolver: Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver - - field :ci_application_settings, Types::Ci::ApplicationSettingType, + resolver: Resolvers::MetadataResolver, + description: 'Metadata about GitLab.' + field :milestone, ::Types::MilestoneType, null: true, - description: 'CI related settings that apply to the entire instance.' - - field :runner_platforms, resolver: Resolvers::Ci::RunnerPlatformsResolver - field :runner_setup, resolver: Resolvers::Ci::RunnerSetupResolver - + extras: [:lookahead], + description: 'Find a milestone.' do + argument :id, ::Types::GlobalIDType[Milestone], required: true, description: 'Find a milestone by its ID.' + end + field :namespace, Types::NamespaceType, + null: true, + resolver: Resolvers::NamespaceResolver, + description: "Find a namespace." + field :package, + description: 'Find a package. This field can only be resolved for one query in any single request. Returns `null` if a package has no `default` status.', + resolver: Resolvers::PackageDetailsResolver + field :project, Types::ProjectType, + null: true, + resolver: Resolvers::ProjectResolver, + description: "Find a project." + field :projects, Types::ProjectType.connection_type, + null: true, + resolver: Resolvers::ProjectsResolver, + description: "Find projects visible to the current user." + field :query_complexity, Types::QueryComplexityType, + null: true, + description: 'Information about the complexity of the GraphQL query.' field :runner, Types::Ci::RunnerType, null: true, resolver: Resolvers::Ci::RunnerResolver, extras: [:lookahead], description: "Find a runner." - + field :runner_platforms, resolver: Resolvers::Ci::RunnerPlatformsResolver + field :runner_setup, resolver: Resolvers::Ci::RunnerSetupResolver field :runners, Types::Ci::RunnerType.connection_type, null: true, resolver: Resolvers::Ci::RunnersResolver, description: "Find runners visible to the current user." - - field :ci_variables, - Types::Ci::InstanceVariableType.connection_type, + field :snippets, + Types::SnippetType.connection_type, null: true, - description: "List of the instance's CI/CD variables." - - field :ci_config, resolver: Resolvers::Ci::ConfigResolver, complexity: 126 # AUTHENTICATED_MAX_COMPLEXITY / 2 + 1 - + resolver: Resolvers::SnippetsResolver, + description: 'Find Snippets visible to the current user.' field :timelogs, Types::TimelogType.connection_type, null: true, description: 'Find timelogs visible to the current user.', extras: [:lookahead], complexity: 5, resolver: ::Resolvers::TimelogResolver - - field :board_list, ::Types::BoardListType, - null: true, - resolver: Resolvers::BoardListResolver - field :todo, null: true, resolver: Resolvers::TodoResolver - field :topics, Types::Projects::TopicType.connection_type, null: true, resolver: Resolvers::TopicsResolver, description: "Find project topics." - - field :gitpod_enabled, GraphQL::Types::Boolean, + field :usage_trends_measurements, Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type, null: true, - description: "Whether Gitpod is enabled in application settings." - - field :jobs, - ::Types::Ci::JobType.connection_type, + description: 'Get statistics on the instance.', + resolver: Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver + field :user, Types::UserType, null: true, - description: 'All jobs on this GitLab instance.', - resolver: ::Resolvers::Ci::AllJobsResolver + description: 'Find a user.', + resolver: Resolvers::UserResolver + field :users, Types::UserType.connection_type, + null: true, + description: 'Find users.', + resolver: Resolvers::UsersResolver + field :work_item, Types::WorkItemType, + null: true, + resolver: Resolvers::WorkItemResolver, + alpha: { milestone: '15.1' }, + description: 'Find a work item.' def design_management DesignManagementObject.new(nil) diff --git a/app/graphql/types/repository/blob_type.rb b/app/graphql/types/repository/blob_type.rb index 8c90a8df611..c5d6e26e94b 100644 --- a/app/graphql/types/repository/blob_type.rb +++ b/app/graphql/types/repository/blob_type.rb @@ -44,11 +44,11 @@ module Types field :fork_and_view_path, GraphQL::Types::String, null: true, description: 'Web path to view this blob using a forked project.' - field :size, GraphQL::Types::Int, null: true, - description: 'Size (in bytes) of the blob.' + field :size, GraphQL::Types::BigInt, null: true, + description: 'Size (in bytes) of the blob.' - field :raw_size, GraphQL::Types::Int, null: true, - description: 'Size (in bytes) of the blob, or the blob target if stored externally.' + field :raw_size, GraphQL::Types::BigInt, null: true, + description: 'Size (in bytes) of the blob, or the blob target if stored externally.' field :raw_blob, GraphQL::Types::String, null: true, method: :data, description: 'Raw content of the blob.' diff --git a/app/graphql/types/time_tracking/timelog_connection_type.rb b/app/graphql/types/time_tracking/timelog_connection_type.rb new file mode 100644 index 00000000000..43e6955c2a3 --- /dev/null +++ b/app/graphql/types/time_tracking/timelog_connection_type.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Types + module TimeTracking + # rubocop: disable Graphql/AuthorizeTypes + class TimelogConnectionType < CountableConnectionType + field :total_spent_time, + GraphQL::Types::Int, + null: false, + description: 'Total time spent in seconds.' + + def total_spent_time + # rubocop: disable CodeReuse/ActiveRecord + relation = object.items + + # sometimes relation is an Array + relation = relation.reorder(nil) if relation.respond_to?(:reorder) + # rubocop: enable CodeReuse/ActiveRecord + + relation.sum(:time_spent) + end + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/time_tracking/timelog_sort_enum.rb b/app/graphql/types/time_tracking/timelog_sort_enum.rb new file mode 100644 index 00000000000..ad21c084d78 --- /dev/null +++ b/app/graphql/types/time_tracking/timelog_sort_enum.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Types + module TimeTracking + class TimelogSortEnum < SortEnum + graphql_name 'TimelogSort' + description 'Values for sorting timelogs' + + sortable_fields = ['Spent at', 'Time spent'] + + sortable_fields.each do |field| + value "#{field.upcase.tr(' ', '_')}_ASC", + value: { field: field.downcase.tr(' ', '_'), direction: :asc }, + description: "#{field} by ascending order." + value "#{field.upcase.tr(' ', '_')}_DESC", + value: { field: field.downcase.tr(' ', '_'), direction: :desc }, + description: "#{field} by descending order." + end + end + end +end diff --git a/app/graphql/types/timelog_type.rb b/app/graphql/types/timelog_type.rb index 3856e1aa3b3..3a060518cd9 100644 --- a/app/graphql/types/timelog_type.rb +++ b/app/graphql/types/timelog_type.rb @@ -4,6 +4,8 @@ module Types class TimelogType < BaseObject graphql_name 'Timelog' + connection_type_class(Types::TimeTracking::TimelogConnectionType) + authorize :read_issuable expose_permissions Types::PermissionTypes::Timelog diff --git a/app/graphql/types/todo_action_enum.rb b/app/graphql/types/todo_action_enum.rb index 33e1c4e98a4..fda96796c0f 100644 --- a/app/graphql/types/todo_action_enum.rb +++ b/app/graphql/types/todo_action_enum.rb @@ -11,6 +11,6 @@ module Types value 'directly_addressed', value: 7, description: 'User was directly addressed.' value 'merge_train_removed', value: 8, description: 'Merge request authored by the user was removed from the merge train.' value 'review_requested', value: 9, description: 'Review was requested from the user.' - value 'member_access_requested', value: 10, description: 'Group access requested from the user.' + value 'member_access_requested', value: 10, description: 'Group or project access requested from the user.' end end diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb index 51046d09f90..a5bed3b9e19 100644 --- a/app/graphql/types/user_interface.rb +++ b/app/graphql/types/user_interface.rb @@ -42,10 +42,23 @@ module Types null: true, description: 'User email.', method: :public_email, deprecated: { reason: :renamed, replacement: 'User.publicEmail', milestone: '13.7' } + field :emails, + type: Types::Users::EmailType.connection_type, + null: true, + description: "User's email addresses." field :public_email, type: GraphQL::Types::String, null: true, description: "User's public email." + field :commit_email, + type: GraphQL::Types::String, + null: true, + description: "User's default commit email.", + authorize: :read_user_email_address + field :namespace_commit_emails, + type: Types::Users::NamespaceCommitEmailType.connection_type, + null: true, + description: "User's custom namespace commit emails." field :avatar_url, type: GraphQL::Types::String, null: true, diff --git a/app/graphql/types/users/email_type.rb b/app/graphql/types/users/email_type.rb new file mode 100644 index 00000000000..5e0b9563f57 --- /dev/null +++ b/app/graphql/types/users/email_type.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Types + module Users + class EmailType < BaseObject + graphql_name 'Email' + + authorize :read_user_email_address + + field :id, + GraphQL::Types::ID, + null: false, + description: 'Internal ID of the email.' + + field :email, + GraphQL::Types::String, + null: false, + description: 'Email address.' + + field :confirmed_at, + Types::TimeType, + null: true, + description: 'Timestamp the email was confirmed.' + + field :created_at, + Types::TimeType, + null: false, + description: 'Timestamp the email was created.' + + field :updated_at, + Types::TimeType, + null: false, + description: 'Timestamp the email was last updated.' + end + end +end diff --git a/app/graphql/types/users/namespace_commit_email_type.rb b/app/graphql/types/users/namespace_commit_email_type.rb new file mode 100644 index 00000000000..a64423c7169 --- /dev/null +++ b/app/graphql/types/users/namespace_commit_email_type.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Types + module Users + class NamespaceCommitEmailType < BaseObject + graphql_name 'NamespaceCommitEmail' + + authorize :read_user_email_address + + field :id, + GraphQL::Types::ID, + null: false, + description: 'Internal ID of the namespace commit email.' + + field :email, + Types::Users::EmailType, + null: false, + description: 'Email.' + + field :namespace, + Types::NamespaceType, + null: false, + description: 'Namespace.' + + field :created_at, + Types::TimeType, + null: false, + description: 'Timestamp the namespace commit email was created.' + + field :updated_at, + Types::TimeType, + null: false, + description: 'Timestamp the namespace commit email was last updated.' + end + end +end diff --git a/app/graphql/types/work_items/widgets/description_type.rb b/app/graphql/types/work_items/widgets/description_type.rb index 4861f7f46d8..92dca965b59 100644 --- a/app/graphql/types/work_items/widgets/description_type.rb +++ b/app/graphql/types/work_items/widgets/description_type.rb @@ -26,9 +26,7 @@ module Types null: true, description: 'User that made the last edit to the work item\'s description.' - markdown_field :description_html, null: true do |resolved_object| - resolved_object.work_item - end + markdown_field :description_html, null: true, &:work_item end # rubocop:enable Graphql/AuthorizeTypes end |