Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 14:18:50 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 14:18:50 +0300
commit8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781 (patch)
treea77e7fe7a93de11213032ed4ab1f33a3db51b738 /app/graphql
parent00b35af3db1abfe813a778f643dad221aad51fca (diff)
Add latest changes from gitlab-org/gitlab@13-1-stable-ee
Diffstat (limited to 'app/graphql')
-rw-r--r--app/graphql/mutations/alert_management/alerts/set_assignees.rb56
-rw-r--r--app/graphql/mutations/alert_management/base.rb4
-rw-r--r--app/graphql/mutations/alert_management/update_alert_status.rb2
-rw-r--r--app/graphql/mutations/base_mutation.rb2
-rw-r--r--app/graphql/mutations/branches/create.rb2
-rw-r--r--app/graphql/mutations/commits/create.rb60
-rw-r--r--app/graphql/mutations/concerns/mutations/resolves_issuable.rb16
-rw-r--r--app/graphql/mutations/concerns/mutations/resolves_project.rb15
-rw-r--r--app/graphql/mutations/container_expiration_policies/update.rb62
-rw-r--r--app/graphql/mutations/discussions/toggle_resolve.rb73
-rw-r--r--app/graphql/mutations/issues/set_confidential.rb2
-rw-r--r--app/graphql/mutations/issues/set_due_date.rb2
-rw-r--r--app/graphql/mutations/issues/update.rb2
-rw-r--r--app/graphql/mutations/jira_import/import_users.rb44
-rw-r--r--app/graphql/mutations/jira_import/start.rb16
-rw-r--r--app/graphql/mutations/merge_requests/create.rb63
-rw-r--r--app/graphql/mutations/merge_requests/set_assignees.rb2
-rw-r--r--app/graphql/mutations/merge_requests/set_labels.rb7
-rw-r--r--app/graphql/mutations/merge_requests/set_locked.rb2
-rw-r--r--app/graphql/mutations/merge_requests/set_milestone.rb2
-rw-r--r--app/graphql/mutations/merge_requests/set_subscription.rb2
-rw-r--r--app/graphql/mutations/merge_requests/set_wip.rb2
-rw-r--r--app/graphql/mutations/metrics/dashboard/annotations/base.rb18
-rw-r--r--app/graphql/mutations/metrics/dashboard/annotations/delete.rb32
-rw-r--r--app/graphql/mutations/snippets/create.rb4
-rw-r--r--app/graphql/mutations/todos/mark_all_done.rb4
-rw-r--r--app/graphql/mutations/todos/mark_done.rb2
-rw-r--r--app/graphql/mutations/todos/restore.rb6
-rw-r--r--app/graphql/mutations/todos/restore_many.rb2
-rw-r--r--app/graphql/resolvers/alert_management/alert_resolver.rb42
-rw-r--r--app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb4
-rw-r--r--app/graphql/resolvers/alert_management_alert_resolver.rb31
-rw-r--r--app/graphql/resolvers/assigned_merge_requests_resolver.rb9
-rw-r--r--app/graphql/resolvers/authored_merge_requests_resolver.rb9
-rw-r--r--app/graphql/resolvers/base_resolver.rb33
-rw-r--r--app/graphql/resolvers/concerns/looks_ahead.rb52
-rw-r--r--app/graphql/resolvers/concerns/resolves_merge_requests.rb67
-rw-r--r--app/graphql/resolvers/concerns/resolves_project.rb15
-rw-r--r--app/graphql/resolvers/full_path_resolver.rb7
-rw-r--r--app/graphql/resolvers/merge_request_resolver.rb18
-rw-r--r--app/graphql/resolvers/merge_requests_resolver.rb52
-rw-r--r--app/graphql/resolvers/project_members_resolver.rb21
-rw-r--r--app/graphql/resolvers/project_pipeline_resolver.rb17
-rw-r--r--app/graphql/resolvers/projects/jira_imports_resolver.rb2
-rw-r--r--app/graphql/resolvers/projects/jira_projects_resolver.rb76
-rw-r--r--app/graphql/resolvers/user_merge_requests_resolver.rb68
-rw-r--r--app/graphql/resolvers/user_resolver.rb43
-rw-r--r--app/graphql/resolvers/users_resolver.rb57
-rw-r--r--app/graphql/types/access_level_enum.rb15
-rw-r--r--app/graphql/types/access_level_type.rb17
-rw-r--r--app/graphql/types/alert_management/alert_sort_enum.rb12
-rw-r--r--app/graphql/types/alert_management/alert_type.rb11
-rw-r--r--app/graphql/types/base_object.rb4
-rw-r--r--app/graphql/types/board_type.rb2
-rw-r--r--app/graphql/types/ci/pipeline_type.rb2
-rw-r--r--app/graphql/types/commit_action_mode_enum.rb14
-rw-r--r--app/graphql/types/commit_action_type.rb22
-rw-r--r--app/graphql/types/commit_encoding_enum.rb10
-rw-r--r--app/graphql/types/container_expiration_policy_cadence_enum.rb17
-rw-r--r--app/graphql/types/container_expiration_policy_keep_enum.rb18
-rw-r--r--app/graphql/types/container_expiration_policy_older_than_enum.rb16
-rw-r--r--app/graphql/types/container_expiration_policy_type.rb21
-rw-r--r--app/graphql/types/evidence_type.rb21
-rw-r--r--app/graphql/types/group_member_type.rb17
-rw-r--r--app/graphql/types/group_type.rb39
-rw-r--r--app/graphql/types/jira_import_type.rb6
-rw-r--r--app/graphql/types/jira_user_type.rb19
-rw-r--r--app/graphql/types/member_interface.rb22
-rw-r--r--app/graphql/types/merge_request_type.rb12
-rw-r--r--app/graphql/types/metrics/dashboard_type.rb3
-rw-r--r--app/graphql/types/milestone_type.rb12
-rw-r--r--app/graphql/types/mutation_type.rb7
-rw-r--r--app/graphql/types/notes/discussion_type.rb2
-rw-r--r--app/graphql/types/notes/note_type.rb12
-rw-r--r--app/graphql/types/notes/noteable_type.rb2
-rw-r--r--app/graphql/types/permission_types/ci/pipeline.rb3
-rw-r--r--app/graphql/types/permission_types/merge_request.rb12
-rw-r--r--app/graphql/types/project_member_type.rb25
-rw-r--r--app/graphql/types/project_type.rb63
-rw-r--r--app/graphql/types/projects/service_type.rb2
-rw-r--r--app/graphql/types/projects/services/jira_project_type.rb21
-rw-r--r--app/graphql/types/projects/services/jira_service_type.rb11
-rw-r--r--app/graphql/types/query_type.rb14
-rw-r--r--app/graphql/types/release_assets_type.rb20
-rw-r--r--app/graphql/types/release_link_type.rb20
-rw-r--r--app/graphql/types/release_link_type_enum.rb12
-rw-r--r--app/graphql/types/release_source_type.rb14
-rw-r--r--app/graphql/types/release_type.rb4
-rw-r--r--app/graphql/types/resolvable_interface.rb28
-rw-r--r--app/graphql/types/snippet_type.rb10
-rw-r--r--app/graphql/types/snippets/file_input_action_enum.rb15
-rw-r--r--app/graphql/types/snippets/file_input_type.rb26
-rw-r--r--app/graphql/types/user_state_enum.rb12
-rw-r--r--app/graphql/types/user_type.rb22
94 files changed, 1655 insertions, 163 deletions
diff --git a/app/graphql/mutations/alert_management/alerts/set_assignees.rb b/app/graphql/mutations/alert_management/alerts/set_assignees.rb
new file mode 100644
index 00000000000..1e0c9fdeeaf
--- /dev/null
+++ b/app/graphql/mutations/alert_management/alerts/set_assignees.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AlertManagement
+ module Alerts
+ class SetAssignees < Base
+ graphql_name 'AlertSetAssignees'
+
+ argument :assignee_usernames,
+ [GraphQL::STRING_TYPE],
+ required: true,
+ description: 'The usernames to assign to the alert. Replaces existing assignees by default.'
+
+ argument :operation_mode,
+ Types::MutationOperationModeEnum,
+ required: false,
+ description: 'The operation to perform. Defaults to REPLACE.'
+
+ def resolve(args)
+ alert = authorized_find!(project_path: args[:project_path], iid: args[:iid])
+ result = set_assignees(alert, args[:assignee_usernames], args[:operation_mode])
+
+ prepare_response(result)
+ end
+
+ private
+
+ def set_assignees(alert, assignee_usernames, operation_mode)
+ operation_mode ||= Types::MutationOperationModeEnum.enum[:replace]
+
+ original_assignees = alert.assignees
+ target_users = find_target_users(assignee_usernames)
+
+ assignees = case Types::MutationOperationModeEnum.enum.key(operation_mode).to_sym
+ when :replace then target_users.uniq
+ when :append then (original_assignees + target_users).uniq
+ when :remove then (original_assignees - target_users)
+ end
+
+ ::AlertManagement::Alerts::UpdateService.new(alert, current_user, assignees: assignees).execute
+ end
+
+ def find_target_users(assignee_usernames)
+ UsersFinder.new(current_user, username: assignee_usernames).execute
+ end
+
+ def prepare_response(result)
+ {
+ alert: result.payload[:alert],
+ errors: result.error? ? [result.message] : []
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/alert_management/base.rb b/app/graphql/mutations/alert_management/base.rb
index ca2057d4845..7fcca63db51 100644
--- a/app/graphql/mutations/alert_management/base.rb
+++ b/app/graphql/mutations/alert_management/base.rb
@@ -3,7 +3,7 @@
module Mutations
module AlertManagement
class Base < BaseMutation
- include Mutations::ResolvesProject
+ include ResolvesProject
argument :project_path, GraphQL::ID_TYPE,
required: true,
@@ -32,7 +32,7 @@ module Mutations
return unless project
- resolver = Resolvers::AlertManagementAlertResolver.single.new(object: project, context: context, field: nil)
+ resolver = Resolvers::AlertManagement::AlertResolver.single.new(object: project, context: context, field: nil)
resolver.resolve(iid: iid)
end
end
diff --git a/app/graphql/mutations/alert_management/update_alert_status.rb b/app/graphql/mutations/alert_management/update_alert_status.rb
index e73a591378a..d820124d26f 100644
--- a/app/graphql/mutations/alert_management/update_alert_status.rb
+++ b/app/graphql/mutations/alert_management/update_alert_status.rb
@@ -27,7 +27,7 @@ module Mutations
def prepare_response(result)
{
alert: result.payload[:alert],
- errors: result.error? ? [result.message] : []
+ errors: result.errors
}
end
end
diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb
index 30510cfab50..33f3f33a440 100644
--- a/app/graphql/mutations/base_mutation.rb
+++ b/app/graphql/mutations/base_mutation.rb
@@ -9,7 +9,7 @@ module Mutations
field :errors, [GraphQL::STRING_TYPE],
null: false,
- description: "Errors encountered during execution of the mutation."
+ description: 'Errors encountered during execution of the mutation.'
def current_user
context[:current_user]
diff --git a/app/graphql/mutations/branches/create.rb b/app/graphql/mutations/branches/create.rb
index 127d5447d0a..214fead2e80 100644
--- a/app/graphql/mutations/branches/create.rb
+++ b/app/graphql/mutations/branches/create.rb
@@ -3,7 +3,7 @@
module Mutations
module Branches
class Create < BaseMutation
- include Mutations::ResolvesProject
+ include ResolvesProject
graphql_name 'CreateBranch'
diff --git a/app/graphql/mutations/commits/create.rb b/app/graphql/mutations/commits/create.rb
new file mode 100644
index 00000000000..9ed1bb819c8
--- /dev/null
+++ b/app/graphql/mutations/commits/create.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Commits
+ class Create < BaseMutation
+ include ResolvesProject
+
+ graphql_name 'CommitCreate'
+
+ argument :project_path, GraphQL::ID_TYPE,
+ required: true,
+ description: 'Project full path the branch is associated with'
+
+ argument :branch, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Name of the branch'
+
+ argument :message,
+ GraphQL::STRING_TYPE,
+ required: true,
+ description: copy_field_description(Types::CommitType, :message)
+
+ argument :actions,
+ [Types::CommitActionType],
+ required: true,
+ description: 'Array of action hashes to commit as a batch'
+
+ field :commit,
+ Types::CommitType,
+ null: true,
+ description: 'The commit after mutation'
+
+ authorize :push_code
+
+ def resolve(project_path:, branch:, message:, actions:)
+ project = authorized_find!(full_path: project_path)
+
+ attributes = {
+ commit_message: message,
+ branch_name: branch,
+ start_branch: branch,
+ actions: actions.map { |action| action.to_h }
+ }
+
+ result = ::Files::MultiService.new(project, current_user, attributes).execute
+
+ {
+ commit: (project.repository.commit(result[:result]) if result[:status] == :success),
+ errors: Array.wrap(result[:message])
+ }
+ end
+
+ private
+
+ def find_object(full_path:)
+ resolve_project(full_path: full_path)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/concerns/mutations/resolves_issuable.rb b/app/graphql/mutations/concerns/mutations/resolves_issuable.rb
index d63cc27a450..13a56f2e709 100644
--- a/app/graphql/mutations/concerns/mutations/resolves_issuable.rb
+++ b/app/graphql/mutations/concerns/mutations/resolves_issuable.rb
@@ -3,12 +3,22 @@
module Mutations
module ResolvesIssuable
extend ActiveSupport::Concern
- include Mutations::ResolvesProject
+
+ included do
+ include ResolvesProject
+ 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 }
+
+ resolver = issuable_resolver(type, parent, context)
+ ready, early_return = resolver.ready?(**args)
+
+ return early_return unless ready
- issuable_resolver(type, parent, context).resolve(iid: iid.to_s)
+ resolver.resolve(**args)
end
private
@@ -22,7 +32,7 @@ module Mutations
def resolve_issuable_parent(type, parent_path)
return unless type == :issue || type == :merge_request
- resolve_project(full_path: parent_path)
+ resolve_project(full_path: parent_path) if parent_path.present?
end
end
end
diff --git a/app/graphql/mutations/concerns/mutations/resolves_project.rb b/app/graphql/mutations/concerns/mutations/resolves_project.rb
deleted file mode 100644
index e223e3edd94..00000000000
--- a/app/graphql/mutations/concerns/mutations/resolves_project.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-module Mutations
- module ResolvesProject
- extend ActiveSupport::Concern
-
- def resolve_project(full_path:)
- project_resolver.resolve(full_path: full_path)
- end
-
- def project_resolver
- Resolvers::ProjectResolver.new(object: nil, context: context, field: nil)
- end
- end
-end
diff --git a/app/graphql/mutations/container_expiration_policies/update.rb b/app/graphql/mutations/container_expiration_policies/update.rb
new file mode 100644
index 00000000000..c210571c6ca
--- /dev/null
+++ b/app/graphql/mutations/container_expiration_policies/update.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Mutations
+ module ContainerExpirationPolicies
+ class Update < Mutations::BaseMutation
+ include ResolvesProject
+
+ graphql_name 'UpdateContainerExpirationPolicy'
+
+ authorize :destroy_container_image
+
+ argument :project_path,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The project path where the container expiration policy is located'
+
+ argument :enabled,
+ GraphQL::BOOLEAN_TYPE,
+ required: false,
+ description: copy_field_description(Types::ContainerExpirationPolicyType, :enabled)
+
+ argument :cadence,
+ Types::ContainerExpirationPolicyCadenceEnum,
+ required: false,
+ description: copy_field_description(Types::ContainerExpirationPolicyType, :cadence)
+
+ argument :older_than,
+ Types::ContainerExpirationPolicyOlderThanEnum,
+ required: false,
+ description: copy_field_description(Types::ContainerExpirationPolicyType, :older_than)
+
+ argument :keep_n,
+ Types::ContainerExpirationPolicyKeepEnum,
+ required: false,
+ description: copy_field_description(Types::ContainerExpirationPolicyType, :keep_n)
+
+ field :container_expiration_policy,
+ Types::ContainerExpirationPolicyType,
+ null: true,
+ description: 'The container expiration policy after mutation'
+
+ def resolve(project_path:, **args)
+ project = authorized_find!(full_path: project_path)
+
+ result = ::ContainerExpirationPolicies::UpdateService
+ .new(container: project, current_user: current_user, params: args)
+ .execute
+
+ {
+ container_expiration_policy: result.payload[:container_expiration_policy],
+ errors: result.errors
+ }
+ end
+
+ private
+
+ def find_object(full_path:)
+ resolve_project(full_path: full_path)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/discussions/toggle_resolve.rb b/app/graphql/mutations/discussions/toggle_resolve.rb
new file mode 100644
index 00000000000..41fd22c6b55
--- /dev/null
+++ b/app/graphql/mutations/discussions/toggle_resolve.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Discussions
+ class ToggleResolve < BaseMutation
+ graphql_name 'DiscussionToggleResolve'
+
+ description 'Toggles the resolved state of a discussion'
+
+ argument :id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the discussion'
+
+ argument :resolve,
+ GraphQL::BOOLEAN_TYPE,
+ required: true,
+ description: 'Will resolve the discussion when true, and unresolve the discussion when false'
+
+ field :discussion,
+ Types::Notes::DiscussionType,
+ null: true,
+ description: 'The discussion after mutation'
+
+ def resolve(id:, resolve:)
+ discussion = authorized_find_discussion!(id: id)
+ errors = []
+
+ begin
+ if resolve
+ resolve!(discussion)
+ else
+ unresolve!(discussion)
+ end
+ rescue ActiveRecord::RecordNotSaved
+ errors << "Discussion failed to be #{'un' unless resolve}resolved"
+ end
+
+ {
+ discussion: discussion,
+ errors: errors
+ }
+ end
+
+ private
+
+ # `Discussion` permissions are checked through `Discussion#can_resolve?`,
+ # so we use this method of checking permissions rather than by defining
+ # an `authorize` permission and calling `authorized_find!`.
+ def authorized_find_discussion!(id:)
+ find_object(id: id).tap do |discussion|
+ raise_resource_not_available_error! unless discussion&.can_resolve?(current_user)
+ end
+ end
+
+ def find_object(id:)
+ GitlabSchema.object_from_id(id, expected_type: ::Discussion)
+ end
+
+ def resolve!(discussion)
+ ::Discussions::ResolveService.new(
+ discussion.project,
+ current_user,
+ one_or_more_discussions: discussion
+ ).execute
+ end
+
+ def unresolve!(discussion)
+ discussion.unresolve!
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/issues/set_confidential.rb b/app/graphql/mutations/issues/set_confidential.rb
index 0fff5518665..75befddc261 100644
--- a/app/graphql/mutations/issues/set_confidential.rb
+++ b/app/graphql/mutations/issues/set_confidential.rb
@@ -19,7 +19,7 @@ module Mutations
{
issue: issue,
- errors: issue.errors.full_messages
+ errors: errors_on_object(issue)
}
end
end
diff --git a/app/graphql/mutations/issues/set_due_date.rb b/app/graphql/mutations/issues/set_due_date.rb
index 1855c6f053b..effd863c541 100644
--- a/app/graphql/mutations/issues/set_due_date.rb
+++ b/app/graphql/mutations/issues/set_due_date.rb
@@ -19,7 +19,7 @@ module Mutations
{
issue: issue,
- errors: issue.errors.full_messages
+ errors: errors_on_object(issue)
}
end
end
diff --git a/app/graphql/mutations/issues/update.rb b/app/graphql/mutations/issues/update.rb
index 3710144fff5..7f6d9b0f988 100644
--- a/app/graphql/mutations/issues/update.rb
+++ b/app/graphql/mutations/issues/update.rb
@@ -33,7 +33,7 @@ module Mutations
{
issue: issue,
- errors: issue.errors.full_messages
+ errors: errors_on_object(issue)
}
end
end
diff --git a/app/graphql/mutations/jira_import/import_users.rb b/app/graphql/mutations/jira_import/import_users.rb
new file mode 100644
index 00000000000..c7225e1a99c
--- /dev/null
+++ b/app/graphql/mutations/jira_import/import_users.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Mutations
+ module JiraImport
+ class ImportUsers < BaseMutation
+ include ResolvesProject
+
+ graphql_name 'JiraImportUsers'
+
+ field :jira_users,
+ [Types::JiraUserType],
+ null: true,
+ description: 'Users returned from Jira, matched by email and name if possible.'
+
+ argument :project_path, GraphQL::ID_TYPE,
+ required: true,
+ description: 'The project to import the Jira users into'
+ argument :start_at, GraphQL::INT_TYPE,
+ required: false,
+ description: 'The index of the record the import should started at, default 0 (50 records returned)'
+
+ def resolve(project_path:, start_at:)
+ project = authorized_find!(full_path: project_path)
+
+ service_response = ::JiraImport::UsersImporter.new(context[:current_user], project, start_at).execute
+
+ {
+ jira_users: service_response.payload,
+ errors: service_response.errors
+ }
+ end
+
+ private
+
+ def find_object(full_path:)
+ resolve_project(full_path: full_path)
+ end
+
+ def authorized_resource?(project)
+ Ability.allowed?(context[:current_user], :admin_project, project)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/jira_import/start.rb b/app/graphql/mutations/jira_import/start.rb
index 6b80c9f8ca4..3df26d33711 100644
--- a/app/graphql/mutations/jira_import/start.rb
+++ b/app/graphql/mutations/jira_import/start.rb
@@ -3,7 +3,7 @@
module Mutations
module JiraImport
class Start < BaseMutation
- include Mutations::ResolvesProject
+ include ResolvesProject
graphql_name 'JiraImportStart'
@@ -23,29 +23,21 @@ module Mutations
description: 'Project name of the importer Jira project'
def resolve(project_path:, jira_project_key:)
- project = find_project!(project_path: project_path)
-
- raise_resource_not_available_error! unless project
+ project = authorized_find!(full_path: project_path)
service_response = ::JiraImport::StartImportService
.new(context[:current_user], project, jira_project_key)
.execute
jira_import = service_response.success? ? service_response.payload[:import_data] : nil
- errors = service_response.error? ? [service_response.message] : []
+
{
jira_import: jira_import,
- errors: errors
+ errors: service_response.errors
}
end
private
- def find_project!(project_path:)
- return unless project_path.present?
-
- authorized_find!(full_path: project_path)
- end
-
def find_object(full_path:)
resolve_project(full_path: full_path)
end
diff --git a/app/graphql/mutations/merge_requests/create.rb b/app/graphql/mutations/merge_requests/create.rb
new file mode 100644
index 00000000000..e210987f259
--- /dev/null
+++ b/app/graphql/mutations/merge_requests/create.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module Mutations
+ module MergeRequests
+ class Create < BaseMutation
+ include ResolvesProject
+
+ graphql_name 'MergeRequestCreate'
+
+ argument :project_path, GraphQL::ID_TYPE,
+ required: true,
+ description: 'Project full path the merge request is associated with'
+
+ argument :title, GraphQL::STRING_TYPE,
+ required: true,
+ description: copy_field_description(Types::MergeRequestType, :title)
+
+ argument :source_branch, GraphQL::STRING_TYPE,
+ required: true,
+ description: copy_field_description(Types::MergeRequestType, :source_branch)
+
+ argument :target_branch, GraphQL::STRING_TYPE,
+ required: true,
+ description: copy_field_description(Types::MergeRequestType, :target_branch)
+
+ argument :description, GraphQL::STRING_TYPE,
+ required: false,
+ description: copy_field_description(Types::MergeRequestType, :description)
+
+ field :merge_request,
+ Types::MergeRequestType,
+ null: true,
+ description: 'The merge request after mutation'
+
+ authorize :create_merge_request_from
+
+ def resolve(project_path:, title:, source_branch:, target_branch:, description: nil)
+ project = authorized_find!(full_path: project_path)
+
+ attributes = {
+ title: title,
+ source_branch: source_branch,
+ target_branch: target_branch,
+ author_id: current_user.id,
+ description: description
+ }
+
+ merge_request = ::MergeRequests::CreateService.new(project, current_user, attributes).execute
+
+ {
+ merge_request: merge_request.valid? ? merge_request : nil,
+ errors: errors_on_object(merge_request)
+ }
+ end
+
+ private
+
+ def find_object(full_path:)
+ resolve_project(full_path: full_path)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/merge_requests/set_assignees.rb b/app/graphql/mutations/merge_requests/set_assignees.rb
index 8f0025f0a58..de244b62d0f 100644
--- a/app/graphql/mutations/merge_requests/set_assignees.rb
+++ b/app/graphql/mutations/merge_requests/set_assignees.rb
@@ -40,7 +40,7 @@ module Mutations
{
merge_request: merge_request,
- errors: merge_request.errors.full_messages
+ errors: errors_on_object(merge_request)
}
end
end
diff --git a/app/graphql/mutations/merge_requests/set_labels.rb b/app/graphql/mutations/merge_requests/set_labels.rb
index 71f7a353bc9..c1e45808593 100644
--- a/app/graphql/mutations/merge_requests/set_labels.rb
+++ b/app/graphql/mutations/merge_requests/set_labels.rb
@@ -24,8 +24,9 @@ module Mutations
project = merge_request.project
label_ids = label_ids
+ .map { |gid| GlobalID.parse(gid) }
.select(&method(:label_descendant?))
- .map { |gid| GlobalID.parse(gid).model_id } # MergeRequests::UpdateService expects integers
+ .map(&:model_id) # MergeRequests::UpdateService expects integers
attribute_name = case operation_mode
when Types::MutationOperationModeEnum.enum[:append]
@@ -41,12 +42,12 @@ module Mutations
{
merge_request: merge_request,
- errors: merge_request.errors.full_messages
+ errors: errors_on_object(merge_request)
}
end
def label_descendant?(gid)
- GlobalID.parse(gid)&.model_class&.ancestors&.include?(Label)
+ gid&.model_class&.ancestors&.include?(Label)
end
end
end
diff --git a/app/graphql/mutations/merge_requests/set_locked.rb b/app/graphql/mutations/merge_requests/set_locked.rb
index 09aaa0b39aa..c49d5186a03 100644
--- a/app/graphql/mutations/merge_requests/set_locked.rb
+++ b/app/graphql/mutations/merge_requests/set_locked.rb
@@ -21,7 +21,7 @@ module Mutations
{
merge_request: merge_request,
- errors: merge_request.errors.full_messages
+ errors: errors_on_object(merge_request)
}
end
end
diff --git a/app/graphql/mutations/merge_requests/set_milestone.rb b/app/graphql/mutations/merge_requests/set_milestone.rb
index 707d6677952..b3412dd9ed2 100644
--- a/app/graphql/mutations/merge_requests/set_milestone.rb
+++ b/app/graphql/mutations/merge_requests/set_milestone.rb
@@ -22,7 +22,7 @@ module Mutations
{
merge_request: merge_request,
- errors: merge_request.errors.full_messages
+ errors: errors_on_object(merge_request)
}
end
end
diff --git a/app/graphql/mutations/merge_requests/set_subscription.rb b/app/graphql/mutations/merge_requests/set_subscription.rb
index 86750152775..1535481ab37 100644
--- a/app/graphql/mutations/merge_requests/set_subscription.rb
+++ b/app/graphql/mutations/merge_requests/set_subscription.rb
@@ -18,7 +18,7 @@ module Mutations
{
merge_request: merge_request,
- errors: merge_request.errors.full_messages
+ errors: errors_on_object(merge_request)
}
end
end
diff --git a/app/graphql/mutations/merge_requests/set_wip.rb b/app/graphql/mutations/merge_requests/set_wip.rb
index a2aa0c84ee4..5d2077c12f2 100644
--- a/app/graphql/mutations/merge_requests/set_wip.rb
+++ b/app/graphql/mutations/merge_requests/set_wip.rb
@@ -21,7 +21,7 @@ module Mutations
{
merge_request: merge_request,
- errors: merge_request.errors.full_messages
+ errors: errors_on_object(merge_request)
}
end
diff --git a/app/graphql/mutations/metrics/dashboard/annotations/base.rb b/app/graphql/mutations/metrics/dashboard/annotations/base.rb
new file mode 100644
index 00000000000..3126267da64
--- /dev/null
+++ b/app/graphql/mutations/metrics/dashboard/annotations/base.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Metrics
+ module Dashboard
+ module Annotations
+ class Base < BaseMutation
+ private
+
+ # This method is defined here in order to be used by `authorized_find!` in the subclasses.
+ def find_object(id:)
+ GitlabSchema.object_from_id(id)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/metrics/dashboard/annotations/delete.rb b/app/graphql/mutations/metrics/dashboard/annotations/delete.rb
new file mode 100644
index 00000000000..fb828ba0e2f
--- /dev/null
+++ b/app/graphql/mutations/metrics/dashboard/annotations/delete.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Metrics
+ module Dashboard
+ module Annotations
+ class Delete < Base
+ graphql_name 'DeleteAnnotation'
+
+ authorize :delete_metrics_dashboard_annotation
+
+ argument :id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the annotation to delete'
+
+ def resolve(id:)
+ annotation = authorized_find!(id: id)
+
+ result = ::Metrics::Dashboard::Annotations::DeleteService.new(context[:current_user], annotation).execute
+
+ errors = Array.wrap(result[:message])
+
+ {
+ errors: errors
+ }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/snippets/create.rb b/app/graphql/mutations/snippets/create.rb
index 6fc223fbee7..e1022358c09 100644
--- a/app/graphql/mutations/snippets/create.rb
+++ b/app/graphql/mutations/snippets/create.rb
@@ -3,7 +3,7 @@
module Mutations
module Snippets
class Create < BaseMutation
- include Mutations::ResolvesProject
+ include ResolvesProject
graphql_name 'CreateSnippet'
@@ -60,7 +60,7 @@ module Mutations
snippet = service_response.payload[:snippet]
{
- snippet: snippet.valid? ? snippet : nil,
+ snippet: service_response.success? ? snippet : nil,
errors: errors_on_object(snippet)
}
end
diff --git a/app/graphql/mutations/todos/mark_all_done.rb b/app/graphql/mutations/todos/mark_all_done.rb
index 5694985717c..d30d1bcbcf0 100644
--- a/app/graphql/mutations/todos/mark_all_done.rb
+++ b/app/graphql/mutations/todos/mark_all_done.rb
@@ -28,7 +28,9 @@ module Mutations
def mark_all_todos_done
return [] unless current_user
- TodoService.new.mark_all_todos_as_done_by_user(current_user)
+ todos = TodosFinder.new(current_user).execute
+
+ TodoService.new.resolve_todos(todos, current_user, resolved_by_action: :api_all_done)
end
end
end
diff --git a/app/graphql/mutations/todos/mark_done.rb b/app/graphql/mutations/todos/mark_done.rb
index d738e387c43..748e02d8782 100644
--- a/app/graphql/mutations/todos/mark_done.rb
+++ b/app/graphql/mutations/todos/mark_done.rb
@@ -30,7 +30,7 @@ module Mutations
private
def mark_done(todo)
- TodoService.new.mark_todo_as_done(todo, current_user)
+ TodoService.new.resolve_todo(todo, current_user, resolved_by_action: :api_done)
end
end
end
diff --git a/app/graphql/mutations/todos/restore.rb b/app/graphql/mutations/todos/restore.rb
index c4597bd84a2..a0a1772db0a 100644
--- a/app/graphql/mutations/todos/restore.rb
+++ b/app/graphql/mutations/todos/restore.rb
@@ -18,7 +18,7 @@ module Mutations
def resolve(id:)
todo = authorized_find!(id: id)
- restore(todo.id) if todo.done?
+ restore(todo)
{
todo: todo.reset,
@@ -28,8 +28,8 @@ module Mutations
private
- def restore(id)
- TodoService.new.mark_todos_as_pending_by_ids([id], current_user)
+ def restore(todo)
+ TodoService.new.restore_todo(todo, current_user)
end
end
end
diff --git a/app/graphql/mutations/todos/restore_many.rb b/app/graphql/mutations/todos/restore_many.rb
index 8a6265207cd..e95651b232f 100644
--- a/app/graphql/mutations/todos/restore_many.rb
+++ b/app/graphql/mutations/todos/restore_many.rb
@@ -68,7 +68,7 @@ module Mutations
end
def restore(todos)
- TodoService.new.mark_todos_as_pending(todos, current_user)
+ TodoService.new.restore_todos(todos, current_user)
end
end
end
diff --git a/app/graphql/resolvers/alert_management/alert_resolver.rb b/app/graphql/resolvers/alert_management/alert_resolver.rb
new file mode 100644
index 00000000000..71a7615685a
--- /dev/null
+++ b/app/graphql/resolvers/alert_management/alert_resolver.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module AlertManagement
+ class AlertResolver < BaseResolver
+ include LooksAhead
+
+ argument :iid, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'IID of the alert. For example, "1"'
+
+ argument :statuses, [Types::AlertManagement::StatusEnum],
+ as: :status,
+ required: false,
+ description: 'Alerts with the specified statues. For example, [TRIGGERED]'
+
+ argument :sort, Types::AlertManagement::AlertSortEnum,
+ description: 'Sort alerts by this criteria',
+ required: false
+
+ argument :search, GraphQL::STRING_TYPE,
+ description: 'Search criteria for filtering alerts. This will search on title, description, service, monitoring_tool.',
+ required: false
+
+ type Types::AlertManagement::AlertType, null: true
+
+ def resolve_with_lookahead(**args)
+ parent = object.respond_to?(:sync) ? object.sync : object
+ return ::AlertManagement::Alert.none if parent.nil?
+
+ apply_lookahead(::AlertManagement::AlertsFinder.new(context[:current_user], parent, args).execute)
+ end
+
+ def preloads
+ {
+ assignees: [:assignees],
+ notes: [:ordered_notes, { ordered_notes: [:system_note_metadata, :project, :noteable] }]
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb b/app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb
index 7f4346632ca..a45de21002f 100644
--- a/app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb
+++ b/app/graphql/resolvers/alert_management/alert_status_counts_resolver.rb
@@ -5,6 +5,10 @@ module Resolvers
class AlertStatusCountsResolver < BaseResolver
type Types::AlertManagement::AlertStatusCountsType, null: true
+ argument :search, GraphQL::STRING_TYPE,
+ description: 'Search criteria for filtering alerts. This will search on title, description, service, monitoring_tool.',
+ required: false
+
def resolve(**args)
::Gitlab::AlertManagement::AlertStatusCounts.new(context[:current_user], object, args)
end
diff --git a/app/graphql/resolvers/alert_management_alert_resolver.rb b/app/graphql/resolvers/alert_management_alert_resolver.rb
deleted file mode 100644
index 51ebbb96476..00000000000
--- a/app/graphql/resolvers/alert_management_alert_resolver.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-module Resolvers
- class AlertManagementAlertResolver < BaseResolver
- argument :iid, GraphQL::STRING_TYPE,
- required: false,
- description: 'IID of the alert. For example, "1"'
-
- argument :statuses, [Types::AlertManagement::StatusEnum],
- as: :status,
- required: false,
- description: 'Alerts with the specified statues. For example, [TRIGGERED]'
-
- argument :sort, Types::AlertManagement::AlertSortEnum,
- description: 'Sort alerts by this criteria',
- required: false
-
- argument :search, GraphQL::STRING_TYPE,
- description: 'Search criteria for filtering alerts. This will search on title, description, service, monitoring_tool.',
- required: false
-
- type Types::AlertManagement::AlertType, null: true
-
- def resolve(**args)
- parent = object.respond_to?(:sync) ? object.sync : object
- return ::AlertManagement::Alert.none if parent.nil?
-
- ::AlertManagement::AlertsFinder.new(context[:current_user], parent, args).execute
- end
- end
-end
diff --git a/app/graphql/resolvers/assigned_merge_requests_resolver.rb b/app/graphql/resolvers/assigned_merge_requests_resolver.rb
new file mode 100644
index 00000000000..fa08b142a7e
--- /dev/null
+++ b/app/graphql/resolvers/assigned_merge_requests_resolver.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class AssignedMergeRequestsResolver < UserMergeRequestsResolver
+ def user_role
+ :assignee
+ end
+ end
+end
diff --git a/app/graphql/resolvers/authored_merge_requests_resolver.rb b/app/graphql/resolvers/authored_merge_requests_resolver.rb
new file mode 100644
index 00000000000..e19bc9e8715
--- /dev/null
+++ b/app/graphql/resolvers/authored_merge_requests_resolver.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class AuthoredMergeRequestsResolver < UserMergeRequestsResolver
+ def user_role
+ :author
+ end
+ end
+end
diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb
index cf0642930ad..7daff68c069 100644
--- a/app/graphql/resolvers/base_resolver.rb
+++ b/app/graphql/resolvers/base_resolver.rb
@@ -3,27 +3,33 @@
module Resolvers
class BaseResolver < GraphQL::Schema::Resolver
extend ::Gitlab::Utils::Override
+ include ::Gitlab::Utils::StrongMemoize
def self.single
@single ||= Class.new(self) do
+ def ready?(**args)
+ ready, early_return = super
+ [ready, select_result(early_return)]
+ end
+
def resolve(**args)
- super.first
+ select_result(super)
end
def single?
true
end
+
+ def select_result(results)
+ results&.first
+ end
end
end
def self.last
- @last ||= Class.new(self) do
- def resolve(**args)
- super.last
- end
-
- def single?
- true
+ @last ||= Class.new(self.single) do
+ def select_result(results)
+ results&.last
end
end
end
@@ -59,6 +65,17 @@ module Resolvers
end
end
+ def synchronized_object
+ strong_memoize(:synchronized_object) do
+ case object
+ when BatchLoader::GraphQL
+ object.sync
+ else
+ object
+ end
+ end
+ end
+
def single?
false
end
diff --git a/app/graphql/resolvers/concerns/looks_ahead.rb b/app/graphql/resolvers/concerns/looks_ahead.rb
new file mode 100644
index 00000000000..becc6debd33
--- /dev/null
+++ b/app/graphql/resolvers/concerns/looks_ahead.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module LooksAhead
+ extend ActiveSupport::Concern
+
+ FEATURE_FLAG = :graphql_lookahead_support
+
+ included do
+ attr_accessor :lookahead
+ end
+
+ def resolve(**args)
+ self.lookahead = args.delete(:lookahead)
+
+ resolve_with_lookahead(**args)
+ end
+
+ def apply_lookahead(query)
+ return query unless Feature.enabled?(FEATURE_FLAG)
+
+ selection = node_selection
+
+ includes = preloads.each.flat_map do |name, requirements|
+ selection&.selects?(name) ? requirements : []
+ end
+ preloads = (unconditional_includes + includes).uniq
+
+ return query if preloads.empty?
+
+ query.preload(*preloads) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
+ private
+
+ def unconditional_includes
+ []
+ end
+
+ def preloads
+ {}
+ end
+
+ def node_selection
+ return unless lookahead
+
+ if lookahead.selects?(:nodes)
+ lookahead.selection(:nodes)
+ elsif lookahead.selects?(:edges)
+ lookahead.selection(:edges).selection(:nodes)
+ end
+ end
+end
diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
new file mode 100644
index 00000000000..a2140728a27
--- /dev/null
+++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+# Mixin for resolving merge requests. All arguments must be in forms
+# that `MergeRequestsFinder` can handle, so you may need to use aliasing.
+module ResolvesMergeRequests
+ extend ActiveSupport::Concern
+ include LooksAhead
+
+ included do
+ type Types::MergeRequestType, null: true
+ end
+
+ def resolve_with_lookahead(**args)
+ args[:iids] = Array.wrap(args[:iids]) if args[:iids]
+ args.compact!
+
+ 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))
+ end
+
+ def ready?(**args)
+ return early_return if no_results_possible?(args)
+
+ super
+ end
+
+ def early_return
+ [false, single? ? nil : MergeRequest.none]
+ end
+
+ 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
+
+ def preloads
+ {
+ assignees: [:assignees],
+ labels: [:labels],
+ author: [:author],
+ milestone: [:milestone],
+ head_pipeline: [:merge_request_diff, { head_pipeline: [:merge_request] }]
+ }
+ end
+end
diff --git a/app/graphql/resolvers/concerns/resolves_project.rb b/app/graphql/resolvers/concerns/resolves_project.rb
new file mode 100644
index 00000000000..3c5ce3dab01
--- /dev/null
+++ b/app/graphql/resolvers/concerns/resolves_project.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module ResolvesProject
+ def resolve_project(full_path: nil, project_id: nil)
+ unless full_path.present? ^ project_id.present?
+ raise ::Gitlab::Graphql::Errors::ArgumentError, 'Incompatible arguments: projectId, projectPath.'
+ end
+
+ if full_path.present?
+ ::Gitlab::Graphql::Loaders::FullPathModelLoader.new(Project, full_path).find
+ else
+ ::GitlabSchema.object_from_id(project_id, expected_type: Project)
+ end
+ end
+end
diff --git a/app/graphql/resolvers/full_path_resolver.rb b/app/graphql/resolvers/full_path_resolver.rb
index 46d3360baae..cbb0bf998a6 100644
--- a/app/graphql/resolvers/full_path_resolver.rb
+++ b/app/graphql/resolvers/full_path_resolver.rb
@@ -11,12 +11,7 @@ module Resolvers
end
def model_by_full_path(model, full_path)
- BatchLoader::GraphQL.for(full_path).batch(key: model) do |full_paths, loader, args|
- # `with_route` avoids an N+1 calculating full_path
- args[:key].where_full_path_in(full_paths).with_route.each do |model_instance|
- loader.call(model_instance.full_path, model_instance)
- end
- end
+ ::Gitlab::Graphql::Loaders::FullPathModelLoader.new(model, full_path).find
end
end
end
diff --git a/app/graphql/resolvers/merge_request_resolver.rb b/app/graphql/resolvers/merge_request_resolver.rb
new file mode 100644
index 00000000000..a47a128ea32
--- /dev/null
+++ b/app/graphql/resolvers/merge_request_resolver.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class MergeRequestResolver < BaseResolver.single
+ include ResolvesMergeRequests
+
+ alias_method :project, :synchronized_object
+
+ argument :iid, GraphQL::STRING_TYPE,
+ required: true,
+ as: :iids,
+ description: 'IID of the merge request, for example `1`'
+
+ def no_results_possible?(args)
+ project.nil?
+ end
+ end
+end
diff --git a/app/graphql/resolvers/merge_requests_resolver.rb b/app/graphql/resolvers/merge_requests_resolver.rb
index 25121dce005..3aa52341eec 100644
--- a/app/graphql/resolvers/merge_requests_resolver.rb
+++ b/app/graphql/resolvers/merge_requests_resolver.rb
@@ -2,47 +2,43 @@
module Resolvers
class MergeRequestsResolver < BaseResolver
- argument :iid, GraphQL::STRING_TYPE,
- required: false,
- description: 'IID of the merge request, for example `1`'
+ include ResolvesMergeRequests
+
+ alias_method :project, :synchronized_object
argument :iids, [GraphQL::STRING_TYPE],
required: false,
description: 'Array of IIDs of merge requests, for example `[1, 2]`'
- type Types::MergeRequestType, null: true
+ argument :source_branches, [GraphQL::STRING_TYPE],
+ required: false,
+ as: :source_branch,
+ description: 'Array of source branch names. All resolved merge requests will have one of these branches as their source.'
- alias_method :project, :object
+ argument :target_branches, [GraphQL::STRING_TYPE],
+ required: false,
+ as: :target_branch,
+ description: 'Array of target branch names. All resolved merge requests will have one of these branches as their target.'
- def resolve(**args)
- project = object.respond_to?(:sync) ? object.sync : object
- return MergeRequest.none if project.nil?
+ argument :state, ::Types::MergeRequestStateEnum,
+ required: false,
+ description: 'A merge request state. If provided, all resolved merge requests will have this state.'
- args[:iids] ||= [args[:iid]].compact
+ argument :labels, [GraphQL::STRING_TYPE],
+ required: false,
+ as: :label_name,
+ description: 'Array of label names. All resolved merge requests will have all of these labels.'
- if args[:iids].any?
- batch_load_merge_requests(args[:iids])
- else
- args[:project_id] = project.id
-
- MergeRequestsFinder.new(context[:current_user], args).execute
- end
+ def self.single
+ ::Resolvers::MergeRequestResolver
end
- def batch_load_merge_requests(iids)
- iids.map { |iid| batch_load(iid) }.select(&:itself) # .compact doesn't work on BatchLoader
+ def no_results_possible?(args)
+ project.nil? || some_argument_is_empty?(args)
end
- # rubocop: disable CodeReuse/ActiveRecord
- def batch_load(iid)
- BatchLoader::GraphQL.for(iid.to_s).batch(key: project) do |iids, loader, args|
- arg_key = args[:key].respond_to?(:sync) ? args[:key].sync : args[:key]
-
- arg_key.merge_requests.where(iid: iids).each do |mr|
- loader.call(mr.iid.to_s, mr)
- end
- end
+ def some_argument_is_empty?(args)
+ args.values.any? { |v| v.is_a?(Array) && v.empty? }
end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/graphql/resolvers/project_members_resolver.rb b/app/graphql/resolvers/project_members_resolver.rb
new file mode 100644
index 00000000000..3846531762e
--- /dev/null
+++ b/app/graphql/resolvers/project_members_resolver.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class ProjectMembersResolver < BaseResolver
+ argument :search, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Search query'
+
+ type Types::ProjectMemberType, null: true
+
+ alias_method :project, :object
+
+ def resolve(**args)
+ return Member.none unless project.present?
+
+ MembersFinder
+ .new(project, context[:current_user], params: args)
+ .execute
+ end
+ end
+end
diff --git a/app/graphql/resolvers/project_pipeline_resolver.rb b/app/graphql/resolvers/project_pipeline_resolver.rb
new file mode 100644
index 00000000000..5bafe3dd140
--- /dev/null
+++ b/app/graphql/resolvers/project_pipeline_resolver.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class ProjectPipelineResolver < BaseResolver
+ alias_method :project, :object
+
+ argument :iid, GraphQL::ID_TYPE,
+ required: true,
+ description: 'IID of the Pipeline, e.g., "1"'
+
+ def resolve(iid:)
+ BatchLoader::GraphQL.for(iid).batch(key: project) do |iids, loader, args|
+ args[:key].ci_pipelines.for_iid(iids).each { |pl| loader.call(pl.iid.to_s, pl) }
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/projects/jira_imports_resolver.rb b/app/graphql/resolvers/projects/jira_imports_resolver.rb
index 25361c068d9..aa9b7139f38 100644
--- a/app/graphql/resolvers/projects/jira_imports_resolver.rb
+++ b/app/graphql/resolvers/projects/jira_imports_resolver.rb
@@ -14,8 +14,6 @@ module Resolvers
end
def authorized_resource?(project)
- return false unless project.jira_issues_import_feature_flag_enabled?
-
context[:current_user].present? && Ability.allowed?(context[:current_user], :read_project, project)
end
end
diff --git a/app/graphql/resolvers/projects/jira_projects_resolver.rb b/app/graphql/resolvers/projects/jira_projects_resolver.rb
new file mode 100644
index 00000000000..a8c3768df41
--- /dev/null
+++ b/app/graphql/resolvers/projects/jira_projects_resolver.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Projects
+ class JiraProjectsResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ argument :name,
+ GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Project name or key'
+
+ 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]
+
+ if response.success?
+ Gitlab::Graphql::ExternallyPaginatedArray.new(start_cursor, end_cursor, *response.payload[:projects])
+ else
+ raise Gitlab::Graphql::Errors::BaseError, response.message
+ end
+ end
+
+ def authorized_resource?(project)
+ Ability.allowed?(context[:current_user], :admin_project, project)
+ end
+
+ private
+
+ alias_method :jira_service, :object
+
+ def project
+ 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)
+
+ # 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]
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/user_merge_requests_resolver.rb b/app/graphql/resolvers/user_merge_requests_resolver.rb
new file mode 100644
index 00000000000..b0d6e159f73
--- /dev/null
+++ b/app/graphql/resolvers/user_merge_requests_resolver.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class UserMergeRequestsResolver < MergeRequestsResolver
+ include ResolvesProject
+
+ argument :project_path, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'The full-path of the project the authored merge requests should be in. Incompatible with projectId.'
+
+ argument :project_id, GraphQL::ID_TYPE,
+ required: false,
+ description: 'The global ID of the project the authored merge requests should be in. Incompatible with projectPath.'
+
+ attr_reader :project
+ alias_method :user, :synchronized_object
+
+ def ready?(project_id: nil, project_path: nil, **args)
+ return early_return unless can_read_profile?
+
+ if project_id || project_path
+ load_project(project_path, project_id)
+ return early_return unless can_read_project?
+ elsif args[:iids].present?
+ raise ::Gitlab::Graphql::Errors::ArgumentError,
+ 'iids requires projectPath or projectId'
+ end
+
+ super(**args)
+ end
+
+ def resolve(**args)
+ prepare_args(args)
+ key = :"#{user_role}_id"
+ super(key => user.id, **args)
+ end
+
+ def user_role
+ raise NotImplementedError
+ end
+
+ private
+
+ def can_read_profile?
+ Ability.allowed?(current_user, :read_user_profile, user)
+ end
+
+ def can_read_project?
+ Ability.allowed?(current_user, :read_merge_request, project)
+ end
+
+ def load_project(project_path, project_id)
+ @project = resolve_project(full_path: project_path, project_id: project_id)
+ @project = @project.sync if @project.respond_to?(:sync)
+ end
+
+ def no_results_possible?(args)
+ some_argument_is_empty?(args)
+ end
+
+ # These arguments are handled in load_project, and should not be passed to
+ # the finder directly.
+ def prepare_args(args)
+ args.delete(:project_id)
+ args.delete(:project_path)
+ end
+ end
+end
diff --git a/app/graphql/resolvers/user_resolver.rb b/app/graphql/resolvers/user_resolver.rb
new file mode 100644
index 00000000000..a34cecba491
--- /dev/null
+++ b/app/graphql/resolvers/user_resolver.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class UserResolver < BaseResolver
+ description 'Retrieve a single user'
+
+ type Types::UserType, null: true
+
+ argument :id, GraphQL::ID_TYPE,
+ required: false,
+ description: 'ID of the User'
+
+ argument :username, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'Username of the User'
+
+ def ready?(id: nil, username: nil)
+ unless id.present? ^ username.present?
+ raise Gitlab::Graphql::Errors::ArgumentError, 'Provide either a single username or id'
+ end
+
+ super
+ end
+
+ def resolve(id: nil, username: nil)
+ if id
+ GitlabSchema.object_from_id(id, expected_type: User)
+ else
+ batch_load(username)
+ end
+ end
+
+ private
+
+ def batch_load(username)
+ BatchLoader::GraphQL.for(username).batch do |usernames, loader|
+ User.by_username(usernames).each do |user|
+ loader.call(user.username, user)
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/users_resolver.rb b/app/graphql/resolvers/users_resolver.rb
new file mode 100644
index 00000000000..110a283b42e
--- /dev/null
+++ b/app/graphql/resolvers/users_resolver.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class UsersResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ description 'Find Users'
+
+ argument :ids, [GraphQL::ID_TYPE],
+ required: false,
+ description: 'List of user Global IDs'
+
+ argument :usernames, [GraphQL::STRING_TYPE], required: false,
+ description: 'List of usernames'
+
+ argument :sort, Types::SortEnum,
+ description: 'Sort users by this criteria',
+ required: false,
+ default_value: 'created_desc'
+
+ def resolve(ids: nil, usernames: nil, sort: nil)
+ authorize!
+
+ ::UsersFinder.new(context[:current_user], finder_params(ids, usernames, sort)).execute
+ end
+
+ def ready?(**args)
+ args = { ids: nil, usernames: nil }.merge!(args)
+
+ return super if args.values.compact.blank?
+
+ if args.values.all?
+ raise Gitlab::Graphql::Errors::ArgumentError, 'Provide either a list of usernames or ids'
+ end
+
+ super
+ end
+
+ def authorize!
+ Ability.allowed?(context[:current_user], :read_users_list) || raise_resource_not_available_error!
+ end
+
+ private
+
+ def finder_params(ids, usernames, sort)
+ params = {}
+ params[:sort] = sort if sort
+ params[:username] = usernames if usernames
+ params[:id] = parse_gids(ids) if ids
+ params
+ end
+
+ def parse_gids(gids)
+ gids.map { |gid| GitlabSchema.parse_gid(gid, expected_type: ::User).model_id }
+ end
+ end
+end
diff --git a/app/graphql/types/access_level_enum.rb b/app/graphql/types/access_level_enum.rb
new file mode 100644
index 00000000000..6754d3d28ce
--- /dev/null
+++ b/app/graphql/types/access_level_enum.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Types
+ class AccessLevelEnum < BaseEnum
+ graphql_name 'AccessLevelEnum'
+ description 'Access level to a resource'
+
+ value 'NO_ACCESS', value: Gitlab::Access::NO_ACCESS
+ value 'GUEST', value: Gitlab::Access::GUEST
+ value 'REPORTER', value: Gitlab::Access::REPORTER
+ value 'DEVELOPER', value: Gitlab::Access::DEVELOPER
+ value 'MAINTAINER', value: Gitlab::Access::MAINTAINER
+ value 'OWNER', value: Gitlab::Access::OWNER
+ end
+end
diff --git a/app/graphql/types/access_level_type.rb b/app/graphql/types/access_level_type.rb
new file mode 100644
index 00000000000..c7f915f5038
--- /dev/null
+++ b/app/graphql/types/access_level_type.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+# rubocop:disable Graphql/AuthorizeTypes
+
+module Types
+ class AccessLevelType < Types::BaseObject
+ graphql_name 'AccessLevel'
+ description 'Represents the access level of a relationship between a User and object that it is related to'
+
+ field :integer_value, GraphQL::INT_TYPE, null: true,
+ description: 'Integer representation of access level',
+ method: :to_i
+
+ field :string_value, Types::AccessLevelEnum, null: true,
+ description: 'String representation of access level',
+ method: :to_i
+ end
+end
diff --git a/app/graphql/types/alert_management/alert_sort_enum.rb b/app/graphql/types/alert_management/alert_sort_enum.rb
index e6d38af8170..3faac9ce53c 100644
--- a/app/graphql/types/alert_management/alert_sort_enum.rb
+++ b/app/graphql/types/alert_management/alert_sort_enum.rb
@@ -6,16 +6,16 @@ module Types
graphql_name 'AlertManagementAlertSort'
description 'Values for sorting alerts'
- value 'START_TIME_ASC', 'Start time by ascending order', value: :start_time_asc
- value 'START_TIME_DESC', 'Start time by descending order', value: :start_time_desc
- value 'END_TIME_ASC', 'End time by ascending order', value: :end_time_asc
- value 'END_TIME_DESC', 'End time by descending order', value: :end_time_desc
+ value 'STARTED_AT_ASC', 'Start time by ascending order', value: :started_at_asc
+ value 'STARTED_AT_DESC', 'Start time by descending order', value: :started_at_desc
+ value 'ENDED_AT_ASC', 'End time by ascending order', value: :ended_at_asc
+ value 'ENDED_AT_DESC', 'End time by descending order', value: :ended_at_desc
value 'CREATED_TIME_ASC', 'Created time by ascending order', value: :created_at_asc
value 'CREATED_TIME_DESC', 'Created time by descending order', value: :created_at_desc
value 'UPDATED_TIME_ASC', 'Created time by ascending order', value: :updated_at_asc
value 'UPDATED_TIME_DESC', 'Created time by descending order', value: :updated_at_desc
- value 'EVENTS_COUNT_ASC', 'Events count by ascending order', value: :events_count_asc
- value 'EVENTS_COUNT_DESC', 'Events count by descending order', value: :events_count_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
diff --git a/app/graphql/types/alert_management/alert_type.rb b/app/graphql/types/alert_management/alert_type.rb
index a766fb3236d..8215ccb152c 100644
--- a/app/graphql/types/alert_management/alert_type.rb
+++ b/app/graphql/types/alert_management/alert_type.rb
@@ -6,6 +6,8 @@ module Types
graphql_name 'AlertManagementAlert'
description "Describes an alert from the project's Alert Management"
+ implements(Types::Notes::NoteableType)
+
authorize :read_alert_management_alert
field :iid,
@@ -83,6 +85,15 @@ module Types
Types::TimeType,
null: true,
description: 'Timestamp the alert was last updated'
+
+ field :assignees,
+ Types::UserType.connection_type,
+ null: true,
+ description: 'Assignees of the alert'
+
+ def notes
+ object.ordered_notes
+ end
end
end
end
diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb
index dad16898ba6..70e665f8fc3 100644
--- a/app/graphql/types/base_object.rb
+++ b/app/graphql/types/base_object.rb
@@ -12,5 +12,9 @@ module Types
def id
GitlabSchema.id_from_object(object)
end
+
+ def current_user
+ context[:current_user]
+ end
end
end
diff --git a/app/graphql/types/board_type.rb b/app/graphql/types/board_type.rb
index c0be782ed1e..f5dc9e08427 100644
--- a/app/graphql/types/board_type.rb
+++ b/app/graphql/types/board_type.rb
@@ -15,7 +15,7 @@ module Types
field :lists,
Types::BoardListType.connection_type,
null: true,
- description: 'Lists of the project board',
+ description: 'Lists of the board',
resolver: Resolvers::BoardListsResolver,
extras: [:lookahead]
end
diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb
index d77b2a2ba32..32050766e5b 100644
--- a/app/graphql/types/ci/pipeline_type.rb
+++ b/app/graphql/types/ci/pipeline_type.rb
@@ -42,3 +42,5 @@ module Types
end
end
end
+
+Types::Ci::PipelineType.prepend_if_ee('::EE::Types::Ci::PipelineType')
diff --git a/app/graphql/types/commit_action_mode_enum.rb b/app/graphql/types/commit_action_mode_enum.rb
new file mode 100644
index 00000000000..77658a85b51
--- /dev/null
+++ b/app/graphql/types/commit_action_mode_enum.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ class CommitActionModeEnum < BaseEnum
+ graphql_name 'CommitActionMode'
+ description 'Mode of a commit action'
+
+ value 'CREATE', description: 'Create command', value: :create
+ value 'DELETE', description: 'Delete command', value: :delete
+ value 'MOVE', description: 'Move command', value: :move
+ value 'UPDATE', description: 'Update command', value: :update
+ value 'CHMOD', description: 'Chmod command', value: :chmod
+ end
+end
diff --git a/app/graphql/types/commit_action_type.rb b/app/graphql/types/commit_action_type.rb
new file mode 100644
index 00000000000..7674abb11eb
--- /dev/null
+++ b/app/graphql/types/commit_action_type.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ class CommitActionType < BaseInputObject
+ argument :action, type: Types::CommitActionModeEnum, required: true,
+ description: 'The action to perform, create, delete, move, update, chmod'
+ argument :file_path, type: GraphQL::STRING_TYPE, required: true,
+ description: 'Full path to the file'
+ argument :content, type: GraphQL::STRING_TYPE, required: false,
+ description: 'Content of the file'
+ argument :previous_path, type: GraphQL::STRING_TYPE, required: false,
+ description: 'Original full path to the file being moved'
+ argument :last_commit_id, type: GraphQL::STRING_TYPE, required: false,
+ description: 'Last known file commit ID'
+ argument :execute_filemode, type: GraphQL::BOOLEAN_TYPE, required: false,
+ description: 'Enables/disables the execute flag on the file'
+ argument :encoding, type: Types::CommitEncodingEnum, required: false,
+ description: 'Encoding of the file. Default is text'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+end
diff --git a/app/graphql/types/commit_encoding_enum.rb b/app/graphql/types/commit_encoding_enum.rb
new file mode 100644
index 00000000000..0ea89b82db7
--- /dev/null
+++ b/app/graphql/types/commit_encoding_enum.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Types
+ class CommitEncodingEnum < BaseEnum
+ graphql_name 'CommitEncoding'
+
+ value 'TEXT', description: 'Text encoding', value: :text
+ value 'BASE64', description: 'Base64 encoding', value: :base64
+ end
+end
diff --git a/app/graphql/types/container_expiration_policy_cadence_enum.rb b/app/graphql/types/container_expiration_policy_cadence_enum.rb
new file mode 100644
index 00000000000..bb8bdf2197b
--- /dev/null
+++ b/app/graphql/types/container_expiration_policy_cadence_enum.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Types
+ class ContainerExpirationPolicyCadenceEnum < BaseEnum
+ OPTIONS_MAPPING = {
+ '1d': 'EVERY_DAY',
+ '7d': 'EVERY_WEEK',
+ '14d': 'EVERY_TWO_WEEKS',
+ '1month': 'EVERY_MONTH',
+ '3month': 'EVERY_THREE_MONTHS'
+ }.freeze
+
+ ::ContainerExpirationPolicy.cadence_options.each do |option, description|
+ value OPTIONS_MAPPING[option], description, value: option.to_s
+ end
+ end
+end
diff --git a/app/graphql/types/container_expiration_policy_keep_enum.rb b/app/graphql/types/container_expiration_policy_keep_enum.rb
new file mode 100644
index 00000000000..7632df61092
--- /dev/null
+++ b/app/graphql/types/container_expiration_policy_keep_enum.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Types
+ class ContainerExpirationPolicyKeepEnum < BaseEnum
+ OPTIONS_MAPPING = {
+ 1 => 'ONE_TAG',
+ 5 => 'FIVE_TAGS',
+ 10 => 'TEN_TAGS',
+ 25 => 'TWENTY_FIVE_TAGS',
+ 50 => 'FIFTY_TAGS',
+ 100 => 'ONE_HUNDRED_TAGS'
+ }.freeze
+
+ ::ContainerExpirationPolicy.keep_n_options.each do |option, description|
+ value OPTIONS_MAPPING[option], description, value: option
+ end
+ end
+end
diff --git a/app/graphql/types/container_expiration_policy_older_than_enum.rb b/app/graphql/types/container_expiration_policy_older_than_enum.rb
new file mode 100644
index 00000000000..da70534b0d7
--- /dev/null
+++ b/app/graphql/types/container_expiration_policy_older_than_enum.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Types
+ class ContainerExpirationPolicyOlderThanEnum < BaseEnum
+ OPTIONS_MAPPING = {
+ '7d': 'SEVEN_DAYS',
+ '14d': 'FOURTEEN_DAYS',
+ '30d': 'THIRTY_DAYS',
+ '90d': 'NINETY_DAYS'
+ }.freeze
+
+ ::ContainerExpirationPolicy.older_than_options.each do |option, description|
+ value OPTIONS_MAPPING[option], description, value: option.to_s
+ end
+ end
+end
diff --git a/app/graphql/types/container_expiration_policy_type.rb b/app/graphql/types/container_expiration_policy_type.rb
new file mode 100644
index 00000000000..da53dbcbd39
--- /dev/null
+++ b/app/graphql/types/container_expiration_policy_type.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Types
+ class ContainerExpirationPolicyType < BaseObject
+ graphql_name 'ContainerExpirationPolicy'
+
+ description 'A tag expiration policy designed to keep only the images that matter most'
+
+ authorize :destroy_container_image
+
+ field :created_at, Types::TimeType, null: false, description: 'Timestamp of when the container expiration policy was created'
+ field :updated_at, Types::TimeType, null: false, description: 'Timestamp of when the container expiration policy was updated'
+ field :enabled, GraphQL::BOOLEAN_TYPE, null: false, description: 'Indicates whether this container expiration policy is enabled'
+ 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 :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/evidence_type.rb b/app/graphql/types/evidence_type.rb
new file mode 100644
index 00000000000..a2fc9953c67
--- /dev/null
+++ b/app/graphql/types/evidence_type.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Types
+ class EvidenceType < BaseObject
+ graphql_name 'ReleaseEvidence'
+ description 'Evidence for a release'
+
+ authorize :download_code
+
+ present_using Releases::EvidencePresenter
+
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: 'ID of the evidence'
+ field :sha, GraphQL::STRING_TYPE, null: true,
+ description: 'SHA1 ID of the evidence hash'
+ field :filepath, GraphQL::STRING_TYPE, null: true,
+ description: 'URL from where the evidence can be downloaded'
+ field :collected_at, Types::TimeType, null: true,
+ description: 'Timestamp when the evidence was collected'
+ end
+end
diff --git a/app/graphql/types/group_member_type.rb b/app/graphql/types/group_member_type.rb
new file mode 100644
index 00000000000..ffffa3247db
--- /dev/null
+++ b/app/graphql/types/group_member_type.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Types
+ class GroupMemberType < BaseObject
+ expose_permissions Types::PermissionTypes::Group
+ authorize :read_group
+
+ implements MemberInterface
+
+ graphql_name 'GroupMember'
+ description 'Represents a Group Member'
+
+ field :group, Types::GroupType, null: true,
+ description: 'Group that a User is a member of',
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.source_id).find }
+ end
+end
diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb
index 20b4c66ba95..fd7d9a9ba3d 100644
--- a/app/graphql/types/group_type.rb
+++ b/app/graphql/types/group_type.rb
@@ -65,6 +65,45 @@ module Types
null: true,
description: 'A single board of the group',
resolver: Resolvers::BoardsResolver.single
+
+ field :label,
+ Types::LabelType,
+ null: true,
+ description: 'A label available on this group' do
+ argument :title, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Title of the label'
+ end
+
+ def label(title:)
+ BatchLoader::GraphQL.for(title).batch(key: group) do |titles, loader, args|
+ LabelsFinder
+ .new(current_user, group: args[:key], title: titles)
+ .execute
+ .each { |label| loader.call(label.title, label) }
+ end
+ end
+
+ field :labels,
+ Types::LabelType.connection_type,
+ null: true,
+ description: 'Labels available on this group' do
+ argument :search_term, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'A search term to find labels with'
+ end
+
+ def labels(search_term: nil)
+ LabelsFinder
+ .new(current_user, group: group, search: search_term)
+ .execute
+ end
+
+ private
+
+ def group
+ object.respond_to?(:sync) ? object.sync : object
+ end
end
end
diff --git a/app/graphql/types/jira_import_type.rb b/app/graphql/types/jira_import_type.rb
index 4a124566ffb..cf58a53b40d 100644
--- a/app/graphql/types/jira_import_type.rb
+++ b/app/graphql/types/jira_import_type.rb
@@ -15,6 +15,12 @@ module Types
description: 'User that started the Jira import'
field :jira_project_key, GraphQL::STRING_TYPE, null: false,
description: 'Project key for the imported Jira project'
+ field :imported_issues_count, GraphQL::INT_TYPE, null: false,
+ description: 'Count of issues that were successfully imported'
+ field :failed_to_import_count, GraphQL::INT_TYPE, null: false,
+ description: 'Count of issues that failed to import'
+ field :total_issue_count, GraphQL::INT_TYPE, null: false,
+ description: 'Total count of issues that were attempted to import'
end
# rubocop: enable Graphql/AuthorizeTypes
end
diff --git a/app/graphql/types/jira_user_type.rb b/app/graphql/types/jira_user_type.rb
new file mode 100644
index 00000000000..8aa21ce669b
--- /dev/null
+++ b/app/graphql/types/jira_user_type.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ # Authorization is at project level for owners or admins on mutation level
+ class JiraUserType < BaseObject
+ graphql_name 'JiraUser'
+
+ field :jira_account_id, GraphQL::STRING_TYPE, null: false,
+ description: 'Account id of the Jira user'
+ field :jira_display_name, GraphQL::STRING_TYPE, null: false,
+ description: 'Display name of the Jira user'
+ 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'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+end
diff --git a/app/graphql/types/member_interface.rb b/app/graphql/types/member_interface.rb
new file mode 100644
index 00000000000..976836221bc
--- /dev/null
+++ b/app/graphql/types/member_interface.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Types
+ module MemberInterface
+ include BaseInterface
+
+ field :access_level, Types::AccessLevelType, null: true,
+ description: 'GitLab::Access level'
+
+ field :created_by, Types::UserType, null: true,
+ description: 'User that authorized membership'
+
+ field :created_at, Types::TimeType, null: true,
+ description: 'Date and time the membership was created'
+
+ field :updated_at, Types::TimeType, null: true,
+ description: 'Date and time the membership was last updated'
+
+ field :expires_at, Types::TimeType, null: true,
+ description: 'Date and time the membership expires'
+ end
+end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index cd4c6b4d46a..cb4ff7ea0c5 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -28,6 +28,8 @@ module Types
description: 'Timestamp of when the merge request was created'
field :updated_at, Types::TimeType, null: false,
description: 'Timestamp of when the merge request was last updated'
+ field :merged_at, Types::TimeType, null: true, complexity: 5,
+ description: 'Timestamp of when the merge request was merged, null if not merged'
field :source_project, Types::ProjectType, null: true,
description: 'Source project of the merge request'
field :target_project, Types::ProjectType, null: false,
@@ -81,8 +83,14 @@ module Types
description: 'Default merge commit message of the merge request'
field :merge_ongoing, GraphQL::BOOLEAN_TYPE, method: :merge_ongoing?, null: false,
description: 'Indicates if a merge is currently occurring'
- field :source_branch_exists, GraphQL::BOOLEAN_TYPE, method: :source_branch_exists?, null: false,
+ field :source_branch_exists, GraphQL::BOOLEAN_TYPE,
+ null: false, calls_gitaly: true,
+ method: :source_branch_exists?,
description: 'Indicates if the source branch of the merge request exists'
+ field :target_branch_exists, GraphQL::BOOLEAN_TYPE,
+ null: false, calls_gitaly: true,
+ method: :target_branch_exists?,
+ description: 'Indicates if the target branch of the merge request exists'
field :mergeable_discussions_state, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if all discussions in the merge request have been resolved, allowing the merge request to be merged'
field :web_url, GraphQL::STRING_TYPE, null: true,
@@ -103,6 +111,8 @@ module Types
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find }
field :assignees, Types::UserType.connection_type, null: true, complexity: 5,
description: 'Assignees of the merge request'
+ field :author, Types::UserType, null: true,
+ description: 'User who created this merge request'
field :participants, Types::UserType.connection_type, null: true, complexity: 5,
description: 'Participants in the merge request'
field :subscribed, GraphQL::BOOLEAN_TYPE, method: :subscribed?, null: false, complexity: 5,
diff --git a/app/graphql/types/metrics/dashboard_type.rb b/app/graphql/types/metrics/dashboard_type.rb
index d684533ff94..bbcce2d9596 100644
--- a/app/graphql/types/metrics/dashboard_type.rb
+++ b/app/graphql/types/metrics/dashboard_type.rb
@@ -10,6 +10,9 @@ module Types
field :path, GraphQL::STRING_TYPE, null: true,
description: 'Path to a file with the dashboard definition'
+ field :schema_validation_warnings, [GraphQL::STRING_TYPE], null: true,
+ description: 'Dashboard schema validation warnings'
+
field :annotations, Types::Metrics::Dashboards::AnnotationType.connection_type, null: true,
description: 'Annotations added to the dashboard',
resolver: Resolvers::Metrics::Dashboards::AnnotationResolver
diff --git a/app/graphql/types/milestone_type.rb b/app/graphql/types/milestone_type.rb
index 900f8c6f01d..99bd6e819d6 100644
--- a/app/graphql/types/milestone_type.rb
+++ b/app/graphql/types/milestone_type.rb
@@ -35,5 +35,17 @@ module Types
field :updated_at, Types::TimeType, null: false,
description: 'Timestamp of last milestone update'
+
+ field :project_milestone, GraphQL::BOOLEAN_TYPE, null: false,
+ description: 'Indicates if milestone is at project level',
+ method: :project_milestone?
+
+ field :group_milestone, GraphQL::BOOLEAN_TYPE, null: false,
+ description: 'Indicates if milestone is at group level',
+ method: :group_milestone?
+
+ field :subgroup_milestone, GraphQL::BOOLEAN_TYPE, null: false,
+ description: 'Indicates if milestone is at subgroup level',
+ method: :subgroup_milestone?
end
end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index aeff84b83b8..8874c56dfdb 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -9,13 +9,17 @@ module Types
mount_mutation Mutations::Admin::SidekiqQueues::DeleteJobs
mount_mutation Mutations::AlertManagement::CreateAlertIssue
mount_mutation Mutations::AlertManagement::UpdateAlertStatus
+ mount_mutation Mutations::AlertManagement::Alerts::SetAssignees
mount_mutation Mutations::AwardEmojis::Add
mount_mutation Mutations::AwardEmojis::Remove
mount_mutation Mutations::AwardEmojis::Toggle
mount_mutation Mutations::Branches::Create, calls_gitaly: true
+ mount_mutation Mutations::Commits::Create, calls_gitaly: true
+ mount_mutation Mutations::Discussions::ToggleResolve
mount_mutation Mutations::Issues::SetConfidential
mount_mutation Mutations::Issues::SetDueDate
mount_mutation Mutations::Issues::Update
+ mount_mutation Mutations::MergeRequests::Create
mount_mutation Mutations::MergeRequests::SetLabels
mount_mutation Mutations::MergeRequests::SetLocked
mount_mutation Mutations::MergeRequests::SetMilestone
@@ -23,6 +27,7 @@ module Types
mount_mutation Mutations::MergeRequests::SetWip, calls_gitaly: true
mount_mutation Mutations::MergeRequests::SetAssignees
mount_mutation Mutations::Metrics::Dashboard::Annotations::Create
+ mount_mutation Mutations::Metrics::Dashboard::Annotations::Delete
mount_mutation Mutations::Notes::Create::Note, calls_gitaly: true
mount_mutation Mutations::Notes::Create::DiffNote, calls_gitaly: true
mount_mutation Mutations::Notes::Create::ImageDiffNote, calls_gitaly: true
@@ -44,8 +49,10 @@ module Types
mount_mutation Mutations::Snippets::Create
mount_mutation Mutations::Snippets::MarkAsSpam
mount_mutation Mutations::JiraImport::Start
+ mount_mutation Mutations::JiraImport::ImportUsers
mount_mutation Mutations::DesignManagement::Upload, calls_gitaly: true
mount_mutation Mutations::DesignManagement::Delete, calls_gitaly: true
+ mount_mutation Mutations::ContainerExpirationPolicies::Update
end
end
diff --git a/app/graphql/types/notes/discussion_type.rb b/app/graphql/types/notes/discussion_type.rb
index 74a233e9d26..a51d253097d 100644
--- a/app/graphql/types/notes/discussion_type.rb
+++ b/app/graphql/types/notes/discussion_type.rb
@@ -7,6 +7,8 @@ module Types
authorize :read_note
+ implements(Types::ResolvableInterface)
+
field :id, GraphQL::ID_TYPE, null: false,
description: "ID of this discussion"
field :reply_id, GraphQL::ID_TYPE, null: false,
diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb
index d48cc868434..8755b4ccad5 100644
--- a/app/graphql/types/notes/note_type.rb
+++ b/app/graphql/types/notes/note_type.rb
@@ -9,6 +9,8 @@ module Types
expose_permissions Types::PermissionTypes::Note
+ implements(Types::ResolvableInterface)
+
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the note'
@@ -22,11 +24,6 @@ module Types
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: '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: 'Indicates whether this note was created by the system or by a user'
@@ -44,11 +41,6 @@ module Types
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'
field :confidential, GraphQL::BOOLEAN_TYPE, null: true,
diff --git a/app/graphql/types/notes/noteable_type.rb b/app/graphql/types/notes/noteable_type.rb
index 187c9109f8c..3a16d54f9cd 100644
--- a/app/graphql/types/notes/noteable_type.rb
+++ b/app/graphql/types/notes/noteable_type.rb
@@ -19,6 +19,8 @@ module Types
Types::SnippetType
when ::DesignManagement::Design
Types::DesignManagement::DesignType
+ when ::AlertManagement::Alert
+ Types::AlertManagement::AlertType
else
raise "Unknown GraphQL type for #{object}"
end
diff --git a/app/graphql/types/permission_types/ci/pipeline.rb b/app/graphql/types/permission_types/ci/pipeline.rb
index 73e44a33eba..cfd68380005 100644
--- a/app/graphql/types/permission_types/ci/pipeline.rb
+++ b/app/graphql/types/permission_types/ci/pipeline.rb
@@ -6,7 +6,8 @@ module Types
class Pipeline < BasePermissionType
graphql_name 'PipelinePermissions'
- abilities :update_pipeline, :admin_pipeline, :destroy_pipeline
+ abilities :admin_pipeline, :destroy_pipeline
+ ability_field :update_pipeline, calls_gitaly: true
end
end
end
diff --git a/app/graphql/types/permission_types/merge_request.rb b/app/graphql/types/permission_types/merge_request.rb
index d877fc177d2..28b7ebd2af6 100644
--- a/app/graphql/types/permission_types/merge_request.rb
+++ b/app/graphql/types/permission_types/merge_request.rb
@@ -3,6 +3,11 @@
module Types
module PermissionTypes
class MergeRequest < BasePermissionType
+ PERMISSION_FIELDS = %i[push_to_source_branch
+ remove_source_branch
+ cherry_pick_on_current_merge_request
+ revert_on_current_merge_request].freeze
+
present_using MergeRequestPresenter
description 'Check permissions for the current user on a merge request'
graphql_name 'MergeRequestPermissions'
@@ -10,10 +15,9 @@ module Types
abilities :read_merge_request, :admin_merge_request,
:update_merge_request, :create_note
- permission_field :push_to_source_branch, method: :can_push_to_source_branch?, calls_gitaly: true
- permission_field :remove_source_branch, method: :can_remove_source_branch?, calls_gitaly: true
- permission_field :cherry_pick_on_current_merge_request, method: :can_cherry_pick_on_current_merge_request?
- permission_field :revert_on_current_merge_request, method: :can_revert_on_current_merge_request?
+ PERMISSION_FIELDS.each do |field_name|
+ permission_field field_name, method: :"can_#{field_name}?", calls_gitaly: true
+ end
end
end
end
diff --git a/app/graphql/types/project_member_type.rb b/app/graphql/types/project_member_type.rb
new file mode 100644
index 00000000000..e9ccb51886b
--- /dev/null
+++ b/app/graphql/types/project_member_type.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Types
+ class ProjectMemberType < BaseObject
+ graphql_name 'ProjectMember'
+ description 'Represents a Project Member'
+
+ expose_permissions Types::PermissionTypes::Project
+
+ implements MemberInterface
+
+ authorize :read_project
+
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: 'ID of the member'
+
+ field :user, Types::UserType, null: false,
+ description: 'User that is associated with the member object',
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.user_id).find }
+
+ field :project, Types::ProjectType, null: true,
+ description: 'Project that User is a member of',
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, obj.source_id).find }
+ end
+end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 4e438ed2576..bbfb7fc4f20 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -95,6 +95,8 @@ module Types
description: 'Status of Jira import background job of the project'
field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if merge requests of the project can only be merged with successful jobs'
+ field :allow_merge_on_skipped_pipeline, GraphQL::BOOLEAN_TYPE, null: true,
+ description: 'If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of the project can also be merged with skipped jobs'
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if users can request member access to the project'
field :only_allow_merge_if_all_discussions_are_resolved, GraphQL::BOOLEAN_TYPE, null: true,
@@ -125,6 +127,7 @@ module Types
Types::MergeRequestType.connection_type,
null: true,
description: 'Merge requests of the project',
+ extras: [:lookahead],
resolver: Resolvers::MergeRequestsResolver
field :merge_request,
@@ -139,6 +142,11 @@ module Types
description: 'Issues of the project',
resolver: Resolvers::IssuesResolver
+ field :project_members,
+ Types::ProjectMemberType.connection_type,
+ description: 'Members of the project',
+ resolver: Resolvers::ProjectMembersResolver
+
field :environments,
Types::EnvironmentType.connection_type,
null: true,
@@ -157,6 +165,12 @@ module Types
description: 'Build pipelines of the project',
resolver: Resolvers::ProjectPipelinesResolver
+ field :pipeline,
+ Types::Ci::PipelineType,
+ null: true,
+ description: 'Build pipeline of the project',
+ resolver: Resolvers::ProjectPipelineResolver
+
field :sentry_detailed_error,
Types::ErrorTracking::SentryDetailedErrorType,
null: true,
@@ -210,13 +224,14 @@ module Types
Types::AlertManagement::AlertType.connection_type,
null: true,
description: 'Alert Management alerts of the project',
- resolver: Resolvers::AlertManagementAlertResolver
+ extras: [:lookahead],
+ resolver: Resolvers::AlertManagement::AlertResolver
field :alert_management_alert,
Types::AlertManagement::AlertType,
null: true,
description: 'A single Alert Management alert of the project',
- resolver: Resolvers::AlertManagementAlertResolver.single
+ resolver: Resolvers::AlertManagement::AlertResolver.single
field :alert_management_alert_status_counts,
Types::AlertManagement::AlertStatusCountsType,
@@ -237,6 +252,50 @@ module Types
description: 'A single release of the project',
resolver: Resolvers::ReleasesResolver.single,
feature_flag: :graphql_release_data
+
+ field :container_expiration_policy,
+ Types::ContainerExpirationPolicyType,
+ null: true,
+ description: 'The container expiration policy of the project'
+
+ field :label,
+ Types::LabelType,
+ null: true,
+ description: 'A label available on this project' do
+ argument :title, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Title of the label'
+ end
+
+ def label(title:)
+ BatchLoader::GraphQL.for(title).batch(key: project) do |titles, loader, args|
+ LabelsFinder
+ .new(current_user, project: args[:key], title: titles)
+ .execute
+ .each { |label| loader.call(label.title, label) }
+ end
+ end
+
+ field :labels,
+ Types::LabelType.connection_type,
+ null: true,
+ description: 'Labels available on this project' do
+ argument :search_term, GraphQL::STRING_TYPE,
+ required: false,
+ description: 'A search term to find labels with'
+ end
+
+ def labels(search_term: nil)
+ LabelsFinder
+ .new(current_user, project: project, search: search_term)
+ .execute
+ end
+
+ private
+
+ def project
+ @project ||= object.respond_to?(:sync) ? object.sync : object
+ end
end
end
diff --git a/app/graphql/types/projects/service_type.rb b/app/graphql/types/projects/service_type.rb
index 55dd828d4b8..4ae7cb77904 100644
--- a/app/graphql/types/projects/service_type.rb
+++ b/app/graphql/types/projects/service_type.rb
@@ -6,7 +6,7 @@ module Types
include Types::BaseInterface
graphql_name 'Service'
- # TODO: Add all the fields that we want to expose for the project services intergrations
+ # TODO: Add all the fields that we want to expose for the project services integrations
# https://gitlab.com/gitlab-org/gitlab/-/issues/213088
field :type, GraphQL::STRING_TYPE, null: true,
description: 'Class name of the service'
diff --git a/app/graphql/types/projects/services/jira_project_type.rb b/app/graphql/types/projects/services/jira_project_type.rb
new file mode 100644
index 00000000000..ccf9107f398
--- /dev/null
+++ b/app/graphql/types/projects/services/jira_project_type.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Types
+ module Projects
+ module Services
+ # rubocop:disable Graphql/AuthorizeTypes
+ class JiraProjectType < BaseObject
+ graphql_name 'JiraProject'
+
+ field :key, GraphQL::STRING_TYPE, null: false,
+ description: 'Key of the Jira project'
+ field :project_id, GraphQL::INT_TYPE, null: false,
+ description: 'ID of the Jira project',
+ method: :id
+ field :name, GraphQL::STRING_TYPE, null: true,
+ description: 'Name of the Jira project'
+ end
+ # rubocop:enable Graphql/AuthorizeTypes
+ end
+ end
+end
diff --git a/app/graphql/types/projects/services/jira_service_type.rb b/app/graphql/types/projects/services/jira_service_type.rb
index 4fd9e61f5a4..e81963f752d 100644
--- a/app/graphql/types/projects/services/jira_service_type.rb
+++ b/app/graphql/types/projects/services/jira_service_type.rb
@@ -9,9 +9,14 @@ module Types
implements(Types::Projects::ServiceType)
authorize :admin_project
- # This is a placeholder for now for the actuall implementation of the JiraServiceType
- # Here we will want to expose a field with jira_projects fetched through Jira Rest API
- # MR implementing it https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28190
+
+ field :projects,
+ Types::Projects::Services::JiraProjectType.connection_type,
+ null: true,
+ connection: false,
+ extensions: [Gitlab::Graphql::Extensions::ExternallyPaginatedArrayExtension],
+ description: 'List of Jira projects fetched through Jira REST API',
+ resolver: Resolvers::Projects::JiraProjectsResolver
end
end
end
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 70cdcb62bc6..362e4004b73 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -47,10 +47,24 @@ module Types
null: false,
description: 'Fields related to design management'
+ field :user, Types::UserType,
+ null: true,
+ description: 'Find a user',
+ resolver: Resolvers::UserResolver
+
+ field :users, Types::UserType.connection_type,
+ null: true,
+ description: 'Find users',
+ resolver: Resolvers::UsersResolver
+
field :echo, GraphQL::STRING_TYPE, null: false,
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_assets_type.rb b/app/graphql/types/release_assets_type.rb
new file mode 100644
index 00000000000..58ad05b5365
--- /dev/null
+++ b/app/graphql/types/release_assets_type.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Types
+ class ReleaseAssetsType < BaseObject
+ graphql_name 'ReleaseAssets'
+
+ authorize :read_release
+
+ alias_method :release, :object
+
+ present_using ReleasePresenter
+
+ field :assets_count, GraphQL::INT_TYPE, null: true,
+ description: 'Number of assets of the release'
+ field :links, Types::ReleaseLinkType.connection_type, null: true,
+ description: 'Asset links of the release'
+ field :sources, Types::ReleaseSourceType.connection_type, null: true,
+ description: 'Sources of the release'
+ end
+end
diff --git a/app/graphql/types/release_link_type.rb b/app/graphql/types/release_link_type.rb
new file mode 100644
index 00000000000..070f14a90df
--- /dev/null
+++ b/app/graphql/types/release_link_type.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Types
+ class ReleaseLinkType < BaseObject
+ graphql_name 'ReleaseLink'
+
+ authorize :read_release
+
+ field :id, GraphQL::ID_TYPE, null: false,
+ description: 'ID of the link'
+ field :name, GraphQL::STRING_TYPE, null: true,
+ description: 'Name of the link'
+ field :url, GraphQL::STRING_TYPE, null: true,
+ description: 'URL of the link'
+ field :link_type, Types::ReleaseLinkTypeEnum, 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'
+ end
+end
diff --git a/app/graphql/types/release_link_type_enum.rb b/app/graphql/types/release_link_type_enum.rb
new file mode 100644
index 00000000000..b364855833f
--- /dev/null
+++ b/app/graphql/types/release_link_type_enum.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Types
+ class ReleaseLinkTypeEnum < BaseEnum
+ graphql_name 'ReleaseLinkType'
+ description 'Type of the link: `other`, `runbook`, `image`, `package`; defaults to `other`'
+
+ ::Releases::Link.link_types.keys.each do |link_type|
+ value link_type.upcase, value: link_type, description: "#{link_type.titleize} link type"
+ end
+ end
+end
diff --git a/app/graphql/types/release_source_type.rb b/app/graphql/types/release_source_type.rb
new file mode 100644
index 00000000000..0ec1ad85a39
--- /dev/null
+++ b/app/graphql/types/release_source_type.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ class ReleaseSourceType < BaseObject
+ graphql_name 'ReleaseSource'
+
+ authorize :read_release_sources
+
+ field :format, GraphQL::STRING_TYPE, null: true,
+ description: 'Format of the source'
+ field :url, GraphQL::STRING_TYPE, null: true,
+ description: 'Download URL of the source'
+ end
+end
diff --git a/app/graphql/types/release_type.rb b/app/graphql/types/release_type.rb
index 632351be5d3..3d8e5a93c68 100644
--- a/app/graphql/types/release_type.rb
+++ b/app/graphql/types/release_type.rb
@@ -23,8 +23,12 @@ module Types
description: 'Timestamp of when the release was created'
field :released_at, Types::TimeType, null: true,
description: 'Timestamp of when the release was released'
+ field :assets, Types::ReleaseAssetsType, null: true, method: :itself,
+ description: 'Assets 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,
+ description: 'Evidence for the release'
field :author, Types::UserType, null: true,
description: 'User that created the release'
diff --git a/app/graphql/types/resolvable_interface.rb b/app/graphql/types/resolvable_interface.rb
new file mode 100644
index 00000000000..a39092c70ca
--- /dev/null
+++ b/app/graphql/types/resolvable_interface.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Types
+ # This Interface contains fields that are shared between objects that include either
+ # the `ResolvableNote` or `ResolvableDiscussion` modules.
+ module ResolvableInterface
+ include Types::BaseInterface
+
+ field :resolved_by, Types::UserType,
+ null: true,
+ description: 'User who resolved the object'
+
+ def resolved_by
+ return unless object.resolved_by_id
+
+ Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.resolved_by_id).find
+ end
+
+ field :resolved, GraphQL::BOOLEAN_TYPE, null: false,
+ description: 'Indicates if the object is resolved',
+ method: :resolved?
+ field :resolvable, GraphQL::BOOLEAN_TYPE, null: false,
+ description: 'Indicates if the object can be resolved',
+ method: :resolvable?
+ field :resolved_at, Types::TimeType, null: true,
+ description: 'Timestamp of when the object was resolved'
+ end
+end
diff --git a/app/graphql/types/snippet_type.rb b/app/graphql/types/snippet_type.rb
index b23c4f71ffa..73ca3425ded 100644
--- a/app/graphql/types/snippet_type.rb
+++ b/app/graphql/types/snippet_type.rb
@@ -27,9 +27,12 @@ module Types
authorize: :read_project,
resolve: -> (snippet, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, snippet.project_id).find }
+ # Author can be nil in some scenarios. For example,
+ # when the admin setting restricted visibility
+ # level is set to public
field :author, Types::UserType,
description: 'The owner of the snippet',
- null: false,
+ null: true,
resolve: -> (snippet, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, snippet.author_id).find }
field :file_name, GraphQL::STRING_TYPE,
@@ -65,6 +68,11 @@ module Types
calls_gitaly: true,
null: false
+ field :blobs, type: [Types::Snippets::BlobType],
+ description: 'Snippet blobs',
+ calls_gitaly: true,
+ null: false
+
field :ssh_url_to_repo, type: GraphQL::STRING_TYPE,
description: 'SSH URL to the snippet repository',
calls_gitaly: true,
diff --git a/app/graphql/types/snippets/file_input_action_enum.rb b/app/graphql/types/snippets/file_input_action_enum.rb
new file mode 100644
index 00000000000..7785853f3a8
--- /dev/null
+++ b/app/graphql/types/snippets/file_input_action_enum.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Types
+ module Snippets
+ class FileInputActionEnum < BaseEnum
+ graphql_name 'SnippetFileInputActionEnum'
+ description 'Type of a snippet file input action'
+
+ value 'create', value: :create
+ value 'update', value: :update
+ value 'delete', value: :delete
+ value 'move', value: :move
+ end
+ end
+end
diff --git a/app/graphql/types/snippets/file_input_type.rb b/app/graphql/types/snippets/file_input_type.rb
new file mode 100644
index 00000000000..85a02c8f493
--- /dev/null
+++ b/app/graphql/types/snippets/file_input_type.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Types
+ module Snippets
+ class FileInputType < BaseInputObject # rubocop:disable Graphql/AuthorizeTypes
+ graphql_name 'SnippetFileInputType'
+ description 'Represents an action to perform over a snippet file'
+
+ argument :action, Types::Snippets::FileInputActionEnum,
+ description: 'Type of input action',
+ required: true
+
+ argument :previous_path, GraphQL::STRING_TYPE,
+ description: 'Previous path of the snippet file',
+ required: false
+
+ argument :file_path, GraphQL::STRING_TYPE,
+ description: 'Path of the snippet file',
+ required: true
+
+ argument :content, GraphQL::STRING_TYPE,
+ description: 'Snippet file content',
+ required: false
+ end
+ end
+end
diff --git a/app/graphql/types/user_state_enum.rb b/app/graphql/types/user_state_enum.rb
new file mode 100644
index 00000000000..d34936b4c48
--- /dev/null
+++ b/app/graphql/types/user_state_enum.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Types
+ class UserStateEnum < BaseEnum
+ graphql_name 'UserState'
+ description 'Possible states of a user'
+
+ value 'active', 'The user is active and is able to use the system', value: 'active'
+ value 'blocked', 'The user has been blocked and is prevented from using the system', value: 'blocked'
+ value 'deactivated', 'The user is no longer active and is unable to use the system', value: 'deactivated'
+ end
+end
diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb
index 29a3f5d452f..ab3c84ea539 100644
--- a/app/graphql/types/user_type.rb
+++ b/app/graphql/types/user_type.rb
@@ -12,12 +12,12 @@ module Types
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the user'
- field :name, GraphQL::STRING_TYPE, null: false,
- description: 'Human-readable name of the user'
- field :state, GraphQL::STRING_TYPE, null: false,
- description: 'State of the issue'
field :username, GraphQL::STRING_TYPE, null: false,
description: 'Username of the user. Unique within this instance of GitLab'
+ field :name, GraphQL::STRING_TYPE, null: false,
+ description: 'Human-readable name of the user'
+ field :state, Types::UserStateEnum, null: false,
+ description: 'State of the user'
field :avatar_url, GraphQL::STRING_TYPE, null: true,
description: "URL of the user's avatar"
field :web_url, GraphQL::STRING_TYPE, null: false,
@@ -25,6 +25,20 @@ module Types
field :todos, Types::TodoType.connection_type, null: false,
resolver: Resolvers::TodoResolver,
description: 'Todos of the user'
+ field :group_memberships, Types::GroupMemberType.connection_type, null: true,
+ description: 'Group memberships of the user',
+ method: :group_members
+ field :project_memberships, Types::ProjectMemberType.connection_type, null: true,
+ description: 'Project memberships of the user',
+ method: :project_members
+
+ # Merge request field: MRs can be either authored or assigned:
+ field :authored_merge_requests, Types::MergeRequestType.connection_type, null: true,
+ resolver: Resolvers::AuthoredMergeRequestsResolver,
+ description: 'Merge Requests authored by the user'
+ field :assigned_merge_requests, Types::MergeRequestType.connection_type, null: true,
+ resolver: Resolvers::AssignedMergeRequestsResolver,
+ description: 'Merge Requests assigned to the user'
field :snippets,
Types::SnippetType.connection_type,