diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-20 12:26:25 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-20 12:26:25 +0000 |
commit | a09983ae35713f5a2bbb100981116d31ce99826e (patch) | |
tree | 2ee2af7bd104d57086db360a7e6d8c9d5d43667a /app/graphql | |
parent | 18c5ab32b738c0b6ecb4d0df3994000482f34bd8 (diff) | |
download | gitlab-ce-a09983ae35713f5a2bbb100981116d31ce99826e.tar.gz |
Add latest changes from gitlab-org/gitlab@13-2-stable-ee
Diffstat (limited to 'app/graphql')
68 files changed, 722 insertions, 143 deletions
diff --git a/app/graphql/mutations/alert_management/alerts/todo/create.rb b/app/graphql/mutations/alert_management/alerts/todo/create.rb new file mode 100644 index 00000000000..3dba96e43f1 --- /dev/null +++ b/app/graphql/mutations/alert_management/alerts/todo/create.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Mutations + module AlertManagement + module Alerts + module Todo + class Create < Base + graphql_name 'AlertTodoCreate' + + def resolve(args) + alert = authorized_find!(project_path: args[:project_path], iid: args[:iid]) + result = ::AlertManagement::Alerts::Todo::CreateService.new(alert, current_user).execute + + prepare_response(result) + end + + private + + def prepare_response(result) + { + alert: result.payload[:alert], + todo: result.payload[:todo], + errors: result.error? ? [result.message] : [] + } + end + end + end + end + end +end diff --git a/app/graphql/mutations/alert_management/base.rb b/app/graphql/mutations/alert_management/base.rb index 7fcca63db51..0de4b9409e4 100644 --- a/app/graphql/mutations/alert_management/base.rb +++ b/app/graphql/mutations/alert_management/base.rb @@ -18,6 +18,11 @@ module Mutations null: true, description: "The alert after mutation" + field :todo, + Types::TodoType, + null: true, + description: "The todo after mutation" + field :issue, Types::IssueType, null: true, diff --git a/app/graphql/mutations/alert_management/update_alert_status.rb b/app/graphql/mutations/alert_management/update_alert_status.rb index d820124d26f..ed61555fbd6 100644 --- a/app/graphql/mutations/alert_management/update_alert_status.rb +++ b/app/graphql/mutations/alert_management/update_alert_status.rb @@ -19,8 +19,8 @@ module Mutations private def update_status(alert, status) - ::AlertManagement::UpdateAlertStatusService - .new(alert, current_user, status) + ::AlertManagement::Alerts::UpdateService + .new(alert, current_user, status: status) .execute end diff --git a/app/graphql/mutations/award_emojis/add.rb b/app/graphql/mutations/award_emojis/add.rb index 85f3eb065bb..856fdd5fb14 100644 --- a/app/graphql/mutations/award_emojis/add.rb +++ b/app/graphql/mutations/award_emojis/add.rb @@ -3,7 +3,7 @@ module Mutations module AwardEmojis class Add < Base - graphql_name 'AddAwardEmoji' + graphql_name 'AwardEmojiAdd' def resolve(args) awardable = authorized_find!(id: args[:awardable_id]) diff --git a/app/graphql/mutations/award_emojis/remove.rb b/app/graphql/mutations/award_emojis/remove.rb index f8a3d0ce390..c654688c6dc 100644 --- a/app/graphql/mutations/award_emojis/remove.rb +++ b/app/graphql/mutations/award_emojis/remove.rb @@ -3,7 +3,7 @@ module Mutations module AwardEmojis class Remove < Base - graphql_name 'RemoveAwardEmoji' + graphql_name 'AwardEmojiRemove' def resolve(args) awardable = authorized_find!(id: args[:awardable_id]) diff --git a/app/graphql/mutations/award_emojis/toggle.rb b/app/graphql/mutations/award_emojis/toggle.rb index 22eab4812a1..a7714e695d2 100644 --- a/app/graphql/mutations/award_emojis/toggle.rb +++ b/app/graphql/mutations/award_emojis/toggle.rb @@ -3,7 +3,7 @@ module Mutations module AwardEmojis class Toggle < Base - graphql_name 'ToggleAwardEmoji' + graphql_name 'AwardEmojiToggle' field :toggledOn, GraphQL::BOOLEAN_TYPE, null: false, description: 'Indicates the status of the emoji. ' \ diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb index 33f3f33a440..68e7853a9b1 100644 --- a/app/graphql/mutations/base_mutation.rb +++ b/app/graphql/mutations/base_mutation.rb @@ -7,6 +7,8 @@ module Mutations ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance' + field_class ::Types::BaseField + field :errors, [GraphQL::STRING_TYPE], null: false, description: 'Errors encountered during execution of the mutation.' diff --git a/app/graphql/mutations/concerns/mutations/resolves_issuable.rb b/app/graphql/mutations/concerns/mutations/resolves_issuable.rb index 13a56f2e709..0fe2d09de6d 100644 --- a/app/graphql/mutations/concerns/mutations/resolves_issuable.rb +++ b/app/graphql/mutations/concerns/mutations/resolves_issuable.rb @@ -9,30 +9,31 @@ module Mutations end def resolve_issuable(type:, parent_path:, iid:) - parent = resolve_issuable_parent(type, parent_path) - key = type == :merge_request ? :iids : :iid - args = { key => iid.to_s } + parent = ::Gitlab::Graphql::Lazy.force(resolve_issuable_parent(type, parent_path)) + return unless parent.present? - resolver = issuable_resolver(type, parent, context) - ready, early_return = resolver.ready?(**args) - - return early_return unless ready - - resolver.resolve(**args) + finder = issuable_finder(type, iids: [iid]) + Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).find_all.first end private - def issuable_resolver(type, parent, context) - resolver_class = "Resolvers::#{type.to_s.classify.pluralize}Resolver".constantize - - resolver_class.single.new(object: parent, context: context, field: nil) + def issuable_finder(type, args) + case type + when :merge_request + MergeRequestsFinder.new(current_user, args) + when :issue + IssuesFinder.new(current_user, args) + else + raise "Unsupported type: #{type}" + end end def resolve_issuable_parent(type, parent_path) + return unless parent_path.present? return unless type == :issue || type == :merge_request - resolve_project(full_path: parent_path) if parent_path.present? + resolve_project(full_path: parent_path) end end end diff --git a/app/graphql/mutations/container_expiration_policies/update.rb b/app/graphql/mutations/container_expiration_policies/update.rb index c210571c6ca..4bff04bb705 100644 --- a/app/graphql/mutations/container_expiration_policies/update.rb +++ b/app/graphql/mutations/container_expiration_policies/update.rb @@ -34,6 +34,16 @@ module Mutations required: false, description: copy_field_description(Types::ContainerExpirationPolicyType, :keep_n) + argument :name_regex, + Types::UntrustedRegexp, + required: false, + description: copy_field_description(Types::ContainerExpirationPolicyType, :name_regex) + + argument :name_regex_keep, + Types::UntrustedRegexp, + required: false, + description: copy_field_description(Types::ContainerExpirationPolicyType, :name_regex_keep) + field :container_expiration_policy, Types::ContainerExpirationPolicyType, null: true, diff --git a/app/graphql/mutations/issues/set_locked.rb b/app/graphql/mutations/issues/set_locked.rb new file mode 100644 index 00000000000..63a8483067a --- /dev/null +++ b/app/graphql/mutations/issues/set_locked.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Mutations + module Issues + class SetLocked < Base + graphql_name 'IssueSetLocked' + + argument :locked, + GraphQL::BOOLEAN_TYPE, + required: true, + description: 'Whether or not to lock discussion on the issue' + + def resolve(project_path:, iid:, locked:) + issue = authorized_find!(project_path: project_path, iid: iid) + + ::Issues::UpdateService.new(issue.project, current_user, discussion_locked: locked) + .execute(issue) + + { + issue: issue, + errors: errors_on_object(issue) + } + end + end + end +end diff --git a/app/graphql/mutations/jira_import/start.rb b/app/graphql/mutations/jira_import/start.rb index 3df26d33711..eda28059272 100644 --- a/app/graphql/mutations/jira_import/start.rb +++ b/app/graphql/mutations/jira_import/start.rb @@ -21,12 +21,17 @@ module Mutations argument :jira_project_name, GraphQL::STRING_TYPE, required: false, description: 'Project name of the importer Jira project' + argument :users_mapping, + [Types::JiraUsersMappingInputType], + required: false, + description: 'The mapping of Jira to GitLab users' - def resolve(project_path:, jira_project_key:) + def resolve(project_path:, jira_project_key:, users_mapping:) project = authorized_find!(full_path: project_path) + mapping = users_mapping.to_ary.map { |map| map.to_hash } service_response = ::JiraImport::StartImportService - .new(context[:current_user], project, jira_project_key) + .new(context[:current_user], project, jira_project_key, mapping) .execute jira_import = service_response.success? ? service_response.payload[:import_data] : nil diff --git a/app/graphql/mutations/merge_requests/update.rb b/app/graphql/mutations/merge_requests/update.rb new file mode 100644 index 00000000000..b583fdfca9b --- /dev/null +++ b/app/graphql/mutations/merge_requests/update.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Mutations + module MergeRequests + class Update < Base + graphql_name 'MergeRequestUpdate' + + description 'Update attributes of a merge request' + + argument :title, GraphQL::STRING_TYPE, + required: false, + description: copy_field_description(Types::MergeRequestType, :title) + + argument :target_branch, GraphQL::STRING_TYPE, + required: false, + description: copy_field_description(Types::MergeRequestType, :target_branch) + + argument :description, GraphQL::STRING_TYPE, + required: false, + description: copy_field_description(Types::MergeRequestType, :description) + + def resolve(args) + merge_request = authorized_find!(args.slice(:project_path, :iid)) + attributes = args.slice(:title, :description, :target_branch).compact + + ::MergeRequests::UpdateService + .new(merge_request.project, current_user, attributes) + .execute(merge_request) + + errors = errors_on_object(merge_request) + + { + merge_request: merge_request.reset, + errors: errors + } + end + end + end +end diff --git a/app/graphql/mutations/notes/create/base.rb b/app/graphql/mutations/notes/create/base.rb index cf9f74a63d8..f081eac368e 100644 --- a/app/graphql/mutations/notes/create/base.rb +++ b/app/graphql/mutations/notes/create/base.rb @@ -18,6 +18,11 @@ module Mutations required: true, description: copy_field_description(Types::Notes::NoteType, :body) + argument :confidential, + GraphQL::BOOLEAN_TYPE, + required: false, + description: 'The confidentiality flag of a note. Default is false.' + def resolve(args) noteable = authorized_find!(id: args[:noteable_id]) @@ -40,7 +45,8 @@ module Mutations def create_note_params(noteable, args) { noteable: noteable, - note: args[:body] + note: args[:body], + confidential: args[:confidential] } end end diff --git a/app/graphql/mutations/snippets/create.rb b/app/graphql/mutations/snippets/create.rb index e1022358c09..89c21486a74 100644 --- a/app/graphql/mutations/snippets/create.rb +++ b/app/graphql/mutations/snippets/create.rb @@ -21,7 +21,7 @@ module Mutations description: 'File name of the snippet' argument :content, GraphQL::STRING_TYPE, - required: true, + required: false, description: 'Content of the snippet' argument :description, GraphQL::STRING_TYPE, @@ -40,6 +40,10 @@ module Mutations required: false, description: 'The paths to files uploaded in the snippet description' + argument :files, [Types::Snippets::FileInputType], + description: "The snippet files to create", + required: false + def resolve(args) project_path = args.delete(:project_path) @@ -49,13 +53,9 @@ module Mutations raise_resource_not_available_error! end - # We need to rename `uploaded_files` into `files` because - # it's the expected key param - args[:files] = args.delete(:uploaded_files) - service_response = ::Snippets::CreateService.new(project, - context[:current_user], - args).execute + context[:current_user], + create_params(args)).execute snippet = service_response.payload[:snippet] @@ -82,6 +82,18 @@ module Mutations def can_create_personal_snippet? Ability.allowed?(context[:current_user], :create_snippet) end + + def create_params(args) + args.tap do |create_args| + # We need to rename `files` into `snippet_actions` because + # it's the expected key param + create_args[:snippet_actions] = create_args.delete(:files)&.map(&:to_h) + + # We need to rename `uploaded_files` into `files` because + # it's the expected key param + create_args[:files] = create_args.delete(:uploaded_files) + end + end end end end diff --git a/app/graphql/mutations/snippets/update.rb b/app/graphql/mutations/snippets/update.rb index b6bdcb9b67b..8890158b0df 100644 --- a/app/graphql/mutations/snippets/update.rb +++ b/app/graphql/mutations/snippets/update.rb @@ -30,12 +30,16 @@ module Mutations description: 'The visibility level of the snippet', required: false + argument :files, [Types::Snippets::FileInputType], + description: 'The snippet files to update', + required: false + def resolve(args) snippet = authorized_find!(id: args.delete(:id)) result = ::Snippets::UpdateService.new(snippet.project, - context[:current_user], - args).execute(snippet) + context[:current_user], + update_params(args)).execute(snippet) snippet = result.payload[:snippet] { @@ -47,7 +51,15 @@ module Mutations private def ability_name - "update" + 'update' + end + + def update_params(args) + args.tap do |update_args| + # We need to rename `files` into `snippet_actions` because + # it's the expected key param + update_args[:snippet_actions] = update_args.delete(:files)&.map(&:to_h) + end end end end diff --git a/app/graphql/mutations/todos/mark_all_done.rb b/app/graphql/mutations/todos/mark_all_done.rb index d30d1bcbcf0..8b53658ddd5 100644 --- a/app/graphql/mutations/todos/mark_all_done.rb +++ b/app/graphql/mutations/todos/mark_all_done.rb @@ -10,8 +10,13 @@ module Mutations field :updated_ids, [GraphQL::ID_TYPE], null: false, + deprecated: { reason: 'Use todos', milestone: '13.2' }, description: 'Ids of the updated todos' + field :todos, [::Types::TodoType], + null: false, + description: 'Updated todos' + def resolve authorize!(current_user) @@ -19,6 +24,7 @@ module Mutations { updated_ids: map_to_global_ids(updated_ids), + todos: Todo.id_in(updated_ids), errors: [] } end diff --git a/app/graphql/mutations/todos/restore_many.rb b/app/graphql/mutations/todos/restore_many.rb index e95651b232f..c5e2750768c 100644 --- a/app/graphql/mutations/todos/restore_many.rb +++ b/app/graphql/mutations/todos/restore_many.rb @@ -14,7 +14,12 @@ module Mutations field :updated_ids, [GraphQL::ID_TYPE], null: false, - description: 'The ids of the updated todo items' + description: 'The ids of the updated todo items', + deprecated: { reason: 'Use todos', milestone: '13.2' } + + field :todos, [::Types::TodoType], + null: false, + description: 'Updated todos' def resolve(ids:) check_update_amount_limit!(ids) @@ -24,6 +29,7 @@ module Mutations { updated_ids: gids_of(updated_ids), + todos: Todo.id_in(updated_ids), errors: errors_on_objects(todos) } end diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb index 7daff68c069..791c6eab42f 100644 --- a/app/graphql/resolvers/base_resolver.rb +++ b/app/graphql/resolvers/base_resolver.rb @@ -83,5 +83,10 @@ module Resolvers def current_user context[:current_user] end + + # Overridden in sub-classes (see .single, .last) + def select_result(results) + results + end end end diff --git a/app/graphql/resolvers/ci_configuration/sast_resolver.rb b/app/graphql/resolvers/ci_configuration/sast_resolver.rb new file mode 100644 index 00000000000..e8c42076ea2 --- /dev/null +++ b/app/graphql/resolvers/ci_configuration/sast_resolver.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "json" + +module Resolvers + module CiConfiguration + class SastResolver < BaseResolver + SAST_UI_SCHEMA_PATH = 'app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json' + + type ::Types::CiConfiguration::Sast::Type, null: true + + def resolve(**args) + Gitlab::Json.parse(File.read(Rails.root.join(SAST_UI_SCHEMA_PATH))) + end + end + end +end diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb index a2140728a27..7ed88be52b9 100644 --- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb +++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb @@ -11,16 +11,10 @@ module ResolvesMergeRequests end def resolve_with_lookahead(**args) - args[:iids] = Array.wrap(args[:iids]) if args[:iids] - args.compact! + mr_finder = MergeRequestsFinder.new(current_user, args.compact) + finder = Gitlab::Graphql::Loaders::IssuableLoader.new(project, mr_finder) - if project && args.keys == [:iids] - batch_load_merge_requests(args[:iids]) - else - args[:project_id] ||= project - - apply_lookahead(MergeRequestsFinder.new(current_user, args).execute) - end.then(&(single? ? :first : :itself)) + select_result(finder.batching_find_all { |query| apply_lookahead(query) }) end def ready?(**args) @@ -35,22 +29,6 @@ module ResolvesMergeRequests private - def batch_load_merge_requests(iids) - iids.map { |iid| batch_load(iid) }.select(&:itself) # .compact doesn't work on BatchLoader - end - - # rubocop: disable CodeReuse/ActiveRecord - def batch_load(iid) - BatchLoader::GraphQL.for(iid.to_s).batch(key: project) do |iids, loader, args| - query = args[:key].merge_requests.where(iid: iids) - - apply_lookahead(query).each do |mr| - loader.call(mr.iid.to_s, mr) - end - end - end - # rubocop: enable CodeReuse/ActiveRecord - def unconditional_includes [:target_project] end diff --git a/app/graphql/resolvers/environments_resolver.rb b/app/graphql/resolvers/environments_resolver.rb index 4e9a17f1e17..1b916a89796 100644 --- a/app/graphql/resolvers/environments_resolver.rb +++ b/app/graphql/resolvers/environments_resolver.rb @@ -8,7 +8,7 @@ module Resolvers argument :search, GraphQL::STRING_TYPE, required: false, - description: 'Search query' + description: 'Search query for environment name' argument :states, [GraphQL::STRING_TYPE], required: false, diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb index f103da07666..9d0535a208f 100644 --- a/app/graphql/resolvers/issues_resolver.rb +++ b/app/graphql/resolvers/issues_resolver.rb @@ -44,7 +44,7 @@ module Resolvers description: 'Issues closed after this date' argument :search, GraphQL::STRING_TYPE, required: false, - description: 'Search query for finding issues by title or description' + description: 'Search query for issue title or description' argument :sort, Types::IssueSortEnum, description: 'Sort issues by this criteria', required: false, @@ -63,18 +63,13 @@ module Resolvers parent = object.respond_to?(:sync) ? object.sync : object return Issue.none if parent.nil? - if parent.is_a?(Group) - args[:group_id] = parent.id - else - args[:project_id] = parent.id - end - # Will need to be be made group & namespace aware with # https://gitlab.com/gitlab-org/gitlab-foss/issues/54520 - args[:iids] ||= [args[:iid]].compact - args[:attempt_project_search_optimizations] = args[:search].present? + args[:iids] ||= [args.delete(:iid)].compact if args[:iid] + args[:attempt_project_search_optimizations] = true if args[:search].present? - issues = IssuesFinder.new(context[:current_user], args).execute + finder = IssuesFinder.new(current_user, args) + issues = Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all if non_stable_cursor_sort?(args[:sort]) # Certain complex sorts are not supported by the stable cursor pagination yet. @@ -97,3 +92,5 @@ module Resolvers end end end + +Resolvers::IssuesResolver.prepend_if_ee('::EE::Resolvers::IssuesResolver') diff --git a/app/graphql/resolvers/last_commit_resolver.rb b/app/graphql/resolvers/last_commit_resolver.rb index 7a433d6556f..dd89c322617 100644 --- a/app/graphql/resolvers/last_commit_resolver.rb +++ b/app/graphql/resolvers/last_commit_resolver.rb @@ -9,7 +9,7 @@ module Resolvers def resolve(**args) # Ensure merge commits can be returned by sending nil to Gitaly instead of '/' path = tree.path == '/' ? nil : tree.path - commit = Gitlab::Git::Commit.last_for_path(tree.repository, tree.sha, path) + commit = Gitlab::Git::Commit.last_for_path(tree.repository, tree.sha, path, literal_pathspec: true) ::Commit.new(commit, tree.repository.project) if commit end diff --git a/app/graphql/resolvers/milestone_resolver.rb b/app/graphql/resolvers/milestone_resolver.rb index 6c6513e0ee4..bcfbc63c31f 100644 --- a/app/graphql/resolvers/milestone_resolver.rb +++ b/app/graphql/resolvers/milestone_resolver.rb @@ -52,7 +52,7 @@ module Resolvers end def group_parameters(args) - return { group_ids: parent.id } unless include_descendants?(args) + return { group_ids: parent.id } unless args[:include_descendants].present? { group_ids: parent.self_and_descendants.public_or_visible_to_user(current_user).select(:id), @@ -60,10 +60,6 @@ module Resolvers } end - def include_descendants?(args) - args[:include_descendants].present? && Feature.enabled?(:group_milestone_descendants, parent) - end - def group_projects GroupProjectsFinder.new( group: parent, diff --git a/app/graphql/resolvers/packages_resolver.rb b/app/graphql/resolvers/packages_resolver.rb new file mode 100644 index 00000000000..519fb87183e --- /dev/null +++ b/app/graphql/resolvers/packages_resolver.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Resolvers + class PackagesResolver < BaseResolver + type Types::PackageType, null: true + + def resolve(**args) + return unless packages_available? + + ::Packages::PackagesFinder.new(object).execute + end + + private + + def packages_available? + ::Gitlab.config.packages.enabled + end + end +end diff --git a/app/graphql/resolvers/projects/jira_projects_resolver.rb b/app/graphql/resolvers/projects/jira_projects_resolver.rb index a8c3768df41..2dc712128cc 100644 --- a/app/graphql/resolvers/projects/jira_projects_resolver.rb +++ b/app/graphql/resolvers/projects/jira_projects_resolver.rb @@ -13,11 +13,10 @@ module Resolvers def resolve(name: nil, **args) authorize!(project) - response, start_cursor, end_cursor = jira_projects(name: name, **compute_pagination_params(args)) - end_cursor = nil if !!response.payload[:is_last] + response = jira_projects(name: name) if response.success? - Gitlab::Graphql::ExternallyPaginatedArray.new(start_cursor, end_cursor, *response.payload[:projects]) + response.payload[:projects] else raise Gitlab::Graphql::Errors::BaseError, response.message end @@ -35,41 +34,10 @@ module Resolvers jira_service&.project end - def compute_pagination_params(params) - after_cursor = Base64.decode64(params[:after].to_s) - before_cursor = Base64.decode64(params[:before].to_s) + def jira_projects(name:) + args = { query: name }.compact - # differentiate between 0 cursor and nil or invalid cursor that decodes into zero. - after_index = after_cursor.to_i == 0 && after_cursor != "0" ? nil : after_cursor.to_i - before_index = before_cursor.to_i == 0 && before_cursor != "0" ? nil : before_cursor.to_i - - if after_index.present? && before_index.present? - if after_index >= before_index - { start_at: 0, limit: 0 } - else - { start_at: after_index + 1, limit: before_index - after_index - 1 } - end - elsif after_index.present? - { start_at: after_index + 1, limit: nil } - elsif before_index.present? - { start_at: 0, limit: before_index - 1 } - else - { start_at: 0, limit: nil } - end - end - - def jira_projects(name:, start_at:, limit:) - args = { query: name, start_at: start_at, limit: limit }.compact - - response = Jira::Requests::Projects.new(project.jira_service, args).execute - - return [response, nil, nil] if response.error? - - projects = response.payload[:projects] - start_cursor = start_at == 0 ? nil : Base64.encode64((start_at - 1).to_s) - end_cursor = Base64.encode64((start_at + projects.size - 1).to_s) - - [response, start_cursor, end_cursor] + Jira::Requests::Projects::ListService.new(project.jira_service, args).execute end end end diff --git a/app/graphql/resolvers/projects_resolver.rb b/app/graphql/resolvers/projects_resolver.rb index 068546cd39f..f75f591b381 100644 --- a/app/graphql/resolvers/projects_resolver.rb +++ b/app/graphql/resolvers/projects_resolver.rb @@ -10,7 +10,7 @@ module Resolvers argument :search, GraphQL::STRING_TYPE, required: false, - description: 'Search criteria' + description: 'Search query for project name, path, or description' def resolve(**args) ProjectsFinder diff --git a/app/graphql/resolvers/release_resolver.rb b/app/graphql/resolvers/release_resolver.rb index 9bae8b8cd13..1edcc8c70b5 100644 --- a/app/graphql/resolvers/release_resolver.rb +++ b/app/graphql/resolvers/release_resolver.rb @@ -15,6 +15,8 @@ module Resolvers end def resolve(tag_name:) + return unless Feature.enabled?(:graphql_release_data, project, default_enabled: true) + ReleasesFinder.new( project, current_user, diff --git a/app/graphql/resolvers/releases_resolver.rb b/app/graphql/resolvers/releases_resolver.rb index b2afbb92684..85892c2abeb 100644 --- a/app/graphql/resolvers/releases_resolver.rb +++ b/app/graphql/resolvers/releases_resolver.rb @@ -12,6 +12,8 @@ module Resolvers end def resolve(**args) + return unless Feature.enabled?(:graphql_release_data, project, default_enabled: true) + ReleasesFinder.new( project, current_user diff --git a/app/graphql/types/alert_management/alert_sort_enum.rb b/app/graphql/types/alert_management/alert_sort_enum.rb index 3faac9ce53c..51e7bef0a7f 100644 --- a/app/graphql/types/alert_management/alert_sort_enum.rb +++ b/app/graphql/types/alert_management/alert_sort_enum.rb @@ -16,10 +16,10 @@ module Types value 'UPDATED_TIME_DESC', 'Created time by descending order', value: :updated_at_desc value 'EVENT_COUNT_ASC', 'Events count by ascending order', value: :event_count_asc value 'EVENT_COUNT_DESC', 'Events count by descending order', value: :event_count_desc - value 'SEVERITY_ASC', 'Severity by ascending order', value: :severity_asc - value 'SEVERITY_DESC', 'Severity by descending order', value: :severity_desc - value 'STATUS_ASC', 'Status by ascending order', value: :status_asc - value 'STATUS_DESC', 'Status by descending order', value: :status_desc + value 'SEVERITY_ASC', 'Severity from less critical to more critical', value: :severity_asc + value 'SEVERITY_DESC', 'Severity from more critical to less critical', value: :severity_desc + value 'STATUS_ASC', 'Status by order: Ignored > Resolved > Acknowledged > Triggered', value: :status_asc + value 'STATUS_DESC', 'Status by order: Triggered > Acknowledged > Resolved > Ignored', value: :status_desc end end end diff --git a/app/graphql/types/alert_management/alert_type.rb b/app/graphql/types/alert_management/alert_type.rb index 8215ccb152c..089d2426158 100644 --- a/app/graphql/types/alert_management/alert_type.rb +++ b/app/graphql/types/alert_management/alert_type.rb @@ -91,6 +91,12 @@ module Types null: true, description: 'Assignees of the alert' + field :metrics_dashboard_url, + GraphQL::STRING_TYPE, + null: true, + description: 'URL for metrics embed for the alert', + resolve: -> (alert, _args, _context) { alert.present.metrics_dashboard_url } + def notes object.ordered_notes end diff --git a/app/graphql/types/ci_configuration/sast/analyzers_entity_type.rb b/app/graphql/types/ci_configuration/sast/analyzers_entity_type.rb new file mode 100644 index 00000000000..ccd1c7dd0eb --- /dev/null +++ b/app/graphql/types/ci_configuration/sast/analyzers_entity_type.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Types + module CiConfiguration + module Sast + # rubocop: disable Graphql/AuthorizeTypes + class AnalyzersEntityType < BaseObject + graphql_name 'SastCiConfigurationAnalyzersEntity' + description 'Represents an analyzer entity in SAST CI configuration' + + field :name, GraphQL::STRING_TYPE, null: true, + description: 'Name of the analyzer.' + + field :label, GraphQL::STRING_TYPE, null: true, + description: 'Analyzer label used in the config UI.' + + field :enabled, GraphQL::BOOLEAN_TYPE, null: true, + description: 'Indicates whether an analyzer is enabled.' + + field :description, GraphQL::STRING_TYPE, null: true, + description: 'Analyzer description that is displayed on the form.' + end + end + end +end diff --git a/app/graphql/types/ci_configuration/sast/entity_type.rb b/app/graphql/types/ci_configuration/sast/entity_type.rb new file mode 100644 index 00000000000..b61b582ad20 --- /dev/null +++ b/app/graphql/types/ci_configuration/sast/entity_type.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Types + module CiConfiguration + module Sast + # rubocop: disable Graphql/AuthorizeTypes + class EntityType < BaseObject + graphql_name 'SastCiConfigurationEntity' + description 'Represents an entity in SAST CI configuration' + + field :field, GraphQL::STRING_TYPE, null: true, + description: 'CI keyword of entity.' + + field :label, GraphQL::STRING_TYPE, null: true, + description: 'Label for entity used in the form.' + + field :type, GraphQL::STRING_TYPE, null: true, + description: 'Type of the field value.' + + field :options, ::Types::CiConfiguration::Sast::OptionsEntityType.connection_type, null: true, + description: 'Different possible values of the field.' + + field :default_value, GraphQL::STRING_TYPE, null: true, + description: 'Default value that is used if value is empty.' + + field :description, GraphQL::STRING_TYPE, null: true, + description: 'Entity description that is displayed on the form.' + + field :value, GraphQL::STRING_TYPE, null: true, + description: 'Current value of the entity.' + end + end + end +end diff --git a/app/graphql/types/ci_configuration/sast/options_entity_type.rb b/app/graphql/types/ci_configuration/sast/options_entity_type.rb new file mode 100644 index 00000000000..86d104a7fda --- /dev/null +++ b/app/graphql/types/ci_configuration/sast/options_entity_type.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Types + module CiConfiguration + module Sast + # rubocop: disable Graphql/AuthorizeTypes + class OptionsEntityType < BaseObject + graphql_name 'SastCiConfigurationOptionsEntity' + description 'Represents an entity for options in SAST CI configuration' + + field :label, GraphQL::STRING_TYPE, null: true, + description: 'Label of option entity.' + + field :value, GraphQL::STRING_TYPE, null: true, + description: 'Value of option entity.' + end + end + end +end diff --git a/app/graphql/types/ci_configuration/sast/type.rb b/app/graphql/types/ci_configuration/sast/type.rb new file mode 100644 index 00000000000..35d11584ac7 --- /dev/null +++ b/app/graphql/types/ci_configuration/sast/type.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Types + module CiConfiguration + module Sast + # rubocop: disable Graphql/AuthorizeTypes + class Type < BaseObject + graphql_name 'SastCiConfiguration' + description 'Represents a CI configuration of SAST' + + field :global, ::Types::CiConfiguration::Sast::EntityType.connection_type, null: true, + description: 'List of global entities related to SAST configuration.' + + field :pipeline, ::Types::CiConfiguration::Sast::EntityType.connection_type, null: true, + description: 'List of pipeline entities related to SAST configuration.' + + field :analyzers, ::Types::CiConfiguration::Sast::AnalyzersEntityType.connection_type, null: true, + description: 'List of analyzers entities attached to SAST configuration.' + end + end + end +end diff --git a/app/graphql/types/container_expiration_policy_type.rb b/app/graphql/types/container_expiration_policy_type.rb index da53dbcbd39..f19aa964377 100644 --- a/app/graphql/types/container_expiration_policy_type.rb +++ b/app/graphql/types/container_expiration_policy_type.rb @@ -14,8 +14,8 @@ module Types field :older_than, Types::ContainerExpirationPolicyOlderThanEnum, null: true, description: 'Tags older that this will expire' field :cadence, Types::ContainerExpirationPolicyCadenceEnum, null: false, description: 'This container expiration policy schedule' field :keep_n, Types::ContainerExpirationPolicyKeepEnum, null: true, description: 'Number of tags to retain' - field :name_regex, GraphQL::STRING_TYPE, null: true, description: 'Tags with names matching this regex pattern will expire' - field :name_regex_keep, GraphQL::STRING_TYPE, null: true, description: 'Tags with names matching this regex pattern will be preserved' + field :name_regex, Types::UntrustedRegexp, null: true, description: 'Tags with names matching this regex pattern will expire' + field :name_regex_keep, Types::UntrustedRegexp, null: true, description: 'Tags with names matching this regex pattern will be preserved' field :next_run_at, Types::TimeType, null: true, description: 'Next time that this container expiration policy will get executed' end end diff --git a/app/graphql/types/deprecated_mutations.rb b/app/graphql/types/deprecated_mutations.rb new file mode 100644 index 00000000000..a4336fa3ef3 --- /dev/null +++ b/app/graphql/types/deprecated_mutations.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Types + module DeprecatedMutations + extend ActiveSupport::Concern + + prepended do + mount_aliased_mutation 'AddAwardEmoji', + Mutations::AwardEmojis::Add, + deprecated: { reason: 'Use awardEmojiAdd', milestone: '13.2' } + mount_aliased_mutation 'RemoveAwardEmoji', + Mutations::AwardEmojis::Remove, + deprecated: { reason: 'Use awardEmojiRemove', milestone: '13.2' } + mount_aliased_mutation 'ToggleAwardEmoji', + Mutations::AwardEmojis::Toggle, + deprecated: { reason: 'Use awardEmojiToggle', milestone: '13.2' } + end + end +end diff --git a/app/graphql/types/diff_stats_summary_type.rb b/app/graphql/types/diff_stats_summary_type.rb new file mode 100644 index 00000000000..956400fd21b --- /dev/null +++ b/app/graphql/types/diff_stats_summary_type.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Types + # rubocop: disable Graphql/AuthorizeTypes + # Types that use DiffStatsType should have their own authorization + class DiffStatsSummaryType < BaseObject + graphql_name 'DiffStatsSummary' + + description 'Aggregated summary of changes' + + field :additions, GraphQL::INT_TYPE, null: false, + description: 'Number of lines added' + field :deletions, GraphQL::INT_TYPE, null: false, + description: 'Number of lines deleted' + field :changes, GraphQL::INT_TYPE, null: false, + description: 'Number of lines changed' + field :file_count, GraphQL::INT_TYPE, null: false, + description: 'Number of files changed' + + def changes + object[:additions] + object[:deletions] + end + end + # rubocop: enable Graphql/AuthorizeTypes +end diff --git a/app/graphql/types/diff_stats_type.rb b/app/graphql/types/diff_stats_type.rb new file mode 100644 index 00000000000..6c79a4c389d --- /dev/null +++ b/app/graphql/types/diff_stats_type.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Types + # rubocop: disable Graphql/AuthorizeTypes + # Types that use DiffStatsType should have their own authorization + class DiffStatsType < BaseObject + graphql_name 'DiffStats' + + description 'Changes to a single file' + + field :path, GraphQL::STRING_TYPE, null: false, + description: 'File path, relative to repository root' + field :additions, GraphQL::INT_TYPE, null: false, + description: 'Number of lines added to this file' + field :deletions, GraphQL::INT_TYPE, null: false, + description: 'Number of lines deleted from this file' + end + # rubocop: enable Graphql/AuthorizeTypes +end diff --git a/app/graphql/types/error_tracking/sentry_detailed_error_type.rb b/app/graphql/types/error_tracking/sentry_detailed_error_type.rb index 124398f28e7..8bdd8afcbff 100644 --- a/app/graphql/types/error_tracking/sentry_detailed_error_type.rb +++ b/app/graphql/types/error_tracking/sentry_detailed_error_type.rb @@ -76,9 +76,15 @@ module Types description: 'Commit the error was last seen' field :first_release_short_version, GraphQL::STRING_TYPE, null: true, - description: 'Release version the error was first seen' + description: 'Release short version the error was first seen' field :last_release_short_version, GraphQL::STRING_TYPE, null: true, + description: 'Release short version the error was last seen' + field :first_release_version, GraphQL::STRING_TYPE, + null: true, + description: 'Release version the error was first seen' + field :last_release_version, GraphQL::STRING_TYPE, + null: true, description: 'Release version the error was last seen' field :gitlab_commit, GraphQL::STRING_TYPE, null: true, diff --git a/app/graphql/types/error_tracking/sentry_error_collection_type.rb b/app/graphql/types/error_tracking/sentry_error_collection_type.rb index 121146133cb..f423fcb1b9f 100644 --- a/app/graphql/types/error_tracking/sentry_error_collection_type.rb +++ b/app/graphql/types/error_tracking/sentry_error_collection_type.rb @@ -17,7 +17,7 @@ module Types resolver: Resolvers::ErrorTracking::SentryErrorsResolver do argument :search_term, String, - description: 'Search term for the Sentry error.', + description: 'Search query for the Sentry error details', required: false argument :sort, String, diff --git a/app/graphql/types/global_id_type.rb b/app/graphql/types/global_id_type.rb new file mode 100644 index 00000000000..a3964ba83e1 --- /dev/null +++ b/app/graphql/types/global_id_type.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module Types + class GlobalIDType < BaseScalar + graphql_name 'GlobalID' + description 'A global identifier' + + # @param value [GID] + # @return [String] + def self.coerce_result(value, _ctx) + ::Gitlab::GlobalId.as_global_id(value).to_s + end + + # @param value [String] + # @return [GID] + def self.coerce_input(value, _ctx) + gid = GlobalID.parse(value) + raise GraphQL::CoercionError, "#{value.inspect} is not a valid Global ID" if gid.nil? + raise GraphQL::CoercionError, "#{value.inspect} is not a Gitlab Global ID" unless gid.app == GlobalID.app + + gid + end + + # Construct a restricted type, that can only be inhabited by an ID of + # a given model class. + def self.[](model_class) + @id_types ||= {} + + @id_types[model_class] ||= Class.new(self) do + graphql_name "#{model_class.name.gsub(/::/, '')}ID" + description "Identifier of #{model_class.name}" + + self.define_singleton_method(:to_s) do + graphql_name + end + + self.define_singleton_method(:inspect) do + graphql_name + end + + self.define_singleton_method(:coerce_result) do |gid, ctx| + global_id = ::Gitlab::GlobalId.as_global_id(gid, model_name: model_class.name) + + if suitable?(global_id) + global_id.to_s + else + raise GraphQL::CoercionError, "Expected a #{model_class.name} ID, got #{global_id}" + end + end + + self.define_singleton_method(:suitable?) do |gid| + gid&.model_class&.ancestors&.include?(model_class) + end + + self.define_singleton_method(:coerce_input) do |string, ctx| + gid = super(string, ctx) + raise GraphQL::CoercionError, "#{string.inspect} does not represent an instance of #{model_class.name}" unless suitable?(gid) + + gid + end + end + end + end +end diff --git a/app/graphql/types/issue_connection_type.rb b/app/graphql/types/issue_connection_type.rb new file mode 100644 index 00000000000..beed392f01a --- /dev/null +++ b/app/graphql/types/issue_connection_type.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Types + # rubocop: disable Graphql/AuthorizeTypes + class IssueConnectionType < GraphQL::Types::Relay::BaseConnection + field :count, Integer, null: false, + description: 'Total count of collection' + + def count + object.items.size + end + end +end diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index 73219ca9e1e..9baa0018999 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -4,6 +4,8 @@ module Types class IssueType < BaseObject graphql_name 'Issue' + connection_type_class(Types::IssueConnectionType) + implements(Types::Notes::NoteableType) authorize :read_issue @@ -12,6 +14,8 @@ module Types present_using IssuePresenter + field :id, GraphQL::ID_TYPE, null: false, + description: "ID of the issue" field :iid, GraphQL::ID_TYPE, null: false, description: "Internal ID of the issue" field :title, GraphQL::STRING_TYPE, null: false, diff --git a/app/graphql/types/jira_user_type.rb b/app/graphql/types/jira_user_type.rb index 8aa21ce669b..999526a920e 100644 --- a/app/graphql/types/jira_user_type.rb +++ b/app/graphql/types/jira_user_type.rb @@ -13,7 +13,11 @@ module Types field :jira_email, GraphQL::STRING_TYPE, null: true, description: 'Email of the Jira user, returned only for users with public emails' field :gitlab_id, GraphQL::INT_TYPE, null: true, - description: 'Id of the matched GitLab user' + description: 'ID of the matched GitLab user' + field :gitlab_username, GraphQL::STRING_TYPE, null: true, + description: 'Username of the matched GitLab user' + field :gitlab_name, GraphQL::STRING_TYPE, null: true, + description: 'Name of the matched GitLab user' end # rubocop: enable Graphql/AuthorizeTypes end diff --git a/app/graphql/types/jira_users_mapping_input_type.rb b/app/graphql/types/jira_users_mapping_input_type.rb new file mode 100644 index 00000000000..61cf1474493 --- /dev/null +++ b/app/graphql/types/jira_users_mapping_input_type.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Types + # rubocop: disable Graphql/AuthorizeTypes + class JiraUsersMappingInputType < BaseInputObject + graphql_name 'JiraUsersMappingInputType' + + argument :jira_account_id, + 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' + end + # rubocop: enable Graphql/AuthorizeTypes +end diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index cb4ff7ea0c5..c194b467363 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -54,6 +54,13 @@ module Types description: 'Indicates if the merge has been set to be merged when its pipeline succeeds (MWPS)' field :diff_head_sha, GraphQL::STRING_TYPE, null: true, description: 'Diff head SHA of the merge request' + field :diff_stats, [Types::DiffStatsType], null: true, calls_gitaly: true, + description: 'Details about which files were changed in this merge request' do + argument :path, GraphQL::STRING_TYPE, required: false, description: 'A specific file-path' + end + + field :diff_stats_summary, Types::DiffStatsSummaryType, null: true, calls_gitaly: true, + description: 'Summary of which files were changed in this merge request' field :merge_commit_sha, GraphQL::STRING_TYPE, null: true, description: 'SHA of the merge request commit (set once merged)' field :user_notes_count, GraphQL::INT_TYPE, null: true, @@ -134,5 +141,24 @@ module Types end field :task_completion_status, Types::TaskCompletionStatus, null: false, description: Types::TaskCompletionStatus.description + + def diff_stats(path: nil) + stats = Array.wrap(object.diff_stats&.to_a) + + if path.present? + stats.select { |s| s.path == path } + else + stats + end + end + + def diff_stats_summary + nil_stats = { additions: 0, deletions: 0, file_count: 0 } + return nil_stats unless object.diff_stats.present? + + object.diff_stats.each_with_object(nil_stats) do |status, hash| + hash.merge!(additions: status.additions, deletions: status.deletions, file_count: 1) { |_, x, y| x + y } + end + end end end diff --git a/app/graphql/types/milestone_stats_type.rb b/app/graphql/types/milestone_stats_type.rb new file mode 100644 index 00000000000..ef533af59e7 --- /dev/null +++ b/app/graphql/types/milestone_stats_type.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Types + class MilestoneStatsType < BaseObject + graphql_name 'MilestoneStats' + description 'Contains statistics about a milestone' + + authorize :read_milestone + + field :total_issues_count, GraphQL::INT_TYPE, null: true, + description: 'Total number of issues associated with the milestone' + + field :closed_issues_count, GraphQL::INT_TYPE, null: true, + description: 'Number of closed issues associated with the milestone' + end +end diff --git a/app/graphql/types/milestone_type.rb b/app/graphql/types/milestone_type.rb index 99bd6e819d6..ca606c9da44 100644 --- a/app/graphql/types/milestone_type.rb +++ b/app/graphql/types/milestone_type.rb @@ -9,6 +9,8 @@ module Types authorize :read_milestone + alias_method :milestone, :object + field :id, GraphQL::ID_TYPE, null: false, description: 'ID of the milestone' @@ -47,5 +49,14 @@ module Types field :subgroup_milestone, GraphQL::BOOLEAN_TYPE, null: false, description: 'Indicates if milestone is at subgroup level', method: :subgroup_milestone? + + field :stats, Types::MilestoneStatsType, null: true, + description: 'Milestone statistics' + + def stats + return unless Feature.enabled?(:graphql_milestone_stats, milestone.project || milestone.group, default_enabled: true) + + milestone + end end end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 8874c56dfdb..49d51b626b2 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -10,6 +10,7 @@ module Types mount_mutation Mutations::AlertManagement::CreateAlertIssue mount_mutation Mutations::AlertManagement::UpdateAlertStatus mount_mutation Mutations::AlertManagement::Alerts::SetAssignees + mount_mutation Mutations::AlertManagement::Alerts::Todo::Create mount_mutation Mutations::AwardEmojis::Add mount_mutation Mutations::AwardEmojis::Remove mount_mutation Mutations::AwardEmojis::Toggle @@ -17,9 +18,11 @@ module Types mount_mutation Mutations::Commits::Create, calls_gitaly: true mount_mutation Mutations::Discussions::ToggleResolve mount_mutation Mutations::Issues::SetConfidential + mount_mutation Mutations::Issues::SetLocked mount_mutation Mutations::Issues::SetDueDate mount_mutation Mutations::Issues::Update mount_mutation Mutations::MergeRequests::Create + mount_mutation Mutations::MergeRequests::Update mount_mutation Mutations::MergeRequests::SetLabels mount_mutation Mutations::MergeRequests::SetLocked mount_mutation Mutations::MergeRequests::SetMilestone @@ -56,4 +59,5 @@ module Types end end +::Types::MutationType.prepend(::Types::DeprecatedMutations) ::Types::MutationType.prepend_if_ee('::EE::Types::MutationType') diff --git a/app/graphql/types/namespace_type.rb b/app/graphql/types/namespace_type.rb index 1714284a5cf..fbdf049b755 100644 --- a/app/graphql/types/namespace_type.rb +++ b/app/graphql/types/namespace_type.rb @@ -38,3 +38,5 @@ module Types resolver: ::Resolvers::NamespaceProjectsResolver end end + +Types::NamespaceType.prepend_if_ee('EE::Types::NamespaceType') diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb index 8755b4ccad5..5d41f0032bd 100644 --- a/app/graphql/types/notes/note_type.rb +++ b/app/graphql/types/notes/note_type.rb @@ -27,6 +27,8 @@ module Types field :system, GraphQL::BOOLEAN_TYPE, null: false, description: 'Indicates whether this note was created by the system or by a user' + field :system_note_icon_name, GraphQL::STRING_TYPE, null: true, + description: 'Name of the icon corresponding to a system note' field :body, GraphQL::STRING_TYPE, null: false, @@ -46,6 +48,10 @@ module Types field :confidential, GraphQL::BOOLEAN_TYPE, null: true, description: 'Indicates if this note is confidential', method: :confidential? + + def system_note_icon_name + SystemNoteHelper.system_note_icon_name(object) if object.system? + end end end end diff --git a/app/graphql/types/package_type.rb b/app/graphql/types/package_type.rb new file mode 100644 index 00000000000..0604bf827a5 --- /dev/null +++ b/app/graphql/types/package_type.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Types + class PackageType < BaseObject + graphql_name 'Package' + description 'Represents a package' + authorize :read_package + + field :id, GraphQL::ID_TYPE, null: false, description: 'The ID of the package' + field :name, GraphQL::STRING_TYPE, null: false, description: 'The name of the package' + field :created_at, Types::TimeType, null: false, description: 'The created date' + field :updated_at, Types::TimeType, null: false, description: 'The update date' + field :version, GraphQL::STRING_TYPE, null: true, description: 'The version of the package' + field :package_type, Types::PackageTypeEnum, null: false, description: 'The type of the package' + end +end diff --git a/app/graphql/types/package_type_enum.rb b/app/graphql/types/package_type_enum.rb new file mode 100644 index 00000000000..bc03b8f5f8b --- /dev/null +++ b/app/graphql/types/package_type_enum.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Types + class PackageTypeEnum < BaseEnum + ::Packages::Package.package_types.keys.each do |package_type| + value package_type.to_s.upcase, "Packages from the #{package_type} package manager", value: package_type.to_s + end + end +end diff --git a/app/graphql/types/project_statistics_type.rb b/app/graphql/types/project_statistics_type.rb index e1546d31e89..b3916e42e92 100644 --- a/app/graphql/types/project_statistics_type.rb +++ b/app/graphql/types/project_statistics_type.rb @@ -21,5 +21,7 @@ module Types description: 'Packages size of the project' field :wiki_size, GraphQL::FLOAT_TYPE, null: true, description: 'Wiki size of the project' + field :snippets_size, GraphQL::FLOAT_TYPE, null: true, + description: 'Snippets size of the project' end end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index bbfb7fc4f20..2251a0f4e0c 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -60,6 +60,12 @@ module Types field :merge_requests_ff_only_enabled, GraphQL::BOOLEAN_TYPE, null: true, description: 'Indicates if no merge commits should be created and all merges should instead be fast-forwarded, which means that merging is only allowed if the branch could be fast-forwarded.' + field :service_desk_enabled, GraphQL::BOOLEAN_TYPE, null: true, + description: 'Indicates if the project has service desk enabled.' + + field :service_desk_address, GraphQL::STRING_TYPE, null: true, + description: 'E-mail address of the service desk.' + field :avatar_url, GraphQL::STRING_TYPE, null: true, calls_gitaly: true, description: 'URL to avatar image file of the project', resolve: -> (project, args, ctx) do @@ -153,12 +159,20 @@ module Types description: 'Environments of the project', resolver: Resolvers::EnvironmentsResolver + field :sast_ci_configuration, ::Types::CiConfiguration::Sast::Type, null: true, + description: 'SAST CI configuration for the project', + resolver: ::Resolvers::CiConfiguration::SastResolver + field :issue, Types::IssueType, null: true, description: 'A single issue of the project', resolver: Resolvers::IssuesResolver.single + field :packages, Types::PackageType.connection_type, null: true, + description: 'Packages of the project', + resolver: Resolvers::PackagesResolver + field :pipelines, Types::Ci::PipelineType.connection_type, null: true, @@ -243,15 +257,14 @@ module Types Types::ReleaseType.connection_type, null: true, description: 'Releases of the project', - resolver: Resolvers::ReleasesResolver, - feature_flag: :graphql_release_data + resolver: Resolvers::ReleasesResolver field :release, Types::ReleaseType, null: true, description: 'A single release of the project', resolver: Resolvers::ReleasesResolver.single, - feature_flag: :graphql_release_data + authorize: :download_code field :container_expiration_policy, Types::ContainerExpirationPolicyType, diff --git a/app/graphql/types/projects/services/jira_service_type.rb b/app/graphql/types/projects/services/jira_service_type.rb index e81963f752d..8bf85a14cbf 100644 --- a/app/graphql/types/projects/services/jira_service_type.rb +++ b/app/graphql/types/projects/services/jira_service_type.rb @@ -15,7 +15,7 @@ module Types null: true, connection: false, extensions: [Gitlab::Graphql::Extensions::ExternallyPaginatedArrayExtension], - description: 'List of Jira projects fetched through Jira REST API', + description: 'List of all Jira projects fetched through Jira REST API', resolver: Resolvers::Projects::JiraProjectsResolver end end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 362e4004b73..b4cbd96bfdb 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -61,10 +61,6 @@ module Types description: 'Text to echo back', resolver: Resolvers::EchoResolver - field :user, Types::UserType, null: true, - description: 'Find a user on this instance', - resolver: Resolvers::UserResolver - def design_management DesignManagementObject.new(nil) end diff --git a/app/graphql/types/release_link_type.rb b/app/graphql/types/release_asset_link_type.rb index 070f14a90df..21f1bd50cff 100644 --- a/app/graphql/types/release_link_type.rb +++ b/app/graphql/types/release_asset_link_type.rb @@ -1,8 +1,9 @@ # frozen_string_literal: true module Types - class ReleaseLinkType < BaseObject - graphql_name 'ReleaseLink' + class ReleaseAssetLinkType < BaseObject + graphql_name 'ReleaseAssetLink' + description 'Represents an asset link associated with a release' authorize :read_release @@ -12,7 +13,7 @@ module Types description: 'Name of the link' field :url, GraphQL::STRING_TYPE, null: true, description: 'URL of the link' - field :link_type, Types::ReleaseLinkTypeEnum, null: true, + field :link_type, Types::ReleaseAssetLinkTypeEnum, null: true, description: 'Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`' field :external, GraphQL::BOOLEAN_TYPE, null: true, method: :external?, description: 'Indicates the link points to an external resource' diff --git a/app/graphql/types/release_link_type_enum.rb b/app/graphql/types/release_asset_link_type_enum.rb index b364855833f..01862ada56d 100644 --- a/app/graphql/types/release_link_type_enum.rb +++ b/app/graphql/types/release_asset_link_type_enum.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module Types - class ReleaseLinkTypeEnum < BaseEnum - graphql_name 'ReleaseLinkType' + class ReleaseAssetLinkTypeEnum < BaseEnum + graphql_name 'ReleaseAssetLinkType' description 'Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`' ::Releases::Link.link_types.keys.each do |link_type| diff --git a/app/graphql/types/release_assets_type.rb b/app/graphql/types/release_assets_type.rb index 58ad05b5365..d6042bdbc0b 100644 --- a/app/graphql/types/release_assets_type.rb +++ b/app/graphql/types/release_assets_type.rb @@ -3,6 +3,7 @@ module Types class ReleaseAssetsType < BaseObject graphql_name 'ReleaseAssets' + description 'A container for all assets associated with a release' authorize :read_release @@ -10,9 +11,9 @@ module Types present_using ReleasePresenter - field :assets_count, GraphQL::INT_TYPE, null: true, + field :count, GraphQL::INT_TYPE, null: true, method: :assets_count, description: 'Number of assets of the release' - field :links, Types::ReleaseLinkType.connection_type, null: true, + field :links, Types::ReleaseAssetLinkType.connection_type, null: true, description: 'Asset links of the release' field :sources, Types::ReleaseSourceType.connection_type, null: true, description: 'Sources of the release' diff --git a/app/graphql/types/release_links_type.rb b/app/graphql/types/release_links_type.rb new file mode 100644 index 00000000000..f61a16f5b67 --- /dev/null +++ b/app/graphql/types/release_links_type.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Types + class ReleaseLinksType < BaseObject + graphql_name 'ReleaseLinks' + + authorize :download_code + + alias_method :release, :object + + present_using ReleasePresenter + + field :self_url, GraphQL::STRING_TYPE, null: true, + description: 'HTTP URL of the release' + field :merge_requests_url, GraphQL::STRING_TYPE, null: true, + description: 'HTTP URL of the merge request page filtered by this release' + field :issues_url, GraphQL::STRING_TYPE, null: true, + description: 'HTTP URL of the issues page filtered by this release' + field :edit_url, GraphQL::STRING_TYPE, null: true, + description: "HTTP URL of the release's edit page", + authorize: :update_release + end +end diff --git a/app/graphql/types/release_source_type.rb b/app/graphql/types/release_source_type.rb index 0ec1ad85a39..891da472116 100644 --- a/app/graphql/types/release_source_type.rb +++ b/app/graphql/types/release_source_type.rb @@ -3,8 +3,9 @@ module Types class ReleaseSourceType < BaseObject graphql_name 'ReleaseSource' + description 'Represents the source code attached to a release in a particular format' - authorize :read_release_sources + authorize :download_code field :format, GraphQL::STRING_TYPE, null: true, description: 'Format of the source' diff --git a/app/graphql/types/release_type.rb b/app/graphql/types/release_type.rb index 3d8e5a93c68..a0703b96a36 100644 --- a/app/graphql/types/release_type.rb +++ b/app/graphql/types/release_type.rb @@ -3,6 +3,7 @@ module Types class ReleaseType < BaseObject graphql_name 'Release' + description 'Represents a release' authorize :read_release @@ -10,10 +11,12 @@ module Types present_using ReleasePresenter - field :tag_name, GraphQL::STRING_TYPE, null: false, method: :tag, - description: 'Name of the tag associated with the release' + field :tag_name, GraphQL::STRING_TYPE, null: true, method: :tag, + description: 'Name of the tag associated with the release', + authorize: :download_code field :tag_path, GraphQL::STRING_TYPE, null: true, - description: 'Relative web path to the tag associated with the release' + description: 'Relative web path to the tag associated with the release', + authorize: :download_code field :description, GraphQL::STRING_TYPE, null: true, description: 'Description (also known as "release notes") of the release' markdown_field :description_html, null: true @@ -25,6 +28,8 @@ module Types description: 'Timestamp of when the release was released' field :assets, Types::ReleaseAssetsType, null: true, method: :itself, description: 'Assets of the release' + field :links, Types::ReleaseLinksType, null: true, method: :itself, + description: 'Links of the release' field :milestones, Types::MilestoneType.connection_type, null: true, description: 'Milestones associated to the release' field :evidences, Types::EvidenceType.connection_type, null: true, @@ -39,8 +44,7 @@ module Types field :commit, Types::CommitType, null: true, complexity: 10, calls_gitaly: true, - description: 'The commit associated with the release', - authorize: :reporter_access + description: 'The commit associated with the release' def commit return if release.sha.nil? diff --git a/app/graphql/types/root_storage_statistics_type.rb b/app/graphql/types/root_storage_statistics_type.rb index e2d85aebc48..3acc1d9ca44 100644 --- a/app/graphql/types/root_storage_statistics_type.rb +++ b/app/graphql/types/root_storage_statistics_type.rb @@ -12,5 +12,6 @@ module Types field :build_artifacts_size, GraphQL::FLOAT_TYPE, null: false, description: 'The CI artifacts size in bytes' field :packages_size, GraphQL::FLOAT_TYPE, null: false, description: 'The packages size in bytes' field :wiki_size, GraphQL::FLOAT_TYPE, null: false, description: 'The wiki size in bytes' + field :snippets_size, GraphQL::FLOAT_TYPE, null: false, description: 'The snippets size in bytes' end end diff --git a/app/graphql/types/todo_target_enum.rb b/app/graphql/types/todo_target_enum.rb index a377c3aafdc..b797722fef8 100644 --- a/app/graphql/types/todo_target_enum.rb +++ b/app/graphql/types/todo_target_enum.rb @@ -6,6 +6,7 @@ module Types value 'ISSUE', value: 'Issue', description: 'An Issue' value 'MERGEREQUEST', value: 'MergeRequest', description: 'A MergeRequest' value 'DESIGN', value: 'DesignManagement::Design', description: 'A Design' + value 'ALERT', value: 'AlertManagement::Alert', description: 'An Alert' end end diff --git a/app/graphql/types/tree/blob_type.rb b/app/graphql/types/tree/blob_type.rb index 22349203519..36cae756a0d 100644 --- a/app/graphql/types/tree/blob_type.rb +++ b/app/graphql/types/tree/blob_type.rb @@ -17,6 +17,8 @@ module Types resolve: -> (blob, args, ctx) do Gitlab::Graphql::Loaders::BatchLfsOidLoader.new(blob.repository, blob.id).find end + field :mode, GraphQL::STRING_TYPE, null: true, + description: 'Blob mode in numeric format' # rubocop: enable Graphql/AuthorizeTypes end end diff --git a/app/graphql/types/untrusted_regexp.rb b/app/graphql/types/untrusted_regexp.rb new file mode 100644 index 00000000000..2c715ab4967 --- /dev/null +++ b/app/graphql/types/untrusted_regexp.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Types + class UntrustedRegexp < Types::BaseScalar + description 'A regexp containing patterns sourced from user input' + + def self.coerce_input(input_value, _) + return unless input_value + + Gitlab::UntrustedRegexp.new(input_value) + + input_value + rescue RegexpError => e + message = "#{input_value} is an invalid regexp: #{e.message}" + raise GraphQL::CoercionError, message + end + + def self.coerce_result(ruby_value, _) + ruby_value.to_s + end + end +end |