summaryrefslogtreecommitdiff
path: root/app/graphql
diff options
context:
space:
mode:
Diffstat (limited to 'app/graphql')
-rw-r--r--app/graphql/mutations/issues/base.rb34
-rw-r--r--app/graphql/mutations/issues/set_confidential.rb27
-rw-r--r--app/graphql/mutations/issues/set_due_date.rb27
-rw-r--r--app/graphql/mutations/snippets/base.rb30
-rw-r--r--app/graphql/mutations/snippets/create.rb77
-rw-r--r--app/graphql/mutations/snippets/destroy.rb33
-rw-r--r--app/graphql/mutations/snippets/mark_as_spam.rb39
-rw-r--r--app/graphql/mutations/snippets/update.rb54
-rw-r--r--app/graphql/mutations/todos/base.rb6
-rw-r--r--app/graphql/mutations/todos/mark_all_done.rb35
-rw-r--r--app/graphql/mutations/todos/mark_done.rb7
-rw-r--r--app/graphql/mutations/todos/restore.rb36
-rw-r--r--app/graphql/resolvers/base_resolver.rb10
-rw-r--r--app/graphql/resolvers/concerns/resolves_snippets.rb57
-rw-r--r--app/graphql/resolvers/echo_resolver.rb4
-rw-r--r--app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb28
-rw-r--r--app/graphql/resolvers/issues_resolver.rb14
-rw-r--r--app/graphql/resolvers/projects/snippets_resolver.rb23
-rw-r--r--app/graphql/resolvers/snippets_resolver.rb45
-rw-r--r--app/graphql/resolvers/todo_resolver.rb50
-rw-r--r--app/graphql/resolvers/users/snippets_resolver.rb21
-rw-r--r--app/graphql/types/diff_refs_type.rb9
-rw-r--r--app/graphql/types/error_tracking/sentry_detailed_error_type.rb93
-rw-r--r--app/graphql/types/error_tracking/sentry_error_frequency_type.rb18
-rw-r--r--app/graphql/types/error_tracking/sentry_error_status_enum.rb15
-rw-r--r--app/graphql/types/issuable_sort_enum.rb2
-rw-r--r--app/graphql/types/issue_sort_enum.rb4
-rw-r--r--app/graphql/types/issue_state_enum.rb3
-rw-r--r--app/graphql/types/label_type.rb2
-rw-r--r--app/graphql/types/merge_request_state_enum.rb3
-rw-r--r--app/graphql/types/merge_request_type.rb2
-rw-r--r--app/graphql/types/mutation_type.rb8
-rw-r--r--app/graphql/types/notes/diff_position_type.rb24
-rw-r--r--app/graphql/types/notes/discussion_type.rb12
-rw-r--r--app/graphql/types/notes/note_type.rb32
-rw-r--r--app/graphql/types/notes/noteable_type.rb2
-rw-r--r--app/graphql/types/permission_types/project.rb8
-rw-r--r--app/graphql/types/permission_types/snippet.rb16
-rw-r--r--app/graphql/types/permission_types/user.rb15
-rw-r--r--app/graphql/types/project_type.rb14
-rw-r--r--app/graphql/types/query_type.rb10
-rw-r--r--app/graphql/types/root_storage_statistics_type.rb2
-rw-r--r--app/graphql/types/snippet_type.rb69
-rw-r--r--app/graphql/types/snippets/type_enum.rb10
-rw-r--r--app/graphql/types/snippets/visibility_scopes_enum.rb11
-rw-r--r--app/graphql/types/user_type.rb8
-rw-r--r--app/graphql/types/visibility_levels_enum.rb9
47 files changed, 958 insertions, 100 deletions
diff --git a/app/graphql/mutations/issues/base.rb b/app/graphql/mutations/issues/base.rb
new file mode 100644
index 00000000000..b7fa234a50b
--- /dev/null
+++ b/app/graphql/mutations/issues/base.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Issues
+ class Base < BaseMutation
+ include Mutations::ResolvesProject
+
+ argument :project_path, GraphQL::ID_TYPE,
+ required: true,
+ description: "The project the issue to mutate is in"
+
+ argument :iid, GraphQL::STRING_TYPE,
+ required: true,
+ description: "The iid of the issue to mutate"
+
+ field :issue,
+ Types::IssueType,
+ null: true,
+ description: "The issue after mutation"
+
+ authorize :update_issue
+
+ private
+
+ def find_object(project_path:, iid:)
+ project = resolve_project(full_path: project_path)
+ resolver = Resolvers::IssuesResolver
+ .single.new(object: project, context: context)
+
+ resolver.resolve(iid: iid)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/issues/set_confidential.rb b/app/graphql/mutations/issues/set_confidential.rb
new file mode 100644
index 00000000000..0fff5518665
--- /dev/null
+++ b/app/graphql/mutations/issues/set_confidential.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Issues
+ class SetConfidential < Base
+ graphql_name 'IssueSetConfidential'
+
+ argument :confidential,
+ GraphQL::BOOLEAN_TYPE,
+ required: true,
+ description: 'Whether or not to set the issue as a confidential.'
+
+ def resolve(project_path:, iid:, confidential:)
+ issue = authorized_find!(project_path: project_path, iid: iid)
+ project = issue.project
+
+ ::Issues::UpdateService.new(project, current_user, confidential: confidential)
+ .execute(issue)
+
+ {
+ issue: issue,
+ errors: issue.errors.full_messages
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/issues/set_due_date.rb b/app/graphql/mutations/issues/set_due_date.rb
new file mode 100644
index 00000000000..1855c6f053b
--- /dev/null
+++ b/app/graphql/mutations/issues/set_due_date.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Issues
+ class SetDueDate < Base
+ graphql_name 'IssueSetDueDate'
+
+ argument :due_date,
+ Types::TimeType,
+ required: true,
+ description: 'The desired due date for the issue'
+
+ def resolve(project_path:, iid:, due_date:)
+ issue = authorized_find!(project_path: project_path, iid: iid)
+ project = issue.project
+
+ ::Issues::UpdateService.new(project, current_user, due_date: due_date)
+ .execute(issue)
+
+ {
+ issue: issue,
+ errors: issue.errors.full_messages
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/snippets/base.rb b/app/graphql/mutations/snippets/base.rb
new file mode 100644
index 00000000000..9dc6d49774e
--- /dev/null
+++ b/app/graphql/mutations/snippets/base.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Snippets
+ class Base < BaseMutation
+ field :snippet,
+ Types::SnippetType,
+ null: true,
+ description: 'The snippet after mutation'
+
+ private
+
+ def find_object(id:)
+ GitlabSchema.object_from_id(id)
+ end
+
+ def authorized_resource?(snippet)
+ Ability.allowed?(context[:current_user], ability_for(snippet), snippet)
+ end
+
+ def ability_for(snippet)
+ "#{ability_name}_#{snippet.to_ability_name}".to_sym
+ end
+
+ def ability_name
+ raise NotImplementedError
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/snippets/create.rb b/app/graphql/mutations/snippets/create.rb
new file mode 100644
index 00000000000..fe1f543ea1a
--- /dev/null
+++ b/app/graphql/mutations/snippets/create.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Snippets
+ class Create < BaseMutation
+ include Mutations::ResolvesProject
+
+ graphql_name 'CreateSnippet'
+
+ field :snippet,
+ Types::SnippetType,
+ null: true,
+ description: 'The snippet after mutation'
+
+ argument :title, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Title of the snippet'
+
+ argument :file_name, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'File name of the snippet'
+
+ argument :content, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Content of the snippet'
+
+ argument :description, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Description of the snippet'
+
+ argument :visibility_level, Types::VisibilityLevelsEnum,
+ description: 'The visibility level of the snippet',
+ required: true
+
+ argument :project_path, GraphQL::ID_TYPE,
+ required: false,
+ description: 'The project full path the snippet is associated with'
+
+ def resolve(args)
+ project_path = args.delete(:project_path)
+
+ if project_path.present?
+ project = find_project!(project_path: project_path)
+ elsif !can_create_personal_snippet?
+ raise_resource_not_avaiable_error!
+ end
+
+ snippet = CreateSnippetService.new(project,
+ context[:current_user],
+ args).execute
+
+ {
+ snippet: snippet.valid? ? snippet : nil,
+ errors: errors_on_object(snippet)
+ }
+ end
+
+ private
+
+ def find_project!(project_path:)
+ authorized_find!(full_path: project_path)
+ end
+
+ def find_object(full_path:)
+ resolve_project(full_path: full_path)
+ end
+
+ def authorized_resource?(project)
+ Ability.allowed?(context[:current_user], :create_project_snippet, project)
+ end
+
+ def can_create_personal_snippet?
+ Ability.allowed?(context[:current_user], :create_personal_snippet)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/snippets/destroy.rb b/app/graphql/mutations/snippets/destroy.rb
new file mode 100644
index 00000000000..115fcfd6488
--- /dev/null
+++ b/app/graphql/mutations/snippets/destroy.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Snippets
+ class Destroy < Base
+ graphql_name 'DestroySnippet'
+
+ ERROR_MSG = 'Error deleting the snippet'
+
+ argument :id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the snippet to destroy'
+
+ def resolve(id:)
+ snippet = authorized_find!(id: id)
+
+ result = snippet.destroy
+ errors = result ? [] : [ERROR_MSG]
+
+ {
+ errors: errors
+ }
+ end
+
+ private
+
+ def ability_name
+ "admin"
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/snippets/mark_as_spam.rb b/app/graphql/mutations/snippets/mark_as_spam.rb
new file mode 100644
index 00000000000..260a9753f76
--- /dev/null
+++ b/app/graphql/mutations/snippets/mark_as_spam.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Snippets
+ class MarkAsSpam < Base
+ graphql_name 'MarkAsSpamSnippet'
+
+ argument :id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the snippet to update'
+
+ def resolve(id:)
+ snippet = authorized_find!(id: id)
+
+ result = mark_as_spam(snippet)
+ errors = result ? [] : ['Error with Akismet. Please check the logs for more info.']
+
+ {
+ errors: errors
+ }
+ end
+
+ private
+
+ def mark_as_spam(snippet)
+ SpamService.new(snippet).mark_as_spam!
+ end
+
+ def authorized_resource?(snippet)
+ super && snippet.submittable_as_spam_by?(context[:current_user])
+ end
+
+ def ability_name
+ "admin"
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/snippets/update.rb b/app/graphql/mutations/snippets/update.rb
new file mode 100644
index 00000000000..27c232bc7f8
--- /dev/null
+++ b/app/graphql/mutations/snippets/update.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Snippets
+ class Update < Base
+ graphql_name 'UpdateSnippet'
+
+ argument :id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the snippet to update'
+
+ argument :title, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Title of the snippet'
+
+ argument :file_name, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'File name of the snippet'
+
+ argument :content, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Content of the snippet'
+
+ argument :description, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Description of the snippet'
+
+ argument :visibility_level, Types::VisibilityLevelsEnum,
+ description: 'The visibility level of the snippet',
+ required: false
+
+ def resolve(args)
+ snippet = authorized_find!(id: args.delete(:id))
+
+ result = UpdateSnippetService.new(snippet.project,
+ context[:current_user],
+ snippet,
+ args).execute
+
+ {
+ snippet: result ? snippet : snippet.reset,
+ errors: errors_on_object(snippet)
+ }
+ end
+
+ private
+
+ def ability_name
+ "update"
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/todos/base.rb b/app/graphql/mutations/todos/base.rb
index b6c7b320be1..2a72019fbac 100644
--- a/app/graphql/mutations/todos/base.rb
+++ b/app/graphql/mutations/todos/base.rb
@@ -9,6 +9,12 @@ module Mutations
GitlabSchema.object_from_id(id)
end
+ def map_to_global_ids(ids)
+ return [] if ids.blank?
+
+ ids.map { |id| to_global_id(id) }
+ end
+
def to_global_id(id)
::URI::GID.build(app: GlobalID.app, model_name: Todo.name, model_id: id, params: nil).to_s
end
diff --git a/app/graphql/mutations/todos/mark_all_done.rb b/app/graphql/mutations/todos/mark_all_done.rb
new file mode 100644
index 00000000000..5694985717c
--- /dev/null
+++ b/app/graphql/mutations/todos/mark_all_done.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Todos
+ class MarkAllDone < ::Mutations::Todos::Base
+ graphql_name 'TodosMarkAllDone'
+
+ authorize :update_user
+
+ field :updated_ids,
+ [GraphQL::ID_TYPE],
+ null: false,
+ description: 'Ids of the updated todos'
+
+ def resolve
+ authorize!(current_user)
+
+ updated_ids = mark_all_todos_done
+
+ {
+ updated_ids: map_to_global_ids(updated_ids),
+ errors: []
+ }
+ end
+
+ private
+
+ def mark_all_todos_done
+ return [] unless current_user
+
+ TodoService.new.mark_all_todos_as_done_by_user(current_user)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/todos/mark_done.rb b/app/graphql/mutations/todos/mark_done.rb
index 5483708b5c6..d738e387c43 100644
--- a/app/graphql/mutations/todos/mark_done.rb
+++ b/app/graphql/mutations/todos/mark_done.rb
@@ -16,22 +16,21 @@ module Mutations
null: false,
description: 'The requested todo'
- # rubocop: disable CodeReuse/ActiveRecord
def resolve(id:)
todo = authorized_find!(id: id)
- mark_done(Todo.where(id: todo.id)) unless todo.done?
+
+ mark_done(todo)
{
todo: todo.reset,
errors: errors_on_object(todo)
}
end
- # rubocop: enable CodeReuse/ActiveRecord
private
def mark_done(todo)
- TodoService.new.mark_todos_as_done(todo, current_user)
+ TodoService.new.mark_todo_as_done(todo, current_user)
end
end
end
diff --git a/app/graphql/mutations/todos/restore.rb b/app/graphql/mutations/todos/restore.rb
new file mode 100644
index 00000000000..c4597bd84a2
--- /dev/null
+++ b/app/graphql/mutations/todos/restore.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Todos
+ class Restore < ::Mutations::Todos::Base
+ graphql_name 'TodoRestore'
+
+ authorize :update_todo
+
+ argument :id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the todo to restore'
+
+ field :todo, Types::TodoType,
+ null: false,
+ description: 'The requested todo'
+
+ def resolve(id:)
+ todo = authorized_find!(id: id)
+ restore(todo.id) if todo.done?
+
+ {
+ todo: todo.reset,
+ errors: errors_on_object(todo)
+ }
+ end
+
+ private
+
+ def restore(id)
+ TodoService.new.mark_todos_as_pending_by_ids([id], current_user)
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb
index 85d6b377934..62dcc41dd9c 100644
--- a/app/graphql/resolvers/base_resolver.rb
+++ b/app/graphql/resolvers/base_resolver.rb
@@ -2,6 +2,8 @@
module Resolvers
class BaseResolver < GraphQL::Schema::Resolver
+ extend ::Gitlab::Utils::Override
+
def self.single
@single ||= Class.new(self) do
def resolve(**args)
@@ -36,5 +38,13 @@ module Resolvers
# complexity difference is minimal in this case.
[args[:iid], args[:iids]].any? ? 0 : 0.01
end
+
+ override :object
+ def object
+ super.tap do |obj|
+ # If the field this resolver is used in is wrapped in a presenter, go back to it's subject
+ break obj.subject if obj.is_a?(Gitlab::View::Presenter::Base)
+ end
+ end
end
end
diff --git a/app/graphql/resolvers/concerns/resolves_snippets.rb b/app/graphql/resolvers/concerns/resolves_snippets.rb
new file mode 100644
index 00000000000..483372bbf63
--- /dev/null
+++ b/app/graphql/resolvers/concerns/resolves_snippets.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module ResolvesSnippets
+ extend ActiveSupport::Concern
+
+ included do
+ type Types::SnippetType, null: false
+
+ argument :ids, [GraphQL::ID_TYPE],
+ required: false,
+ description: 'Array of global snippet ids, e.g., "gid://gitlab/ProjectSnippet/1"'
+
+ argument :visibility, Types::Snippets::VisibilityScopesEnum,
+ required: false,
+ description: 'The visibility of the snippet'
+ end
+
+ def resolve(**args)
+ resolve_snippets(args)
+ end
+
+ private
+
+ def resolve_snippets(args)
+ SnippetsFinder.new(context[:current_user], snippet_finder_params(args)).execute
+ end
+
+ def snippet_finder_params(args)
+ {
+ ids: resolve_ids(args[:ids]),
+ scope: args[:visibility]
+ }.merge(options_by_type(args[:type]))
+ end
+
+ def resolve_ids(ids)
+ Array.wrap(ids).map { |id| resolve_gid(id, :id) }
+ end
+
+ def resolve_gid(gid, argument)
+ return unless gid.present?
+
+ GlobalID.parse(gid)&.model_id.tap do |id|
+ raise Gitlab::Graphql::Errors::ArgumentError, "Invalid global id format for param #{argument}" if id.nil?
+ end
+ end
+
+ def options_by_type(type)
+ case type
+ when 'personal'
+ { only_personal: true }
+ when 'project'
+ { only_project: true }
+ else
+ {}
+ end
+ end
+end
diff --git a/app/graphql/resolvers/echo_resolver.rb b/app/graphql/resolvers/echo_resolver.rb
index 2ce55544254..fe0b1893a23 100644
--- a/app/graphql/resolvers/echo_resolver.rb
+++ b/app/graphql/resolvers/echo_resolver.rb
@@ -2,9 +2,11 @@
module Resolvers
class EchoResolver < BaseResolver
- argument :text, GraphQL::STRING_TYPE, required: true # rubocop:disable Graphql/Descriptions
description 'Testing endpoint to validate the API with'
+ argument :text, GraphQL::STRING_TYPE, required: true,
+ description: 'Text to echo back'
+
def resolve(**args)
username = context[:current_user]&.username
diff --git a/app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb b/app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb
new file mode 100644
index 00000000000..63455ff3acb
--- /dev/null
+++ b/app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module ErrorTracking
+ class SentryDetailedErrorResolver < BaseResolver
+ argument :id, GraphQL::ID_TYPE,
+ required: true,
+ description: 'ID of the Sentry issue'
+
+ def resolve(**args)
+ project = object
+ current_user = context[:current_user]
+ issue_id = GlobalID.parse(args[:id]).model_id
+
+ # Get data from Sentry
+ response = ::ErrorTracking::IssueDetailsService.new(
+ project,
+ current_user,
+ { issue_id: issue_id }
+ ).execute
+ issue = response[:issue]
+ issue.gitlab_project = project if issue
+
+ issue
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb
index 1fbc61cd950..664e0955535 100644
--- a/app/graphql/resolvers/issues_resolver.rb
+++ b/app/graphql/resolvers/issues_resolver.rb
@@ -4,17 +4,17 @@ module Resolvers
class IssuesResolver < BaseResolver
argument :iid, GraphQL::STRING_TYPE,
required: false,
- description: 'The IID of the issue, e.g., "1"'
+ description: 'IID of the issue. For example, "1"'
argument :iids, [GraphQL::STRING_TYPE],
required: false,
- description: 'The list of IIDs of issues, e.g., [1, 2]'
+ description: 'List of IIDs of issues. For example, [1, 2]'
argument :state, Types::IssuableStateEnum,
required: false,
- description: 'Current state of Issue'
+ description: 'Current state of this issue'
argument :label_name, GraphQL::STRING_TYPE.to_list_type,
required: false,
- description: 'Labels applied to the Issue'
+ description: 'Labels applied to this issue'
argument :created_before, Types::TimeType,
required: false,
description: 'Issues created before this date'
@@ -33,8 +33,9 @@ module Resolvers
argument :closed_after, Types::TimeType,
required: false,
description: 'Issues closed after this date'
- argument :search, GraphQL::STRING_TYPE, # rubocop:disable Graphql/Descriptions
- required: false
+ argument :search, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Search query for finding issues by title or description'
argument :sort, Types::IssueSortEnum,
description: 'Sort issues by this criteria',
required: false,
@@ -53,6 +54,7 @@ module Resolvers
# https://gitlab.com/gitlab-org/gitlab-foss/issues/54520
args[:project_id] = project.id
args[:iids] ||= [args[:iid]].compact
+ args[:attempt_project_search_optimizations] = args[:search].present?
IssuesFinder.new(context[:current_user], args).execute
end
diff --git a/app/graphql/resolvers/projects/snippets_resolver.rb b/app/graphql/resolvers/projects/snippets_resolver.rb
new file mode 100644
index 00000000000..bf9aa45349f
--- /dev/null
+++ b/app/graphql/resolvers/projects/snippets_resolver.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Projects
+ class SnippetsResolver < BaseResolver
+ include ResolvesSnippets
+
+ alias_method :project, :object
+
+ def resolve(**args)
+ return Snippet.none if project.nil?
+
+ super
+ end
+
+ private
+
+ def snippet_finder_params(args)
+ super.merge(project: project)
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/snippets_resolver.rb b/app/graphql/resolvers/snippets_resolver.rb
new file mode 100644
index 00000000000..530a288a25b
--- /dev/null
+++ b/app/graphql/resolvers/snippets_resolver.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class SnippetsResolver < BaseResolver
+ include ResolvesSnippets
+
+ ERROR_MESSAGE = 'Filtering by both an author and a project is not supported'
+
+ alias_method :user, :object
+
+ argument :author_id, GraphQL::ID_TYPE,
+ required: false,
+ description: 'The ID of an author'
+
+ argument :project_id, GraphQL::ID_TYPE,
+ required: false,
+ description: 'The ID of a project'
+
+ argument :type, Types::Snippets::TypeEnum,
+ required: false,
+ description: 'The type of snippet'
+
+ argument :explore,
+ GraphQL::BOOLEAN_TYPE,
+ required: false,
+ description: 'Explore personal snippets'
+
+ def resolve(**args)
+ if args[:author_id].present? && args[:project_id].present?
+ raise Gitlab::Graphql::Errors::ArgumentError, ERROR_MESSAGE
+ end
+
+ super
+ end
+
+ private
+
+ def snippet_finder_params(args)
+ super
+ .merge(author: resolve_gid(args[:author_id], :author),
+ project: resolve_gid(args[:project_id], :project),
+ explore: args[:explore])
+ end
+ end
+end
diff --git a/app/graphql/resolvers/todo_resolver.rb b/app/graphql/resolvers/todo_resolver.rb
index 38a4539f34a..cff65321dc0 100644
--- a/app/graphql/resolvers/todo_resolver.rb
+++ b/app/graphql/resolvers/todo_resolver.rb
@@ -38,53 +38,15 @@ module Resolvers
private
- # TODO: Support multiple queries for e.g. state and type on TodosFinder:
- #
- # https://gitlab.com/gitlab-org/gitlab/merge_requests/18487
- # https://gitlab.com/gitlab-org/gitlab/merge_requests/18518
- #
- # As soon as these MR's are merged, we can refactor this to query by
- # multiple contents.
- #
def todo_finder_params(args)
{
- state: first_state(args),
- type: first_type(args),
- group_id: first_group_id(args),
- author_id: first_author_id(args),
- action_id: first_action(args),
- project_id: first_project(args)
+ state: args[:state],
+ type: args[:type],
+ group_id: args[:group_id],
+ author_id: args[:author_id],
+ action_id: args[:action],
+ project_id: args[:project_id]
}
end
-
- def first_project(args)
- first_query_field(args, :project_id)
- end
-
- def first_action(args)
- first_query_field(args, :action)
- end
-
- def first_author_id(args)
- first_query_field(args, :author_id)
- end
-
- def first_group_id(args)
- first_query_field(args, :group_id)
- end
-
- def first_state(args)
- first_query_field(args, :state)
- end
-
- def first_type(args)
- first_query_field(args, :type)
- end
-
- def first_query_field(query, field)
- return unless query.key?(field)
-
- query[field].first if query[field].respond_to?(:first)
- end
end
end
diff --git a/app/graphql/resolvers/users/snippets_resolver.rb b/app/graphql/resolvers/users/snippets_resolver.rb
new file mode 100644
index 00000000000..d757640b5ff
--- /dev/null
+++ b/app/graphql/resolvers/users/snippets_resolver.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Users
+ class SnippetsResolver < BaseResolver
+ include ResolvesSnippets
+
+ alias_method :user, :object
+
+ argument :type, Types::Snippets::TypeEnum,
+ required: false,
+ description: 'The type of snippet'
+
+ private
+
+ def snippet_finder_params(args)
+ super.merge(author: user)
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/diff_refs_type.rb b/app/graphql/types/diff_refs_type.rb
index 33a5780cd68..03d080d784b 100644
--- a/app/graphql/types/diff_refs_type.rb
+++ b/app/graphql/types/diff_refs_type.rb
@@ -6,9 +6,12 @@ module Types
class DiffRefsType < BaseObject
graphql_name 'DiffRefs'
- field :head_sha, GraphQL::STRING_TYPE, null: false, description: 'The sha of the head at the time the comment was made'
- field :base_sha, GraphQL::STRING_TYPE, null: false, description: 'The merge base of the branch the comment was made on'
- field :start_sha, GraphQL::STRING_TYPE, null: false, description: 'The sha of the branch being compared against'
+ field :head_sha, GraphQL::STRING_TYPE, null: false,
+ description: 'SHA of the HEAD at the time the comment was made'
+ field :base_sha, GraphQL::STRING_TYPE, null: false,
+ description: 'Merge base of the branch the comment was made on'
+ field :start_sha, GraphQL::STRING_TYPE, null: false,
+ description: 'SHA of the branch being compared against'
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
new file mode 100644
index 00000000000..c680f387a9a
--- /dev/null
+++ b/app/graphql/types/error_tracking/sentry_detailed_error_type.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+module Types
+ module ErrorTracking
+ class SentryDetailedErrorType < ::Types::BaseObject
+ graphql_name 'SentryDetailedError'
+
+ present_using SentryDetailedErrorPresenter
+
+ authorize :read_sentry_issue
+
+ field :id, GraphQL::ID_TYPE,
+ null: false,
+ description: "ID (global ID) of the error"
+ field :sentry_id, GraphQL::STRING_TYPE,
+ method: :id,
+ null: false,
+ description: "ID (Sentry ID) of the error"
+ field :title, GraphQL::STRING_TYPE,
+ null: false,
+ description: "Title of the error"
+ field :type, GraphQL::STRING_TYPE,
+ null: false,
+ description: "Type of the error"
+ field :user_count, GraphQL::INT_TYPE,
+ null: false,
+ description: "Count of users affected by the error"
+ field :count, GraphQL::INT_TYPE,
+ null: false,
+ description: "Count of occurrences"
+ field :first_seen, Types::TimeType,
+ null: false,
+ description: "Timestamp when the error was first seen"
+ field :last_seen, Types::TimeType,
+ null: false,
+ description: "Timestamp when the error was last seen"
+ field :message, GraphQL::STRING_TYPE,
+ null: true,
+ description: "Sentry metadata message of the error"
+ field :culprit, GraphQL::STRING_TYPE,
+ null: false,
+ description: "Culprit of the error"
+ field :external_url, GraphQL::STRING_TYPE,
+ null: false,
+ description: "External URL of the error"
+ field :sentry_project_id, GraphQL::ID_TYPE,
+ method: :project_id,
+ null: false,
+ description: "ID of the project (Sentry project)"
+ field :sentry_project_name, GraphQL::STRING_TYPE,
+ method: :project_name,
+ null: false,
+ description: "Name of the project affected by the error"
+ field :sentry_project_slug, GraphQL::STRING_TYPE,
+ method: :project_slug,
+ null: false,
+ description: "Slug of the project affected by the error"
+ field :short_id, GraphQL::STRING_TYPE,
+ null: false,
+ description: "Short ID (Sentry ID) of the error"
+ field :status, Types::ErrorTracking::SentryErrorStatusEnum,
+ null: false,
+ description: "Status of the error"
+ field :frequency, [Types::ErrorTracking::SentryErrorFrequencyType],
+ null: false,
+ description: "Last 24hr stats of the error"
+ field :first_release_last_commit, GraphQL::STRING_TYPE,
+ null: true,
+ description: "Commit the error was first seen"
+ field :last_release_last_commit, GraphQL::STRING_TYPE,
+ null: true,
+ 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"
+ field :last_release_short_version, GraphQL::STRING_TYPE,
+ null: true,
+ description: "Release version the error was last seen"
+
+ def first_seen
+ DateTime.parse(object.first_seen)
+ end
+
+ def last_seen
+ DateTime.parse(object.last_seen)
+ end
+
+ def project_id
+ Gitlab::GlobalId.build(model_name: 'Project', id: object.project_id).to_s
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/error_tracking/sentry_error_frequency_type.rb b/app/graphql/types/error_tracking/sentry_error_frequency_type.rb
new file mode 100644
index 00000000000..a44ca0684b6
--- /dev/null
+++ b/app/graphql/types/error_tracking/sentry_error_frequency_type.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Types
+ module ErrorTracking
+ # rubocop: disable Graphql/AuthorizeTypes
+ class SentryErrorFrequencyType < ::Types::BaseObject
+ graphql_name 'SentryErrorFrequency'
+
+ field :time, Types::TimeType,
+ null: false,
+ description: "Time the error frequency stats were recorded"
+ field :count, GraphQL::INT_TYPE,
+ null: false,
+ description: "Count of errors received since the previously recorded time"
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/error_tracking/sentry_error_status_enum.rb b/app/graphql/types/error_tracking/sentry_error_status_enum.rb
new file mode 100644
index 00000000000..df68eef4f3c
--- /dev/null
+++ b/app/graphql/types/error_tracking/sentry_error_status_enum.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Types
+ module ErrorTracking
+ class SentryErrorStatusEnum < ::Types::BaseEnum
+ graphql_name 'SentryErrorStatus'
+ description 'State of a Sentry error'
+
+ value 'RESOLVED', value: 'resolved', description: 'Error has been resolved'
+ value 'RESOLVED_IN_NEXT_RELEASE', value: 'resolvedInNextRelease', description: 'Error has been ignored until next release'
+ value 'UNRESOLVED', value: 'unresolved', description: 'Error is unresolved'
+ value 'IGNORED', value: 'ignored', description: 'Error has been ignored'
+ end
+ end
+end
diff --git a/app/graphql/types/issuable_sort_enum.rb b/app/graphql/types/issuable_sort_enum.rb
index 932e90c2d22..9fb1249d582 100644
--- a/app/graphql/types/issuable_sort_enum.rb
+++ b/app/graphql/types/issuable_sort_enum.rb
@@ -1,10 +1,8 @@
# frozen_string_literal: true
module Types
- # rubocop: disable Graphql/AuthorizeTypes
class IssuableSortEnum < SortEnum
graphql_name 'IssuableSort'
description 'Values for sorting issuables'
end
- # rubocop: enable Graphql/AuthorizeTypes
end
diff --git a/app/graphql/types/issue_sort_enum.rb b/app/graphql/types/issue_sort_enum.rb
index 48ff5819286..c8d8f3ef079 100644
--- a/app/graphql/types/issue_sort_enum.rb
+++ b/app/graphql/types/issue_sort_enum.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
module Types
- # rubocop: disable Graphql/AuthorizeTypes
class IssueSortEnum < IssuableSortEnum
graphql_name 'IssueSort'
description 'Values for sorting issues'
@@ -10,5 +9,6 @@ module Types
value 'DUE_DATE_DESC', 'Due date by descending order', value: 'due_date_desc'
value 'RELATIVE_POSITION_ASC', 'Relative position by ascending order', value: 'relative_position_asc'
end
- # rubocop: enable Graphql/AuthorizeTypes
end
+
+Types::IssueSortEnum.prepend_if_ee('::EE::Types::IssueSortEnum')
diff --git a/app/graphql/types/issue_state_enum.rb b/app/graphql/types/issue_state_enum.rb
index 70c34fbe491..6521407fc9d 100644
--- a/app/graphql/types/issue_state_enum.rb
+++ b/app/graphql/types/issue_state_enum.rb
@@ -1,11 +1,8 @@
# frozen_string_literal: true
module Types
- # rubocop: disable Graphql/AuthorizeTypes
- # This is a BaseEnum through IssuableEnum, so it does not need authorization
class IssueStateEnum < IssuableStateEnum
graphql_name 'IssueState'
description 'State of a GitLab issue'
end
- # rubocop: enable Graphql/AuthorizeTypes
end
diff --git a/app/graphql/types/label_type.rb b/app/graphql/types/label_type.rb
index d0bcf2068b7..738a00ad616 100644
--- a/app/graphql/types/label_type.rb
+++ b/app/graphql/types/label_type.rb
@@ -9,7 +9,7 @@ module Types
field :id, GraphQL::ID_TYPE, null: false,
description: 'Label ID'
field :description, GraphQL::STRING_TYPE, null: true,
- description: 'Description of the label (markdown rendered as HTML for caching)'
+ description: 'Description of the label (Markdown rendered as HTML for caching)'
markdown_field :description_html, null: true
field :title, GraphQL::STRING_TYPE, null: false,
description: 'Content of the label'
diff --git a/app/graphql/types/merge_request_state_enum.rb b/app/graphql/types/merge_request_state_enum.rb
index 37c890a3c8d..92f52726ab3 100644
--- a/app/graphql/types/merge_request_state_enum.rb
+++ b/app/graphql/types/merge_request_state_enum.rb
@@ -1,13 +1,10 @@
# frozen_string_literal: true
module Types
- # rubocop: disable Graphql/AuthorizeTypes
- # This is a BaseEnum through IssuableEnum, so it does not need authorization
class MergeRequestStateEnum < IssuableStateEnum
graphql_name 'MergeRequestState'
description 'State of a GitLab merge request'
value 'merged'
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 278a95fe3ca..0da95b367d8 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -20,7 +20,7 @@ module Types
description: 'Title of the merge request'
markdown_field :title_html, null: true
field :description, GraphQL::STRING_TYPE, null: true,
- description: 'Description of the merge request (markdown rendered as HTML for caching)'
+ description: 'Description of the merge request (Markdown rendered as HTML for caching)'
markdown_field :description_html, null: true
field :state, MergeRequestStateEnum, null: false,
description: 'State of the merge request'
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index b3c7c162bb3..0a9c0143945 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -9,6 +9,8 @@ module Types
mount_mutation Mutations::AwardEmojis::Add
mount_mutation Mutations::AwardEmojis::Remove
mount_mutation Mutations::AwardEmojis::Toggle
+ mount_mutation Mutations::Issues::SetConfidential
+ mount_mutation Mutations::Issues::SetDueDate
mount_mutation Mutations::MergeRequests::SetLabels
mount_mutation Mutations::MergeRequests::SetLocked
mount_mutation Mutations::MergeRequests::SetMilestone
@@ -21,6 +23,12 @@ module Types
mount_mutation Mutations::Notes::Update
mount_mutation Mutations::Notes::Destroy
mount_mutation Mutations::Todos::MarkDone
+ mount_mutation Mutations::Todos::Restore
+ mount_mutation Mutations::Todos::MarkAllDone
+ mount_mutation Mutations::Snippets::Destroy
+ mount_mutation Mutations::Snippets::Update
+ mount_mutation Mutations::Snippets::Create
+ mount_mutation Mutations::Snippets::MarkAsSpam
end
end
diff --git a/app/graphql/types/notes/diff_position_type.rb b/app/graphql/types/notes/diff_position_type.rb
index cab8c750dc0..654562da0a7 100644
--- a/app/graphql/types/notes/diff_position_type.rb
+++ b/app/graphql/types/notes/diff_position_type.rb
@@ -7,36 +7,38 @@ module Types
class DiffPositionType < BaseObject
graphql_name 'DiffPosition'
- field :diff_refs, Types::DiffRefsType, null: false # rubocop:disable Graphql/Descriptions
+ field :diff_refs, Types::DiffRefsType, null: false,
+ description: 'Information about the branch, HEAD, and base at the time of commenting'
field :file_path, GraphQL::STRING_TYPE, null: false,
- description: "The path of the file that was changed"
+ description: 'Path of the file that was changed'
field :old_path, GraphQL::STRING_TYPE, null: true,
- description: "The path of the file on the start sha."
+ description: 'Path of the file on the start SHA'
field :new_path, GraphQL::STRING_TYPE, null: true,
- description: "The path of the file on the head sha."
- field :position_type, Types::Notes::PositionTypeEnum, null: false # rubocop:disable Graphql/Descriptions
+ description: 'Path of the file on the HEAD SHA'
+ field :position_type, Types::Notes::PositionTypeEnum, null: false,
+ description: 'Type of file the position refers to'
# Fields for text positions
field :old_line, GraphQL::INT_TYPE, null: true,
- description: "The line on start sha that was changed",
+ description: 'Line on start SHA that was changed',
resolve: -> (position, _args, _ctx) { position.old_line if position.on_text? }
field :new_line, GraphQL::INT_TYPE, null: true,
- description: "The line on head sha that was changed",
+ description: 'Line on HEAD SHA that was changed',
resolve: -> (position, _args, _ctx) { position.new_line if position.on_text? }
# Fields for image positions
field :x, GraphQL::INT_TYPE, null: true,
- description: "The X postion on which the comment was made",
+ description: 'X position on which the comment was made',
resolve: -> (position, _args, _ctx) { position.x if position.on_image? }
field :y, GraphQL::INT_TYPE, null: true,
- description: "The Y position on which the comment was made",
+ description: 'Y position on which the comment was made',
resolve: -> (position, _args, _ctx) { position.y if position.on_image? }
field :width, GraphQL::INT_TYPE, null: true,
- description: "The total width of the image",
+ description: 'Total width of the image',
resolve: -> (position, _args, _ctx) { position.width if position.on_image? }
field :height, GraphQL::INT_TYPE, null: true,
- description: "The total height of the image",
+ description: 'Total height of the image',
resolve: -> (position, _args, _ctx) { position.height if position.on_image? }
end
# rubocop: enable Graphql/AuthorizeTypes
diff --git a/app/graphql/types/notes/discussion_type.rb b/app/graphql/types/notes/discussion_type.rb
index ab87f8280ac..74a233e9d26 100644
--- a/app/graphql/types/notes/discussion_type.rb
+++ b/app/graphql/types/notes/discussion_type.rb
@@ -7,10 +7,14 @@ module Types
authorize :read_note
- field :id, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
- field :reply_id, GraphQL::ID_TYPE, null: false, description: 'The ID used to reply to this discussion'
- field :created_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions
- field :notes, Types::Notes::NoteType.connection_type, null: false, description: "All notes in the discussion"
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: "ID of this discussion"
+ field :reply_id, GraphQL::ID_TYPE, null: false,
+ description: 'ID used to reply to this discussion'
+ field :created_at, Types::TimeType, null: false,
+ description: "Timestamp of the discussion's creation"
+ field :notes, Types::Notes::NoteType.connection_type, null: false,
+ description: 'All notes in the discussion'
# The gem we use to generate Global IDs is hard-coded to work with
# `id` properties. To generate a GID for the `reply_id` property,
diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb
index 4edf6ed90f7..b60fc96bd03 100644
--- a/app/graphql/types/notes/note_type.rb
+++ b/app/graphql/types/notes/note_type.rb
@@ -9,40 +9,48 @@ module Types
expose_permissions Types::PermissionTypes::Note
- field :id, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: 'ID of the note'
field :project, Types::ProjectType,
null: true,
- description: "The project this note is associated to",
+ description: 'Project associated with the note',
resolve: -> (note, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, note.project_id).find }
field :author, Types::UserType,
null: false,
- description: "The user who wrote this note",
+ description: 'User who wrote this note',
resolve: -> (note, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, note.author_id).find }
field :resolved_by, Types::UserType,
null: true,
- description: "The user that resolved the discussion",
+ description: 'User that resolved the discussion',
resolve: -> (note, _args, _context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, note.resolved_by_id).find }
field :system, GraphQL::BOOLEAN_TYPE,
null: false,
- description: "Whether or not this note was created by the system or by a user"
+ description: 'Indicates whether this note was created by the system or by a user'
field :body, GraphQL::STRING_TYPE,
null: false,
method: :note,
- description: "The content note itself"
+ description: 'Content of the note'
markdown_field :body_html, null: true, method: :note
- field :created_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions
- field :updated_at, Types::TimeType, null: false # rubocop:disable Graphql/Descriptions
- field :discussion, Types::Notes::DiscussionType, null: true, description: "The discussion this note is a part of"
- field :resolvable, GraphQL::BOOLEAN_TYPE, null: false, method: :resolvable? # rubocop:disable Graphql/Descriptions
- field :resolved_at, Types::TimeType, null: true, description: "The time the discussion was resolved"
- field :position, Types::Notes::DiffPositionType, null: true, description: "The position of this note on a diff"
+ field :created_at, Types::TimeType, null: false,
+ description: 'Timestamp of the note creation'
+ field :updated_at, Types::TimeType, null: false,
+ description: "Timestamp of the note's last activity"
+ field :discussion, Types::Notes::DiscussionType, null: true,
+ description: 'The discussion this note is a part of'
+ field :resolvable, GraphQL::BOOLEAN_TYPE, null: false,
+ description: 'Indicates if this note can be resolved. That is, if it is a resolvable discussion or simply a standalone note',
+ method: :resolvable?
+ field :resolved_at, Types::TimeType, null: true,
+ description: "Timestamp of the note's resolution"
+ field :position, Types::Notes::DiffPositionType, null: true,
+ description: 'The position of this note on a diff'
end
end
end
diff --git a/app/graphql/types/notes/noteable_type.rb b/app/graphql/types/notes/noteable_type.rb
index ab4a170b123..2ac66452841 100644
--- a/app/graphql/types/notes/noteable_type.rb
+++ b/app/graphql/types/notes/noteable_type.rb
@@ -15,6 +15,8 @@ module Types
Types::IssueType
when MergeRequest
Types::MergeRequestType
+ when Snippet
+ Types::SnippetType
else
raise "Unknown GraphQL type for #{object}"
end
diff --git a/app/graphql/types/permission_types/project.rb b/app/graphql/types/permission_types/project.rb
index 3a6ba371154..2879dbd2b5c 100644
--- a/app/graphql/types/permission_types/project.rb
+++ b/app/graphql/types/permission_types/project.rb
@@ -10,13 +10,19 @@ module Types
:remove_pages, :read_project, :create_merge_request_in,
:read_wiki, :read_project_member, :create_issue, :upload_file,
:read_cycle_analytics, :download_code, :download_wiki_code,
- :fork_project, :create_project_snippet, :read_commit_status,
+ :fork_project, :read_commit_status,
:request_access, :create_pipeline, :create_pipeline_schedule,
:create_merge_request_from, :create_wiki, :push_code,
:create_deployment, :push_to_delete_protected_branch,
:admin_wiki, :admin_project, :update_pages,
:admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki,
:create_pages, :destroy_pages, :read_pages_content, :admin_operations
+
+ permission_field :create_snippet
+
+ def create_snippet
+ Ability.allowed?(context[:current_user], :create_project_snippet, object)
+ end
end
end
end
diff --git a/app/graphql/types/permission_types/snippet.rb b/app/graphql/types/permission_types/snippet.rb
new file mode 100644
index 00000000000..0fc13c60983
--- /dev/null
+++ b/app/graphql/types/permission_types/snippet.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Types
+ module PermissionTypes
+ class Snippet < BasePermissionType
+ graphql_name 'SnippetPermissions'
+
+ abilities :create_note, :award_emoji
+
+ permission_field :read_snippet, method: :can_read_snippet?
+ permission_field :update_snippet, method: :can_update_snippet?
+ permission_field :admin_snippet, method: :can_admin_snippet?
+ permission_field :report_snippet, method: :can_report_as_spam?
+ end
+ end
+end
diff --git a/app/graphql/types/permission_types/user.rb b/app/graphql/types/permission_types/user.rb
new file mode 100644
index 00000000000..dba4de2dacc
--- /dev/null
+++ b/app/graphql/types/permission_types/user.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Types
+ module PermissionTypes
+ class User < BasePermissionType
+ graphql_name 'UserPermissions'
+
+ permission_field :create_snippet
+
+ def create_snippet
+ Ability.allowed?(context[:current_user], :create_personal_snippet)
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 73255021119..bd80ad7ff74 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -145,5 +145,19 @@ module Types
null: true,
description: 'Build pipelines of the project',
resolver: Resolvers::ProjectPipelinesResolver
+
+ field :sentry_detailed_error,
+ Types::ErrorTracking::SentryDetailedErrorType,
+ null: true,
+ description: 'Detailed version of a Sentry error on the project',
+ resolver: Resolvers::ErrorTracking::SentryDetailedErrorResolver
+
+ field :snippets,
+ Types::SnippetType.connection_type,
+ null: true,
+ description: 'Snippets of the project',
+ resolver: Resolvers::Projects::SnippetsResolver
end
end
+
+Types::ProjectType.prepend_if_ee('::EE::Types::ProjectType')
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 996bf225976..199a6226c6d 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -29,6 +29,14 @@ module Types
resolver: Resolvers::MetadataResolver,
description: 'Metadata about GitLab'
- field :echo, GraphQL::STRING_TYPE, null: false, resolver: Resolvers::EchoResolver # rubocop:disable Graphql/Descriptions
+ field :snippets,
+ Types::SnippetType.connection_type,
+ null: true,
+ resolver: Resolvers::SnippetsResolver,
+ description: 'Find Snippets visible to the current user'
+
+ field :echo, GraphQL::STRING_TYPE, null: false,
+ description: 'Text to echo back',
+ resolver: Resolvers::EchoResolver
end
end
diff --git a/app/graphql/types/root_storage_statistics_type.rb b/app/graphql/types/root_storage_statistics_type.rb
index a7498ee0a2e..3c471df072d 100644
--- a/app/graphql/types/root_storage_statistics_type.rb
+++ b/app/graphql/types/root_storage_statistics_type.rb
@@ -7,7 +7,7 @@ module Types
authorize :read_statistics
field :storage_size, GraphQL::INT_TYPE, null: false, description: 'The total storage in bytes'
- field :repository_size, GraphQL::INT_TYPE, null: false, description: 'The git repository size in bytes'
+ field :repository_size, GraphQL::INT_TYPE, null: false, description: 'The Git repository size in bytes'
field :lfs_objects_size, GraphQL::INT_TYPE, null: false, description: 'The LFS objects size in bytes'
field :build_artifacts_size, GraphQL::INT_TYPE, null: false, description: 'The CI artifacts size in bytes'
field :packages_size, GraphQL::INT_TYPE, null: false, description: 'The packages size in bytes'
diff --git a/app/graphql/types/snippet_type.rb b/app/graphql/types/snippet_type.rb
new file mode 100644
index 00000000000..3f780528945
--- /dev/null
+++ b/app/graphql/types/snippet_type.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module Types
+ class SnippetType < BaseObject
+ graphql_name 'Snippet'
+ description 'Represents a snippet entry'
+
+ implements(Types::Notes::NoteableType)
+
+ present_using SnippetPresenter
+
+ authorize :read_snippet
+
+ expose_permissions Types::PermissionTypes::Snippet
+
+ field :id, GraphQL::ID_TYPE,
+ description: 'Id of the snippet',
+ null: false
+
+ field :title, GraphQL::STRING_TYPE,
+ description: 'Title of the snippet',
+ null: false
+
+ field :project, Types::ProjectType,
+ description: 'The project the snippet is associated with',
+ null: true,
+ authorize: :read_project,
+ resolve: -> (snippet, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, snippet.project_id).find }
+
+ field :author, Types::UserType,
+ description: 'The owner of the snippet',
+ null: false,
+ resolve: -> (snippet, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, snippet.author_id).find }
+
+ field :file_name, GraphQL::STRING_TYPE,
+ description: 'File Name of the snippet',
+ null: true
+
+ field :content, GraphQL::STRING_TYPE,
+ description: 'Content of the snippet',
+ null: false
+
+ field :description, GraphQL::STRING_TYPE,
+ description: 'Description of the snippet',
+ null: true
+
+ field :visibility_level, Types::VisibilityLevelsEnum,
+ description: 'Visibility Level of the snippet',
+ null: false
+
+ field :created_at, Types::TimeType,
+ description: 'Timestamp this snippet was created',
+ null: false
+
+ field :updated_at, Types::TimeType,
+ description: 'Timestamp this snippet was updated',
+ null: false
+
+ field :web_url, type: GraphQL::STRING_TYPE,
+ description: 'Web URL of the snippet',
+ null: false
+
+ field :raw_url, type: GraphQL::STRING_TYPE,
+ description: 'Raw URL of the snippet',
+ null: false
+
+ markdown_field :description_html, null: true, method: :description
+ end
+end
diff --git a/app/graphql/types/snippets/type_enum.rb b/app/graphql/types/snippets/type_enum.rb
new file mode 100644
index 00000000000..243f05359db
--- /dev/null
+++ b/app/graphql/types/snippets/type_enum.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Types
+ module Snippets
+ class TypeEnum < BaseEnum
+ value 'personal', value: 'personal'
+ value 'project', value: 'project'
+ end
+ end
+end
diff --git a/app/graphql/types/snippets/visibility_scopes_enum.rb b/app/graphql/types/snippets/visibility_scopes_enum.rb
new file mode 100644
index 00000000000..5488e05b95d
--- /dev/null
+++ b/app/graphql/types/snippets/visibility_scopes_enum.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Types
+ module Snippets
+ class VisibilityScopesEnum < BaseEnum
+ value 'private', value: 'are_private'
+ value 'internal', value: 'are_internal'
+ value 'public', value: 'are_public'
+ end
+ end
+end
diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb
index b45c7893e75..3943c891335 100644
--- a/app/graphql/types/user_type.rb
+++ b/app/graphql/types/user_type.rb
@@ -8,6 +8,8 @@ module Types
present_using UserPresenter
+ expose_permissions Types::PermissionTypes::User
+
field :name, GraphQL::STRING_TYPE, null: false,
description: 'Human-readable name of the user'
field :username, GraphQL::STRING_TYPE, null: false,
@@ -19,5 +21,11 @@ module Types
field :todos, Types::TodoType.connection_type, null: false,
resolver: Resolvers::TodoResolver,
description: 'Todos of the user'
+
+ field :snippets,
+ Types::SnippetType.connection_type,
+ null: true,
+ description: 'Snippets authored by the user',
+ resolver: Resolvers::Users::SnippetsResolver
end
end
diff --git a/app/graphql/types/visibility_levels_enum.rb b/app/graphql/types/visibility_levels_enum.rb
new file mode 100644
index 00000000000..d5ace24455e
--- /dev/null
+++ b/app/graphql/types/visibility_levels_enum.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Types
+ class VisibilityLevelsEnum < BaseEnum
+ Gitlab::VisibilityLevel.string_options.each do |name, int_value|
+ value name.downcase, value: int_value
+ end
+ end
+end