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>2022-06-20 14:10:13 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-06-20 14:10:13 +0300
commit0ea3fcec397b69815975647f5e2aa5fe944a8486 (patch)
tree7979381b89d26011bcf9bdc989a40fcc2f1ed4ff /app/graphql
parent72123183a20411a36d607d70b12d57c484394c8e (diff)
Add latest changes from gitlab-org/gitlab@15-1-stable-eev15.1.0-rc42
Diffstat (limited to 'app/graphql')
-rw-r--r--app/graphql/gitlab_schema.rb28
-rw-r--r--app/graphql/mutations/base_mutation.rb18
-rw-r--r--app/graphql/mutations/ci/pipeline/destroy.rb13
-rw-r--r--app/graphql/mutations/ci/runner/update.rb4
-rw-r--r--app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb21
-rw-r--r--app/graphql/mutations/incident_management/timeline_event/create.rb4
-rw-r--r--app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb3
-rw-r--r--app/graphql/mutations/issues/set_crm_contacts.rb2
-rw-r--r--app/graphql/mutations/merge_requests/set_draft.rb4
-rw-r--r--app/graphql/mutations/packages/cleanup/policy/update.rb48
-rw-r--r--app/graphql/mutations/packages/destroy_files.rb54
-rw-r--r--app/graphql/mutations/releases/create.rb4
-rw-r--r--app/graphql/mutations/security/ci_configuration/configure_sast.rb2
-rw-r--r--app/graphql/mutations/terraform/state/delete.rb4
-rw-r--r--app/graphql/mutations/user_preferences/update.rb17
-rw-r--r--app/graphql/mutations/work_items/create.rb3
-rw-r--r--app/graphql/mutations/work_items/create_from_task.rb2
-rw-r--r--app/graphql/mutations/work_items/delete.rb2
-rw-r--r--app/graphql/mutations/work_items/delete_task.rb3
-rw-r--r--app/graphql/mutations/work_items/update.rb13
-rw-r--r--app/graphql/mutations/work_items/update_task.rb77
-rw-r--r--app/graphql/mutations/work_items/update_widgets.rb59
-rw-r--r--app/graphql/resolvers/base_issues_resolver.rb7
-rw-r--r--app/graphql/resolvers/ci/runner_owner_project_resolver.rb65
-rw-r--r--app/graphql/resolvers/clusters/agent_tokens_resolver.rb2
-rw-r--r--app/graphql/resolvers/clusters/agents_resolver.rb2
-rw-r--r--app/graphql/resolvers/concerns/issue_resolver_arguments.rb5
-rw-r--r--app/graphql/resolvers/concerns/resolves_groups.rb2
-rw-r--r--app/graphql/resolvers/concerns/resolves_merge_requests.rb1
-rw-r--r--app/graphql/resolvers/crm/contacts_resolver.rb36
-rw-r--r--app/graphql/resolvers/crm/organizations_resolver.rb36
-rw-r--r--app/graphql/resolvers/design_management/versions_resolver.rb2
-rw-r--r--app/graphql/resolvers/merge_request_pipelines_resolver.rb7
-rw-r--r--app/graphql/resolvers/milestones_resolver.rb23
-rw-r--r--app/graphql/resolvers/paginated_tree_resolver.rb2
-rw-r--r--app/graphql/resolvers/tree_resolver.rb2
-rw-r--r--app/graphql/resolvers/user_resolver.rb8
-rw-r--r--app/graphql/resolvers/users_resolver.rb5
-rw-r--r--app/graphql/resolvers/work_items_resolver.rb60
-rw-r--r--app/graphql/types/base_field.rb29
-rw-r--r--app/graphql/types/ci/detailed_status_type.rb2
-rw-r--r--app/graphql/types/ci/job_type.rb2
-rw-r--r--app/graphql/types/ci/pipeline_merge_request_event_type_enum.rb19
-rw-r--r--app/graphql/types/ci/pipeline_type.rb3
-rw-r--r--app/graphql/types/ci/runner_type.rb29
-rw-r--r--app/graphql/types/ci/runner_web_url_edge.rb11
-rw-r--r--app/graphql/types/ci/status_action_type.rb3
-rw-r--r--app/graphql/types/concerns/find_closest.rb15
-rw-r--r--app/graphql/types/customer_relations/contact_state_enum.rb17
-rw-r--r--app/graphql/types/customer_relations/organization_state_enum.rb17
-rw-r--r--app/graphql/types/global_id_type.rb3
-rw-r--r--app/graphql/types/group_member_type.rb2
-rw-r--r--app/graphql/types/group_type.rb6
-rw-r--r--app/graphql/types/issue_sort_enum.rb6
-rw-r--r--app/graphql/types/issue_type.rb7
-rw-r--r--app/graphql/types/limited_countable_connection_type.rb26
-rw-r--r--app/graphql/types/merge_requests/interacts_with_merge_request.rb10
-rw-r--r--app/graphql/types/milestone_type.rb4
-rw-r--r--app/graphql/types/mutation_type.rb14
-rw-r--r--app/graphql/types/packages/cleanup/keep_duplicated_package_files_enum.rb25
-rw-r--r--app/graphql/types/packages/cleanup/policy_type.rb23
-rw-r--r--app/graphql/types/permission_types/base_permission_type.rb14
-rw-r--r--app/graphql/types/project_type.rb15
-rw-r--r--app/graphql/types/query_complexity_type.rb4
-rw-r--r--app/graphql/types/query_type.rb10
-rw-r--r--app/graphql/types/release_asset_link_type.rb9
-rw-r--r--app/graphql/types/release_type.rb3
-rw-r--r--app/graphql/types/terraform/state_type.rb4
-rw-r--r--app/graphql/types/time_type.rb3
-rw-r--r--app/graphql/types/todo_type.rb4
-rw-r--r--app/graphql/types/work_item_sort_enum.rb11
-rw-r--r--app/graphql/types/work_item_type.rb2
-rw-r--r--app/graphql/types/work_items/updated_task_input_type.rb11
-rw-r--r--app/graphql/types/work_items/widget_interface.rb28
-rw-r--r--app/graphql/types/work_items/widget_type_enum.rb14
-rw-r--r--app/graphql/types/work_items/widgets/description_input_type.rb15
-rw-r--r--app/graphql/types/work_items/widgets/description_type.rb25
-rw-r--r--app/graphql/types/work_items/widgets/hierarchy_type.rb30
78 files changed, 951 insertions, 174 deletions
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index 9b23aa60eab..b399f0490ee 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -14,17 +14,19 @@ class GitlabSchema < GraphQL::Schema
use Gitlab::Graphql::Tracers::ApplicationContextTracer
use Gitlab::Graphql::Tracers::MetricsTracer
use Gitlab::Graphql::Tracers::LoggerTracer
- use Gitlab::Graphql::GenericTracing # Old tracer which will be removed eventually
+
+ # TODO: Old tracer which will be removed eventually
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/345396
+ use Gitlab::Graphql::GenericTracing
use Gitlab::Graphql::Tracers::TimerTracer
use GraphQL::Subscriptions::ActionCableSubscriptions
- use GraphQL::Pagination::Connections
use BatchLoader::GraphQL
use Gitlab::Graphql::Pagination::Connections
use Gitlab::Graphql::Timeout, max_seconds: Gitlab.config.gitlab.graphql_timeout
- query_analyzer Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer.new
- query_analyzer Gitlab::Graphql::QueryAnalyzers::RecursionAnalyzer.new
+ query_analyzer Gitlab::Graphql::QueryAnalyzers::AST::LoggerAnalyzer
+ query_analyzer Gitlab::Graphql::QueryAnalyzers::AST::RecursionAnalyzer
query Types::QueryType
mutation Types::MutationType
@@ -49,10 +51,10 @@ class GitlabSchema < GraphQL::Schema
super(queries, **kwargs)
end
- def get_type(type_name)
+ def get_type(type_name, context = GraphQL::Query::NullContext)
type_name = Gitlab::GlobalId::Deprecations.apply_to_graphql_name(type_name)
- super(type_name)
+ super(type_name, context)
end
def id_from_object(object, _type = nil, _ctx = nil)
@@ -77,8 +79,7 @@ class GitlabSchema < GraphQL::Schema
end
def resolve_type(type, object, ctx = :__undefined__)
- tc = type.metadata[:type_class]
- return if tc.respond_to?(:assignable?) && !tc.assignable?(object)
+ return if type.respond_to?(:assignable?) && !type.assignable?(object)
super
end
@@ -168,14 +169,3 @@ class GitlabSchema < GraphQL::Schema
end
GitlabSchema.prepend_mod_with('GitlabSchema') # rubocop: disable Cop/InjectEnterpriseEditionModule
-
-# Force the schema to load as a workaround for intermittent errors we
-# see due to a lack of thread safety.
-#
-# TODO: We can remove this workaround when we convert the schema to use
-# the new query interpreter runtime.
-#
-# See:
-# - https://gitlab.com/gitlab-org/gitlab/-/issues/211478
-# - https://gitlab.com/gitlab-org/gitlab/-/issues/210556
-GitlabSchema.graphql_definition
diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb
index d57a097a9e2..5f98b222099 100644
--- a/app/graphql/mutations/base_mutation.rb
+++ b/app/graphql/mutations/base_mutation.rb
@@ -39,14 +39,16 @@ module Mutations
true
end
- def load_application_object(argument, lookup_as_type, id, context)
- ::Gitlab::Graphql::Lazy.new { super }.catch(::GraphQL::UnauthorizedError) do |e|
- Gitlab::ErrorTracking.track_exception(e)
- # The default behaviour is to abort processing and return nil for the
- # entire mutation field, but not set any top-level errors. We prefer to
- # at least say that something went wrong.
- raise_resource_not_available_error!
- end
+ def load_application_object(argument, id, context)
+ ::Gitlab::Graphql::Lazy.new { super }
+ end
+
+ def unauthorized_object(error)
+ # The default behavior is to abort processing and return nil for the
+ # entire mutation field, but not set any top-level errors. We prefer to
+ # at least say that something went wrong.
+ Gitlab::ErrorTracking.track_exception(error)
+ raise_resource_not_available_error!
end
def self.authorizes_object?
diff --git a/app/graphql/mutations/ci/pipeline/destroy.rb b/app/graphql/mutations/ci/pipeline/destroy.rb
index 3f933818ce1..935cf45c4ab 100644
--- a/app/graphql/mutations/ci/pipeline/destroy.rb
+++ b/app/graphql/mutations/ci/pipeline/destroy.rb
@@ -12,12 +12,25 @@ module Mutations
pipeline = authorized_find!(id: id)
project = pipeline.project
+ return undergoing_refresh_error(project) if project.refreshing_build_artifacts_size?
+
result = ::Ci::DestroyPipelineService.new(project, current_user).execute(pipeline)
{
success: result.success?,
errors: result.errors
}
end
+
+ private
+
+ def undergoing_refresh_error(project)
+ Gitlab::ProjectStatsRefreshConflictsLogger.warn_request_rejected_during_stats_refresh(project.id)
+
+ {
+ success: false,
+ errors: ['Action temporarily disabled. The project this pipeline belongs to is undergoing stats refresh.']
+ }
+ end
end
end
end
diff --git a/app/graphql/mutations/ci/runner/update.rb b/app/graphql/mutations/ci/runner/update.rb
index faccd1273e5..b6d8c20c40b 100644
--- a/app/graphql/mutations/ci/runner/update.rb
+++ b/app/graphql/mutations/ci/runner/update.rb
@@ -18,6 +18,10 @@ module Mutations
required: false,
description: 'Description of the runner.'
+ argument :maintenance_note, GraphQL::Types::String,
+ required: false,
+ description: 'Runner\'s maintenance notes.'
+
argument :maximum_timeout, GraphQL::Types::Int,
required: false,
description: 'Maximum timeout (in seconds) for jobs processed by the runner.'
diff --git a/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb b/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb
new file mode 100644
index 00000000000..6a91a097a17
--- /dev/null
+++ b/app/graphql/mutations/concerns/mutations/work_items/update_arguments.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Mutations
+ module WorkItems
+ module UpdateArguments
+ extend ActiveSupport::Concern
+
+ included do
+ argument :id, ::Types::GlobalIDType[::WorkItem],
+ required: true,
+ description: 'Global ID of the work item.'
+ argument :state_event, Types::WorkItems::StateEventEnum,
+ description: 'Close or reopen a work item.',
+ required: false
+ argument :title, GraphQL::Types::String,
+ required: false,
+ description: copy_field_description(Types::WorkItemType, :title)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/incident_management/timeline_event/create.rb b/app/graphql/mutations/incident_management/timeline_event/create.rb
index cbc708a2530..1907954cada 100644
--- a/app/graphql/mutations/incident_management/timeline_event/create.rb
+++ b/app/graphql/mutations/incident_management/timeline_event/create.rb
@@ -23,7 +23,9 @@ module Mutations
authorize!(incident)
- response ::IncidentManagement::TimelineEvents::CreateService.new(incident, current_user, args).execute
+ response ::IncidentManagement::TimelineEvents::CreateService.new(
+ incident, current_user, args.merge(editable: true)
+ ).execute
end
private
diff --git a/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb b/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb
index 73a20b8a380..31ae29d896b 100644
--- a/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb
+++ b/app/graphql/mutations/incident_management/timeline_event/promote_from_note.rb
@@ -21,7 +21,8 @@ module Mutations
current_user,
promoted_from_note: note,
note: note.note,
- occurred_at: note.created_at
+ occurred_at: note.created_at,
+ editable: true
).execute
end
diff --git a/app/graphql/mutations/issues/set_crm_contacts.rb b/app/graphql/mutations/issues/set_crm_contacts.rb
index 4df65e4769c..cc718b4ae33 100644
--- a/app/graphql/mutations/issues/set_crm_contacts.rb
+++ b/app/graphql/mutations/issues/set_crm_contacts.rb
@@ -48,7 +48,7 @@ module Mutations
private
def feature_enabled?(project)
- Feature.enabled?(:customer_relations, project.group) && project.group&.crm_enabled?
+ project.group&.crm_enabled?
end
end
end
diff --git a/app/graphql/mutations/merge_requests/set_draft.rb b/app/graphql/mutations/merge_requests/set_draft.rb
index ab4ca73e5dc..f83c1a0caf4 100644
--- a/app/graphql/mutations/merge_requests/set_draft.rb
+++ b/app/graphql/mutations/merge_requests/set_draft.rb
@@ -27,8 +27,8 @@ module Mutations
private
- def wip_event(wip)
- wip ? 'wip' : 'unwip'
+ def wip_event(draft)
+ draft ? 'draft' : 'ready'
end
end
end
diff --git a/app/graphql/mutations/packages/cleanup/policy/update.rb b/app/graphql/mutations/packages/cleanup/policy/update.rb
new file mode 100644
index 00000000000..e7ab7439949
--- /dev/null
+++ b/app/graphql/mutations/packages/cleanup/policy/update.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Packages
+ module Cleanup
+ module Policy
+ class Update < Mutations::BaseMutation
+ graphql_name 'UpdatePackagesCleanupPolicy'
+
+ include FindsProject
+
+ authorize :admin_package
+
+ argument :project_path,
+ GraphQL::Types::ID,
+ required: true,
+ description: 'Project path where the packages cleanup policy is located.'
+
+ argument :keep_n_duplicated_package_files,
+ Types::Packages::Cleanup::KeepDuplicatedPackageFilesEnum,
+ required: false,
+ description: copy_field_description(
+ Types::Packages::Cleanup::PolicyType,
+ :keep_n_duplicated_package_files
+ )
+
+ field :packages_cleanup_policy,
+ Types::Packages::Cleanup::PolicyType,
+ null: true,
+ description: 'Packages cleanup policy after mutation.'
+
+ def resolve(project_path:, **args)
+ project = authorized_find!(project_path)
+
+ result = ::Packages::Cleanup::UpdatePolicyService
+ .new(project: project, current_user: current_user, params: args)
+ .execute
+
+ {
+ packages_cleanup_policy: result.payload[:packages_cleanup_policy],
+ errors: result.errors
+ }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/packages/destroy_files.rb b/app/graphql/mutations/packages/destroy_files.rb
new file mode 100644
index 00000000000..3900a2c46ae
--- /dev/null
+++ b/app/graphql/mutations/packages/destroy_files.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Packages
+ class DestroyFiles < ::Mutations::BaseMutation
+ graphql_name 'DestroyPackageFiles'
+
+ include FindsProject
+
+ MAXIMUM_FILES = 100
+
+ authorize :destroy_package
+
+ argument :project_path,
+ GraphQL::Types::ID,
+ required: true,
+ description: 'Project path where the packages cleanup policy is located.'
+
+ argument :ids,
+ [::Types::GlobalIDType[::Packages::PackageFile]],
+ required: true,
+ description: 'IDs of the Package file.'
+
+ def resolve(project_path:, ids:)
+ project = authorized_find!(project_path)
+ raise_resource_not_available_error! "Cannot delete more than #{MAXIMUM_FILES} files" if ids.size > MAXIMUM_FILES
+
+ package_files = ::Packages::PackageFile.where(id: parse_gids(ids)) # rubocop:disable CodeReuse/ActiveRecord
+
+ ensure_file_access!(project, package_files)
+
+ result = ::Packages::MarkPackageFilesForDestructionService.new(package_files).execute
+
+ errors = result.error? ? Array.wrap(result[:message]) : []
+
+ { errors: errors }
+ end
+
+ private
+
+ def ensure_file_access!(project, package_files)
+ project_ids = package_files.map(&:project_id).uniq
+
+ unless project_ids.size == 1 && project_ids.include?(project.id)
+ raise_resource_not_available_error! 'All files must be in the requested project'
+ end
+ end
+
+ def parse_gids(gids)
+ gids.map { |gid| GitlabSchema.parse_gid(gid, expected_type: ::Packages::PackageFile).model_id }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/releases/create.rb b/app/graphql/mutations/releases/create.rb
index 037ade2589c..70a0e71c869 100644
--- a/app/graphql/mutations/releases/create.rb
+++ b/app/graphql/mutations/releases/create.rb
@@ -14,6 +14,10 @@ module Mutations
required: true, as: :tag,
description: 'Name of the tag to associate with the release.'
+ argument :tag_message, GraphQL::Types::String,
+ required: false,
+ description: 'Message to use if creating a new annotated tag.'
+
argument :ref, GraphQL::Types::String,
required: false,
description: 'Commit SHA or branch name to use if creating a new tag.'
diff --git a/app/graphql/mutations/security/ci_configuration/configure_sast.rb b/app/graphql/mutations/security/ci_configuration/configure_sast.rb
index 7ce0bf83a4b..cc3c1d6033b 100644
--- a/app/graphql/mutations/security/ci_configuration/configure_sast.rb
+++ b/app/graphql/mutations/security/ci_configuration/configure_sast.rb
@@ -16,7 +16,7 @@ module Mutations
description: 'SAST CI configuration for the project.'
def configure_analyzer(project, **args)
- ::Security::CiConfiguration::SastCreateService.new(project, current_user, args[:configuration]).execute
+ ::Security::CiConfiguration::SastCreateService.new(project, current_user, args[:configuration].to_h).execute
end
end
end
diff --git a/app/graphql/mutations/terraform/state/delete.rb b/app/graphql/mutations/terraform/state/delete.rb
index f08219cb395..f52ace07393 100644
--- a/app/graphql/mutations/terraform/state/delete.rb
+++ b/app/graphql/mutations/terraform/state/delete.rb
@@ -8,9 +8,9 @@ module Mutations
def resolve(id:)
state = authorized_find!(id: id)
- state.destroy
+ response = ::Terraform::States::TriggerDestroyService.new(state, current_user: current_user).execute
- { errors: errors_on_object(state) }
+ { errors: response.errors }
end
end
end
diff --git a/app/graphql/mutations/user_preferences/update.rb b/app/graphql/mutations/user_preferences/update.rb
index b71c952b0f2..c92c6d725b7 100644
--- a/app/graphql/mutations/user_preferences/update.rb
+++ b/app/graphql/mutations/user_preferences/update.rb
@@ -14,15 +14,6 @@ module Mutations
null: true,
description: 'User preferences after mutation.'
- def ready?(**args)
- if disabled_sort_value?(args)
- raise Gitlab::Graphql::Errors::ArgumentError,
- 'Feature flag `incident_escalations` must be enabled to use this sort order.'
- end
-
- super
- end
-
def resolve(**attributes)
user_preferences = current_user.user_preference
user_preferences.update(attributes)
@@ -32,14 +23,6 @@ module Mutations
errors: errors_on_object(user_preferences)
}
end
-
- private
-
- def disabled_sort_value?(args)
- return false unless [:escalation_status_asc, :escalation_status_desc].include?(args[:issues_sort])
-
- Feature.disabled?(:incident_escalations)
- end
end
end
end
diff --git a/app/graphql/mutations/work_items/create.rb b/app/graphql/mutations/work_items/create.rb
index 2e136d409ab..2ae26ed0e1a 100644
--- a/app/graphql/mutations/work_items/create.rb
+++ b/app/graphql/mutations/work_items/create.rb
@@ -8,8 +8,7 @@ module Mutations
include Mutations::SpamProtection
include FindsProject
- description "Creates a work item." \
- " Available only when feature flag `work_items` is enabled. The feature is experimental and is subject to change without notice."
+ description "Creates a work item. Available only when feature flag `work_items` is enabled."
authorize :create_work_item
diff --git a/app/graphql/mutations/work_items/create_from_task.rb b/app/graphql/mutations/work_items/create_from_task.rb
index 4da709401a6..5ebe8b2c6d7 100644
--- a/app/graphql/mutations/work_items/create_from_task.rb
+++ b/app/graphql/mutations/work_items/create_from_task.rb
@@ -8,7 +8,7 @@ module Mutations
include Mutations::SpamProtection
description "Creates a work item from a task in another work item's description." \
- " Available only when feature flag `work_items` is enabled. This feature is experimental and is subject to change without notice."
+ " Available only when feature flag `work_items` is enabled."
authorize :update_work_item
diff --git a/app/graphql/mutations/work_items/delete.rb b/app/graphql/mutations/work_items/delete.rb
index 1830ab5443c..240a8b4c11e 100644
--- a/app/graphql/mutations/work_items/delete.rb
+++ b/app/graphql/mutations/work_items/delete.rb
@@ -5,7 +5,7 @@ module Mutations
class Delete < BaseMutation
graphql_name 'WorkItemDelete'
description "Deletes a work item." \
- " Available only when feature flag `work_items` is enabled. The feature is experimental and is subject to change without notice."
+ " Available only when feature flag `work_items` is enabled."
authorize :delete_work_item
diff --git a/app/graphql/mutations/work_items/delete_task.rb b/app/graphql/mutations/work_items/delete_task.rb
index 87620a28fa1..b1bfed0cbf1 100644
--- a/app/graphql/mutations/work_items/delete_task.rb
+++ b/app/graphql/mutations/work_items/delete_task.rb
@@ -6,8 +6,7 @@ module Mutations
graphql_name 'WorkItemDeleteTask'
description "Deletes a task in a work item's description." \
- ' Available only when feature flag `work_items` is enabled. This feature is experimental and' \
- ' is subject to change without notice.'
+ ' Available only when feature flag `work_items` is enabled.'
authorize :update_work_item
diff --git a/app/graphql/mutations/work_items/update.rb b/app/graphql/mutations/work_items/update.rb
index 20319301482..c495da00f41 100644
--- a/app/graphql/mutations/work_items/update.rb
+++ b/app/graphql/mutations/work_items/update.rb
@@ -5,22 +5,13 @@ module Mutations
class Update < BaseMutation
graphql_name 'WorkItemUpdate'
description "Updates a work item by Global ID." \
- " Available only when feature flag `work_items` is enabled. The feature is experimental and is subject to change without notice."
+ " Available only when feature flag `work_items` is enabled."
include Mutations::SpamProtection
+ include Mutations::WorkItems::UpdateArguments
authorize :update_work_item
- argument :id, ::Types::GlobalIDType[::WorkItem],
- required: true,
- description: 'Global ID of the work item.'
- argument :state_event, Types::WorkItems::StateEventEnum,
- description: 'Close or reopen a work item.',
- required: false
- argument :title, GraphQL::Types::String,
- required: false,
- description: copy_field_description(Types::WorkItemType, :title)
-
field :work_item, Types::WorkItemType,
null: true,
description: 'Updated work item.'
diff --git a/app/graphql/mutations/work_items/update_task.rb b/app/graphql/mutations/work_items/update_task.rb
new file mode 100644
index 00000000000..35fbe672b66
--- /dev/null
+++ b/app/graphql/mutations/work_items/update_task.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+module Mutations
+ module WorkItems
+ class UpdateTask < BaseMutation
+ graphql_name 'WorkItemUpdateTask'
+ description "Updates a work item's task by Global ID." \
+ " Available only when feature flag `work_items` is enabled."
+
+ include Mutations::SpamProtection
+
+ authorize :read_work_item
+
+ argument :id, ::Types::GlobalIDType[::WorkItem],
+ required: true,
+ description: 'Global ID of the work item.'
+ argument :task_data, ::Types::WorkItems::UpdatedTaskInputType,
+ required: true,
+ description: 'Arguments necessary to update a task.'
+
+ field :task, Types::WorkItemType,
+ null: true,
+ description: 'Updated task.'
+ field :work_item, Types::WorkItemType,
+ null: true,
+ description: 'Updated work item.'
+
+ def resolve(id:, task_data:)
+ task_data_hash = task_data.to_h
+ work_item = authorized_find!(id: id)
+ task = authorized_find_task!(task_data_hash[:id])
+
+ unless work_item.project.work_items_feature_flag_enabled?
+ return { errors: ['`work_items` feature flag disabled for this project'] }
+ end
+
+ spam_params = ::Spam::SpamParams.new_from_request(request: context[:request])
+
+ ::WorkItems::UpdateService.new(
+ project: task.project,
+ current_user: current_user,
+ params: task_data_hash.except(:id),
+ spam_params: spam_params
+ ).execute(task)
+
+ check_spam_action_response!(task)
+
+ response = { errors: errors_on_object(task) }
+
+ if task.valid?
+ work_item.expire_etag_cache
+
+ response.merge(work_item: work_item, task: task)
+ else
+ response
+ end
+ end
+
+ private
+
+ def authorized_find_task!(task_id)
+ task = task_id.find
+
+ if current_user.can?(:update_work_item, task)
+ task
+ else
+ # Fail early if user cannot update task
+ raise_resource_not_available_error!
+ end
+ end
+
+ def find_object(id:)
+ id.find
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/work_items/update_widgets.rb b/app/graphql/mutations/work_items/update_widgets.rb
new file mode 100644
index 00000000000..d19da0abaac
--- /dev/null
+++ b/app/graphql/mutations/work_items/update_widgets.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Mutations
+ module WorkItems
+ class UpdateWidgets < BaseMutation
+ graphql_name 'WorkItemUpdateWidgets'
+ description "Updates the attributes of a work item's widgets by global ID." \
+ " Available only when feature flag `work_items` is enabled."
+
+ include Mutations::SpamProtection
+
+ authorize :update_work_item
+
+ argument :id, ::Types::GlobalIDType[::WorkItem],
+ required: true,
+ description: 'Global ID of the work item.'
+
+ argument :description_widget, ::Types::WorkItems::Widgets::DescriptionInputType,
+ required: false,
+ description: 'Input for description widget.'
+
+ field :work_item, Types::WorkItemType,
+ null: true,
+ description: 'Updated work item.'
+
+ def resolve(id:, **widget_attributes)
+ work_item = authorized_find!(id: id)
+
+ unless work_item.project.work_items_feature_flag_enabled?
+ return { errors: ['`work_items` feature flag disabled for this project'] }
+ end
+
+ spam_params = ::Spam::SpamParams.new_from_request(request: context[:request])
+
+ ::WorkItems::UpdateService.new(
+ project: work_item.project,
+ current_user: current_user,
+ # Cannot use prepare to use `.to_h` on each input due to
+ # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87472#note_945199865
+ widget_params: widget_attributes.transform_values { |values| values.to_h },
+ spam_params: spam_params
+ ).execute(work_item)
+
+ check_spam_action_response!(work_item)
+
+ {
+ work_item: work_item.valid? ? work_item : nil,
+ errors: errors_on_object(work_item)
+ }
+ end
+
+ private
+
+ def find_object(id:)
+ GitlabSchema.find_by_gid(id)
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/base_issues_resolver.rb b/app/graphql/resolvers/base_issues_resolver.rb
index a1fda976876..ec47a8996eb 100644
--- a/app/graphql/resolvers/base_issues_resolver.rb
+++ b/app/graphql/resolvers/base_issues_resolver.rb
@@ -33,13 +33,6 @@ module Resolvers
end
end
- def prepare_params(args, parent)
- return unless [:escalation_status_asc, :escalation_status_desc].include?(args[:sort])
- return if Feature.enabled?(:incident_escalations, parent)
-
- args[:sort] = :created_desc # default for sort argument
- end
-
private
def unconditional_includes
diff --git a/app/graphql/resolvers/ci/runner_owner_project_resolver.rb b/app/graphql/resolvers/ci/runner_owner_project_resolver.rb
new file mode 100644
index 00000000000..14b5f8f90eb
--- /dev/null
+++ b/app/graphql/resolvers/ci/runner_owner_project_resolver.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Ci
+ class RunnerOwnerProjectResolver < BaseResolver
+ include LooksAhead
+
+ type Types::ProjectType, null: true
+
+ alias_method :runner, :object
+
+ def resolve_with_lookahead(**args)
+ resolve_owner
+ end
+
+ def preloads
+ {
+ full_path: [:route]
+ }
+ end
+
+ def filtered_preloads
+ selection = lookahead
+
+ preloads.each.flat_map do |name, requirements|
+ selection&.selects?(name) ? requirements : []
+ end
+ end
+
+ private
+
+ def resolve_owner
+ return unless runner.project_type?
+
+ BatchLoader::GraphQL.for(runner.id).batch(key: :runner_owner_projects) do |runner_ids, loader|
+ # rubocop: disable CodeReuse/ActiveRecord
+ runner_and_projects_with_row_number =
+ ::Ci::RunnerProject
+ .where(runner_id: runner_ids)
+ .select('id, runner_id, project_id, ROW_NUMBER() OVER (PARTITION BY runner_id ORDER BY id ASC)')
+ runner_and_owner_projects =
+ ::Ci::RunnerProject
+ .select(:id, :runner_id, :project_id)
+ .from("(#{runner_and_projects_with_row_number.to_sql}) temp WHERE row_number = 1")
+ owner_project_id_by_runner_id =
+ runner_and_owner_projects
+ .group_by(&:runner_id)
+ .transform_values { |runner_projects| runner_projects.first.project_id }
+ project_ids = owner_project_id_by_runner_id.values.uniq
+
+ all_preloads = unconditional_includes + filtered_preloads
+ owner_relation = Project.all
+ owner_relation = owner_relation.preload(*all_preloads) if all_preloads.any?
+ projects = owner_relation.where(id: project_ids).index_by(&:id)
+
+ runner_ids.each do |runner_id|
+ owner_project_id = owner_project_id_by_runner_id[runner_id]
+ loader.call(runner_id, projects[owner_project_id])
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/clusters/agent_tokens_resolver.rb b/app/graphql/resolvers/clusters/agent_tokens_resolver.rb
index 722fbab3bb7..9740bc6bb6a 100644
--- a/app/graphql/resolvers/clusters/agent_tokens_resolver.rb
+++ b/app/graphql/resolvers/clusters/agent_tokens_resolver.rb
@@ -16,7 +16,7 @@ module Resolvers
def resolve(**args)
return ::Clusters::AgentToken.none unless can_read_agent_tokens?
- tokens = agent.last_used_agent_tokens
+ tokens = agent.agent_tokens
tokens = tokens.with_status(args[:status]) if args[:status].present?
tokens
diff --git a/app/graphql/resolvers/clusters/agents_resolver.rb b/app/graphql/resolvers/clusters/agents_resolver.rb
index 5ad66ed7cdd..28618bef807 100644
--- a/app/graphql/resolvers/clusters/agents_resolver.rb
+++ b/app/graphql/resolvers/clusters/agents_resolver.rb
@@ -30,7 +30,7 @@ module Resolvers
def preloads
{
activity_events: { activity_events: [:user, agent_token: :agent] },
- tokens: :last_used_agent_tokens
+ tokens: :agent_tokens
}
end
end
diff --git a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb
index de44dbb26d7..fe213936f55 100644
--- a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb
+++ b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb
@@ -66,7 +66,6 @@ module IssueResolverArguments
description: 'Filter for confidential issues. If "false", excludes confidential issues. If "true", returns only confidential issues.'
argument :not, Types::Issues::NegatedIssueFilterInputType,
description: 'Negated arguments.',
- prepare: ->(negated_args, ctx) { negated_args.to_h },
required: false
argument :crm_contact_id, GraphQL::Types::String,
required: false,
@@ -85,12 +84,12 @@ module IssueResolverArguments
# Will need to be made group & namespace aware with
# https://gitlab.com/gitlab-org/gitlab-foss/issues/54520
+ args[:not] = args[:not].to_h if args[:not].present?
args[:iids] ||= [args.delete(:iid)].compact if args[:iid]
args[:attempt_project_search_optimizations] = true if args[:search].present?
prepare_assignee_username_params(args)
prepare_release_tag_params(args)
- prepare_params(args, parent) if defined?(prepare_params)
finder = IssuesFinder.new(current_user, args)
@@ -98,6 +97,8 @@ module IssueResolverArguments
end
def ready?(**args)
+ args[:not] = args[:not].to_h if args[:not].present?
+
params_not_mutually_exclusive(args, mutually_exclusive_assignee_username_args)
params_not_mutually_exclusive(args, mutually_exclusive_milestone_args)
params_not_mutually_exclusive(args.fetch(:not, {}), mutually_exclusive_milestone_args)
diff --git a/app/graphql/resolvers/concerns/resolves_groups.rb b/app/graphql/resolvers/concerns/resolves_groups.rb
index c451d4e7936..2a3dce80057 100644
--- a/app/graphql/resolvers/concerns/resolves_groups.rb
+++ b/app/graphql/resolvers/concerns/resolves_groups.rb
@@ -18,11 +18,9 @@ module ResolvesGroups
def preloads
{
- contacts: [:contacts],
container_repositories_count: [:container_repositories],
custom_emoji: [:custom_emoji],
full_path: [:route],
- organizations: [:organizations],
path: [:route],
dependency_proxy_blob_count: [:dependency_proxy_blobs],
dependency_proxy_blobs: [:dependency_proxy_blobs],
diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
index a72b9a09118..697cc6f5b03 100644
--- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb
+++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb
@@ -52,6 +52,7 @@ module ResolvesMergeRequests
security_auto_fix: [:author],
head_pipeline: [:merge_request_diff, { head_pipeline: [:merge_request] }],
timelogs: [:timelogs],
+ pipelines: [:merge_request_diffs], # used by `recent_diff_head_shas` to load pipelines
committers: [merge_request_diff: [:merge_request_diff_commits]]
}
end
diff --git a/app/graphql/resolvers/crm/contacts_resolver.rb b/app/graphql/resolvers/crm/contacts_resolver.rb
new file mode 100644
index 00000000000..58d0e2ce13d
--- /dev/null
+++ b/app/graphql/resolvers/crm/contacts_resolver.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Crm
+ class ContactsResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+ include ResolvesIds
+
+ authorize :read_crm_contact
+
+ type Types::CustomerRelations::ContactType, null: true
+
+ argument :search, GraphQL::Types::String,
+ required: false,
+ description: 'Search term to find contacts with.'
+
+ argument :state, Types::CustomerRelations::ContactStateEnum,
+ required: false,
+ description: 'State of the contacts to search for.'
+
+ argument :ids, [::Types::GlobalIDType[CustomerRelations::Contact]],
+ required: false,
+ description: 'Filter contacts by IDs.'
+
+ def resolve(**args)
+ args[:ids] = resolve_ids(args.delete(:ids))
+
+ ::Crm::ContactsFinder.new(current_user, { group: group }.merge(args)).execute
+ end
+
+ def group
+ object.respond_to?(:sync) ? object.sync : object
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/crm/organizations_resolver.rb b/app/graphql/resolvers/crm/organizations_resolver.rb
new file mode 100644
index 00000000000..ca0a908ee22
--- /dev/null
+++ b/app/graphql/resolvers/crm/organizations_resolver.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Crm
+ class OrganizationsResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+ include ResolvesIds
+
+ authorize :read_crm_organization
+
+ type Types::CustomerRelations::OrganizationType, null: true
+
+ argument :search, GraphQL::Types::String,
+ required: false,
+ description: 'Search term used to find organizations with.'
+
+ argument :state, Types::CustomerRelations::OrganizationStateEnum,
+ required: false,
+ description: 'State of the organization to search for.'
+
+ argument :ids, [Types::GlobalIDType[CustomerRelations::Organization]],
+ required: false,
+ description: 'Filter organizations by IDs.'
+
+ def resolve(**args)
+ args[:ids] = resolve_ids(args.delete(:ids))
+
+ ::Crm::OrganizationsFinder.new(current_user, { group: group }.merge(args)).execute
+ end
+
+ def group
+ object.respond_to?(:sync) ? object.sync : object
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/design_management/versions_resolver.rb b/app/graphql/resolvers/design_management/versions_resolver.rb
index de636655087..bd9b82283c3 100644
--- a/app/graphql/resolvers/design_management/versions_resolver.rb
+++ b/app/graphql/resolvers/design_management/versions_resolver.rb
@@ -42,8 +42,6 @@ module Resolvers
def cutoff(id, sha)
if sha.present? || id.present?
specific_version(id, sha)
- elsif at_version = context[:at_version_argument]
- by_id(at_version) # See: DesignsResolver
else
:unconstrained
end
diff --git a/app/graphql/resolvers/merge_request_pipelines_resolver.rb b/app/graphql/resolvers/merge_request_pipelines_resolver.rb
index f84eedb4c3b..deb698c63e1 100644
--- a/app/graphql/resolvers/merge_request_pipelines_resolver.rb
+++ b/app/graphql/resolvers/merge_request_pipelines_resolver.rb
@@ -21,8 +21,9 @@ module Resolvers
super
end
- def query_for(args)
- resolve_pipelines(project, args).merge(merge_request.all_pipelines)
+ def query_for(input)
+ mr, args = input
+ resolve_pipelines(mr.source_project, args).merge(mr.all_pipelines)
end
def model_class
@@ -30,7 +31,7 @@ module Resolvers
end
def query_input(**args)
- args
+ [merge_request, args]
end
def project
diff --git a/app/graphql/resolvers/milestones_resolver.rb b/app/graphql/resolvers/milestones_resolver.rb
index dc6d781f584..25ff783b408 100644
--- a/app/graphql/resolvers/milestones_resolver.rb
+++ b/app/graphql/resolvers/milestones_resolver.rb
@@ -4,6 +4,11 @@ module Resolvers
class MilestonesResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
include TimeFrameArguments
+ include LooksAhead
+
+ # authorize before resolution
+ authorize :read_milestone
+ authorizes_object!
argument :ids, [GraphQL::Types::ID],
required: false,
@@ -34,12 +39,10 @@ module Resolvers
NON_STABLE_CURSOR_SORTS = %i[expired_last_due_date_asc expired_last_due_date_desc].freeze
- def resolve(**args)
+ def resolve_with_lookahead(**args)
validate_timeframe_params!(args)
- authorize!
-
- milestones = MilestonesFinder.new(milestones_finder_params(args)).execute
+ milestones = apply_lookahead(MilestonesFinder.new(milestones_finder_params(args)).execute)
if non_stable_cursor_sort?(args[:sort])
offset_pagination(milestones)
@@ -50,6 +53,12 @@ module Resolvers
private
+ def preloads
+ {
+ releases: :releases
+ }
+ end
+
def milestones_finder_params(args)
{
ids: parse_gids(args[:ids]),
@@ -69,12 +78,6 @@ module Resolvers
raise NotImplementedError
end
- # MilestonesFinder does not check for current_user permissions,
- # so for now we need to keep it here.
- def authorize!
- Ability.allowed?(context[:current_user], :read_milestone, parent) || raise_resource_not_available_error!
- end
-
def parse_gids(gids)
gids&.map { |gid| GitlabSchema.parse_gid(gid, expected_type: Milestone).model_id }
end
diff --git a/app/graphql/resolvers/paginated_tree_resolver.rb b/app/graphql/resolvers/paginated_tree_resolver.rb
index d29d87ca204..1b4211366e0 100644
--- a/app/graphql/resolvers/paginated_tree_resolver.rb
+++ b/app/graphql/resolvers/paginated_tree_resolver.rb
@@ -17,7 +17,6 @@ module Resolvers
description: 'Used to get a recursive tree. Default is false.'
argument :ref, GraphQL::Types::String,
required: false,
- default_value: :head,
description: 'Commit ref to get the tree for. Default value is HEAD.'
alias_method :repository, :object
@@ -26,6 +25,7 @@ module Resolvers
return unless repository.exists?
cursor = args.delete(:after)
+ args[:ref] ||= :head
pagination_params = {
limit: @field.max_page_size || 100,
diff --git a/app/graphql/resolvers/tree_resolver.rb b/app/graphql/resolvers/tree_resolver.rb
index f02eb226810..553f9aa6cd9 100644
--- a/app/graphql/resolvers/tree_resolver.rb
+++ b/app/graphql/resolvers/tree_resolver.rb
@@ -16,7 +16,6 @@ module Resolvers
description: 'Used to get a recursive tree. Default is false.'
argument :ref, GraphQL::Types::String,
required: false,
- default_value: :head,
description: 'Commit ref to get the tree for. Default value is HEAD.'
alias_method :repository, :object
@@ -24,6 +23,7 @@ module Resolvers
def resolve(**args)
return unless repository.exists?
+ args[:ref] ||= :head
repository.tree(args[:ref], args[:path], recursive: args[:recursive])
end
end
diff --git a/app/graphql/resolvers/user_resolver.rb b/app/graphql/resolvers/user_resolver.rb
index 99fd0d4927d..f0fd60e9cbb 100644
--- a/app/graphql/resolvers/user_resolver.rb
+++ b/app/graphql/resolvers/user_resolver.rb
@@ -2,6 +2,8 @@
module Resolvers
class UserResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
description 'Retrieve a single user'
type Types::UserType, null: true
@@ -23,6 +25,8 @@ module Resolvers
end
def resolve(id: nil, username: nil)
+ authorize!
+
if id
GitlabSchema.object_from_id(id, expected_type: User)
else
@@ -39,5 +43,9 @@ module Resolvers
end
end
end
+
+ def authorize!
+ raise_resource_not_available_error! unless context[:current_user].present?
+ end
end
end
diff --git a/app/graphql/resolvers/users_resolver.rb b/app/graphql/resolvers/users_resolver.rb
index 1424c14083d..b0d704d09fc 100644
--- a/app/graphql/resolvers/users_resolver.rb
+++ b/app/graphql/resolvers/users_resolver.rb
@@ -47,10 +47,7 @@ module Resolvers
end
def authorize!(usernames)
- authorized = Ability.allowed?(context[:current_user], :read_users_list)
- authorized &&= usernames.present? if context[:current_user].blank?
-
- raise_resource_not_available_error! unless authorized
+ raise_resource_not_available_error! unless context[:current_user].present?
end
private
diff --git a/app/graphql/resolvers/work_items_resolver.rb b/app/graphql/resolvers/work_items_resolver.rb
new file mode 100644
index 00000000000..1bc74131b9e
--- /dev/null
+++ b/app/graphql/resolvers/work_items_resolver.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class WorkItemsResolver < BaseResolver
+ include SearchArguments
+ include LooksAhead
+
+ type Types::WorkItemType.connection_type, null: true
+
+ argument :iid, GraphQL::Types::String,
+ required: false,
+ description: 'IID of the issue. For example, "1".'
+ argument :iids, [GraphQL::Types::String],
+ required: false,
+ description: 'List of IIDs of work items. For example, `["1", "2"]`.'
+ argument :sort, Types::WorkItemSortEnum,
+ description: 'Sort work items by this criteria.',
+ required: false,
+ default_value: :created_desc
+ argument :state, Types::IssuableStateEnum,
+ required: false,
+ description: 'Current state of this work item.'
+ argument :types, [Types::IssueTypeEnum],
+ as: :issue_types,
+ description: 'Filter work items by the given work item types.',
+ required: false
+
+ def resolve_with_lookahead(**args)
+ # The project could have been loaded in batch by `BatchLoader`.
+ # At this point we need the `id` of the project to query for issues, so
+ # make sure it's loaded and not `nil` before continuing.
+ parent = object.respond_to?(:sync) ? object.sync : object
+ return WorkItem.none if parent.nil? || !parent.work_items_feature_flag_enabled?
+
+ args[:iids] ||= [args.delete(:iid)].compact if args[:iid]
+ args[:attempt_project_search_optimizations] = true if args[:search].present?
+
+ finder = ::WorkItems::WorkItemsFinder.new(current_user, args)
+
+ Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all { |q| apply_lookahead(q) }
+ end
+
+ def ready?(**args)
+ validate_anonymous_search_access! if args[:search].present?
+
+ super
+ end
+
+ private
+
+ def unconditional_includes
+ [
+ {
+ project: [:project_feature, :group]
+ },
+ :author
+ ]
+ end
+ end
+end
diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb
index b4cd54b1332..6aee9a5c052 100644
--- a/app/graphql/types/base_field.rb
+++ b/app/graphql/types/base_field.rb
@@ -53,6 +53,30 @@ module Types
field_authorized?(object, ctx) && resolver_authorized?(object, ctx)
end
+ # This gets called from the gem's `calculate_complexity` method, allowing us
+ # to ensure our complexity calculation is used even for connections.
+ # This code is actually a copy of the default case in `calculate_complexity`
+ # in `lib/graphql/schema/field.rb`
+ # (https://github.com/rmosolgo/graphql-ruby/blob/master/lib/graphql/schema/field.rb)
+ def complexity_for(child_complexity:, query:, lookahead:)
+ defined_complexity = complexity
+
+ case defined_complexity
+ when Proc
+ arguments = query.arguments_for(lookahead.ast_nodes.first, self)
+
+ if arguments.respond_to?(:keyword_arguments)
+ defined_complexity.call(query.context, arguments.keyword_arguments, child_complexity)
+ else
+ child_complexity
+ end
+ when Numeric
+ defined_complexity + child_complexity
+ else
+ raise("Invalid complexity: #{defined_complexity.inspect} on #{path} (#{inspect})")
+ end
+ end
+
def base_complexity
complexity = DEFAULT_COMPLEXITY
complexity += 1 if calls_gitaly?
@@ -150,10 +174,9 @@ module Types
def connection_complexity_multiplier(ctx, args)
# Resolvers may add extra complexity depending on number of items being loaded.
- field_defn = to_graphql
- return 0 unless field_defn.connection?
+ return 0 unless connection?
- page_size = field_defn.connection_max_page_size || ctx.schema.default_max_page_size
+ page_size = max_page_size || ctx.schema.default_max_page_size
limit_value = [args[:first], args[:last], page_size].compact.min
multiplier = resolver&.try(:complexity_multiplier, args).to_f
limit_value * multiplier
diff --git a/app/graphql/types/ci/detailed_status_type.rb b/app/graphql/types/ci/detailed_status_type.rb
index e3413551a3f..3fab040cc0b 100644
--- a/app/graphql/types/ci/detailed_status_type.rb
+++ b/app/graphql/types/ci/detailed_status_type.rb
@@ -33,7 +33,7 @@ module Types
method: :status_tooltip
def id(parent:)
- "#{object.id}-#{parent.object.object.id}"
+ "#{object.id}-#{parent.id}"
end
def action
diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb
index f25fc56a588..b20a671179b 100644
--- a/app/graphql/types/ci/job_type.rb
+++ b/app/graphql/types/ci/job_type.rb
@@ -7,7 +7,7 @@ module Types
class JobType < BaseObject
graphql_name 'CiJob'
- connection_type_class(Types::CountableConnectionType)
+ connection_type_class(Types::LimitedCountableConnectionType)
expose_permissions Types::PermissionTypes::Ci::Job
diff --git a/app/graphql/types/ci/pipeline_merge_request_event_type_enum.rb b/app/graphql/types/ci/pipeline_merge_request_event_type_enum.rb
new file mode 100644
index 00000000000..a1236b8f2c1
--- /dev/null
+++ b/app/graphql/types/ci/pipeline_merge_request_event_type_enum.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ class PipelineMergeRequestEventTypeEnum < BaseEnum
+ graphql_name 'PipelineMergeRequestEventType'
+ description 'Event type of the pipeline associated with a merge request'
+
+ value 'MERGED_RESULT',
+ 'Pipeline run on the changes from the source branch combined with the target branch.',
+ value: :merged_result
+ value 'DETACHED',
+ 'Pipeline run on the changes in the merge request source branch.',
+ value: :detached
+ end
+ end
+end
+
+Types::Ci::PipelineMergeRequestEventTypeEnum.prepend_mod
diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb
index 81afc7f0f42..60418fec6c5 100644
--- a/app/graphql/types/ci/pipeline_type.rb
+++ b/app/graphql/types/ci/pipeline_type.rb
@@ -175,6 +175,9 @@ module Types
field :warning_messages, [Types::Ci::PipelineMessageType], null: true,
description: 'Pipeline warning messages.'
+ field :merge_request_event_type, Types::Ci::PipelineMergeRequestEventTypeEnum, null: true,
+ description: "Event type of the pipeline associated with a merge request."
+
def detailed_status
object.detailed_status(current_user)
end
diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb
index 6f957d2511f..949e216a982 100644
--- a/app/graphql/types/ci/runner_type.rb
+++ b/app/graphql/types/ci/runner_type.rb
@@ -85,6 +85,15 @@ module Types
method: :token_expires_at
field :version, GraphQL::Types::String, null: true,
description: 'Version of the runner.'
+ field :owner_project, ::Types::ProjectType, null: true,
+ description: 'Project that owns the runner. For project runners only.',
+ resolver: ::Resolvers::Ci::RunnerOwnerProjectResolver
+
+ markdown_field :maintenance_note_html, null: true
+
+ def maintenance_note_html_resolver
+ ::MarkupHelper.markdown(object.maintenance_note, context.to_h.dup)
+ end
def job_count
# We limit to 1 above the JOB_COUNT_LIMIT to indicate that more items exist after JOB_COUNT_LIMIT
@@ -136,16 +145,22 @@ module Types
# rubocop: disable CodeReuse/ActiveRecord
def batched_owners(runner_assoc_type, assoc_type, key, column_name)
- BatchLoader::GraphQL.for(runner.id).batch(key: key) do |runner_ids, loader, args|
- runner_and_owner_ids = runner_assoc_type.where(runner_id: runner_ids).pluck(:runner_id, column_name)
-
- owner_ids_by_runner_id = runner_and_owner_ids.group_by(&:first).transform_values { |v| v.pluck(1) }
- owner_ids = runner_and_owner_ids.pluck(1).uniq
-
+ BatchLoader::GraphQL.for(runner.id).batch(key: key) do |runner_ids, loader|
+ plucked_runner_and_owner_ids = runner_assoc_type
+ .select(:runner_id, column_name)
+ .where(runner_id: runner_ids)
+ .pluck(:runner_id, column_name)
+ # In plucked_runner_and_owner_ids, first() represents the runner ID, and second() the owner ID,
+ # so let's group the owner IDs by runner ID
+ runner_owner_ids_by_runner_id = plucked_runner_and_owner_ids
+ .group_by(&:first)
+ .transform_values { |runner_and_owner_id| runner_and_owner_id.map(&:second) }
+
+ owner_ids = runner_owner_ids_by_runner_id.values.flatten.uniq
owners = assoc_type.where(id: owner_ids).index_by(&:id)
runner_ids.each do |runner_id|
- loader.call(runner_id, owner_ids_by_runner_id[runner_id]&.map { |owner_id| owners[owner_id] } || [])
+ loader.call(runner_id, runner_owner_ids_by_runner_id[runner_id]&.map { |owner_id| owners[owner_id] } || [])
end
end
end
diff --git a/app/graphql/types/ci/runner_web_url_edge.rb b/app/graphql/types/ci/runner_web_url_edge.rb
index 035d75c22c6..7dfcd1f3510 100644
--- a/app/graphql/types/ci/runner_web_url_edge.rb
+++ b/app/graphql/types/ci/runner_web_url_edge.rb
@@ -4,8 +4,6 @@ module Types
module Ci
# rubocop: disable Graphql/AuthorizeTypes
class RunnerWebUrlEdge < ::Types::BaseEdge
- include FindClosest
-
field :edit_url, GraphQL::Types::String, null: true,
description: 'Web URL of the runner edit page. The value depends on where you put this field in the query. You can use it for projects or groups.',
extras: [:parent]
@@ -19,19 +17,18 @@ module Types
@runner = node.node
end
+ # here parent is a Keyset::Connection
def edit_url(parent:)
- runner_url(parent: parent, url_type: :edit_url)
+ runner_url(owner: parent.parent, url_type: :edit_url)
end
def web_url(parent:)
- runner_url(parent: parent, url_type: :default)
+ runner_url(owner: parent.parent, url_type: :default)
end
private
- def runner_url(parent:, url_type: :default)
- owner = closest_parent([::Types::ProjectType, ::Types::GroupType], parent)
-
+ def runner_url(owner:, url_type: :default)
# Only ::Group is supported at the moment, future iterations will include ::Project.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/16338
case owner
diff --git a/app/graphql/types/ci/status_action_type.rb b/app/graphql/types/ci/status_action_type.rb
index 26ca3c1438a..c0f61cf49f2 100644
--- a/app/graphql/types/ci/status_action_type.rb
+++ b/app/graphql/types/ci/status_action_type.rb
@@ -21,7 +21,8 @@ module Types
description: 'Title for the action, for example: Retry.'
def id(parent:)
- "#{parent.parent.object.object.class.name}-#{parent.object.object.id}"
+ # parent is a SimpleDelegator
+ "#{parent.subject.class.name}-#{parent.id}"
end
def action_method
diff --git a/app/graphql/types/concerns/find_closest.rb b/app/graphql/types/concerns/find_closest.rb
deleted file mode 100644
index 3064db19ea0..00000000000
--- a/app/graphql/types/concerns/find_closest.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-module FindClosest
- # Find the closest node which has any of the given types above this node, and return the domain object
- def closest_parent(types, parent)
- while parent
-
- if types.any? {|type| parent.object.instance_of? type}
- return parent.object.object
- else
- parent = parent.try(:parent)
- end
- end
- end
-end
diff --git a/app/graphql/types/customer_relations/contact_state_enum.rb b/app/graphql/types/customer_relations/contact_state_enum.rb
new file mode 100644
index 00000000000..445d2a41401
--- /dev/null
+++ b/app/graphql/types/customer_relations/contact_state_enum.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Types
+ module CustomerRelations
+ class ContactStateEnum < BaseEnum
+ graphql_name 'CustomerRelationsContactState'
+
+ value 'active',
+ description: "Active contact.",
+ value: :active
+
+ value 'inactive',
+ description: "Inactive contact.",
+ value: :inactive
+ end
+ end
+end
diff --git a/app/graphql/types/customer_relations/organization_state_enum.rb b/app/graphql/types/customer_relations/organization_state_enum.rb
new file mode 100644
index 00000000000..ecdd7d092ad
--- /dev/null
+++ b/app/graphql/types/customer_relations/organization_state_enum.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Types
+ module CustomerRelations
+ class OrganizationStateEnum < BaseEnum
+ graphql_name 'CustomerRelationsOrganizationState'
+
+ value 'active',
+ description: "Active organization.",
+ value: :active
+
+ value 'inactive',
+ description: "Inactive organization.",
+ value: :inactive
+ end
+ end
+end
diff --git a/app/graphql/types/global_id_type.rb b/app/graphql/types/global_id_type.rb
index 6a924c13a3c..145a5a22460 100644
--- a/app/graphql/types/global_id_type.rb
+++ b/app/graphql/types/global_id_type.rb
@@ -84,7 +84,8 @@ module Types
end
define_singleton_method(:suitable?) do |gid|
- next false if gid.nil?
+ # an argument can be nil, so allow it here
+ next true if gid.nil?
gid.model_name.safe_constantize.present? &&
gid.model_class.ancestors.include?(model_class)
diff --git a/app/graphql/types/group_member_type.rb b/app/graphql/types/group_member_type.rb
index 18242f7b8b1..c4582f31bec 100644
--- a/app/graphql/types/group_member_type.rb
+++ b/app/graphql/types/group_member_type.rb
@@ -15,7 +15,7 @@ module Types
field :notification_email,
resolver: Resolvers::GroupMembers::NotificationEmailResolver,
- description: "Group notification email for User. Only availble for admins."
+ description: "Group notification email for User. Only available for admins."
def group
Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, object.source_id).find
diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb
index a94cd6fad20..49971d52a30 100644
--- a/app/graphql/types/group_type.rb
+++ b/app/graphql/types/group_type.rb
@@ -201,11 +201,13 @@ module Types
field :organizations, Types::CustomerRelations::OrganizationType.connection_type,
null: true,
- description: "Find organizations of this group."
+ description: "Find organizations of this group.",
+ resolver: Resolvers::Crm::OrganizationsResolver
field :contacts, Types::CustomerRelations::ContactType.connection_type,
null: true,
- description: "Find contacts of this group."
+ description: "Find contacts of this group.",
+ resolver: Resolvers::Crm::ContactsResolver
field :work_item_types, Types::WorkItems::TypeType.connection_type,
resolver: Resolvers::WorkItems::TypesResolver,
diff --git a/app/graphql/types/issue_sort_enum.rb b/app/graphql/types/issue_sort_enum.rb
index db51e491d4e..7dced3c8e00 100644
--- a/app/graphql/types/issue_sort_enum.rb
+++ b/app/graphql/types/issue_sort_enum.rb
@@ -14,8 +14,10 @@ module Types
value 'TITLE_DESC', 'Title by descending order.', value: :title_desc
value 'POPULARITY_ASC', 'Number of upvotes (awarded "thumbs up" emoji) by ascending order.', value: :popularity_asc
value 'POPULARITY_DESC', 'Number of upvotes (awarded "thumbs up" emoji) by descending order.', value: :popularity_desc
- value 'ESCALATION_STATUS_ASC', 'Status from triggered to resolved. Defaults to `CREATED_DESC` if `incident_escalations` feature flag is disabled.', value: :escalation_status_asc
- value 'ESCALATION_STATUS_DESC', 'Status from resolved to triggered. Defaults to `CREATED_DESC` if `incident_escalations` feature flag is disabled.', value: :escalation_status_desc
+ value 'ESCALATION_STATUS_ASC', 'Status from triggered to resolved.', value: :escalation_status_asc
+ value 'ESCALATION_STATUS_DESC', 'Status from resolved to triggered.', value: :escalation_status_desc
+ value 'CLOSED_AT_ASC', 'Closed time by ascending order.', value: :closed_at_asc
+ value 'CLOSED_AT_DESC', 'Closed time by descending order.', value: :closed_at_desc
end
end
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index c83200bd614..58729b34fc7 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -127,6 +127,9 @@ module Types
field :moved_to, Types::IssueType, null: true,
description: 'Updated Issue after it got moved to another project.'
+ field :closed_as_duplicate_of, Types::IssueType, null: true,
+ description: 'Issue this issue was closed as a duplicate of.'
+
field :create_note_email, GraphQL::Types::String, null: true,
description: 'User specific email address for the issue.'
@@ -161,6 +164,10 @@ module Types
Gitlab::Graphql::Loaders::BatchModelLoader.new(Issue, object.moved_to_id).find
end
+ def closed_as_duplicate_of
+ Gitlab::Graphql::Loaders::BatchModelLoader.new(Issue, object.duplicated_to_id).find
+ end
+
def discussion_locked
!!object.discussion_locked
end
diff --git a/app/graphql/types/limited_countable_connection_type.rb b/app/graphql/types/limited_countable_connection_type.rb
new file mode 100644
index 00000000000..f0698222ea3
--- /dev/null
+++ b/app/graphql/types/limited_countable_connection_type.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ class LimitedCountableConnectionType < GraphQL::Types::Relay::BaseConnection
+ COUNT_LIMIT = 1000
+ COUNT_DESCRIPTION = "Limited count of collection. Returns limit + 1 for counts greater than the limit."
+
+ field :count, GraphQL::Types::Int, null: false, description: COUNT_DESCRIPTION do
+ argument :limit, GraphQL::Types::Int,
+ required: false, default_value: COUNT_LIMIT,
+ validates: { numericality: { greater_than: 0, less_than_or_equal_to: COUNT_LIMIT } },
+ description: "Limit value to be applied to the count query. Default is 1000."
+ end
+
+ def count(limit:)
+ relation = object.items
+
+ if relation.respond_to?(:page)
+ relation.page.total_count_with_limit(:all, limit: limit)
+ else
+ [relation.size, limit.next].min
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/merge_requests/interacts_with_merge_request.rb b/app/graphql/types/merge_requests/interacts_with_merge_request.rb
index 15621ef1472..bef2d39dc5c 100644
--- a/app/graphql/types/merge_requests/interacts_with_merge_request.rb
+++ b/app/graphql/types/merge_requests/interacts_with_merge_request.rb
@@ -5,8 +5,6 @@ module Types
module InteractsWithMergeRequest
extend ActiveSupport::Concern
- include FindClosest
-
included do
field :merge_request_interaction,
type: ::Types::UserMergeRequestInteractionType,
@@ -16,11 +14,9 @@ module Types
end
def merge_request_interaction(parent:, id: nil)
- merge_request = closest_parent([::Types::MergeRequestType], parent)
-
- return unless merge_request
-
- Users::MergeRequestInteraction.new(user: object, merge_request: merge_request)
+ # need the connection parent if called from a connection node:
+ parent = parent.parent if parent.try(:field)&.connection?
+ Users::MergeRequestInteraction.new(user: object, merge_request: parent)
end
end
end
diff --git a/app/graphql/types/milestone_type.rb b/app/graphql/types/milestone_type.rb
index 18e4a5d33e3..7741fd723f0 100644
--- a/app/graphql/types/milestone_type.rb
+++ b/app/graphql/types/milestone_type.rb
@@ -59,6 +59,10 @@ module Types
field :stats, Types::MilestoneStatsType, null: true,
description: 'Milestone statistics.'
+ field :releases, ::Types::ReleaseType.connection_type,
+ null: true,
+ description: 'Releases associated with this milestone.'
+
def stats
milestone
end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 7d8ada82d40..8642957af02 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -136,12 +136,16 @@ module Types
mount_mutation Mutations::UserPreferences::Update
mount_mutation Mutations::Packages::Destroy
mount_mutation Mutations::Packages::DestroyFile
+ mount_mutation Mutations::Packages::DestroyFiles
+ mount_mutation Mutations::Packages::Cleanup::Policy::Update
mount_mutation Mutations::Echo
- mount_mutation Mutations::WorkItems::Create
- mount_mutation Mutations::WorkItems::CreateFromTask
- mount_mutation Mutations::WorkItems::Delete
- mount_mutation Mutations::WorkItems::DeleteTask
- mount_mutation Mutations::WorkItems::Update
+ mount_mutation Mutations::WorkItems::Create, deprecated: { milestone: '15.1', reason: :alpha }
+ mount_mutation Mutations::WorkItems::CreateFromTask, deprecated: { milestone: '15.1', reason: :alpha }
+ mount_mutation Mutations::WorkItems::Delete, deprecated: { milestone: '15.1', reason: :alpha }
+ mount_mutation Mutations::WorkItems::DeleteTask, deprecated: { milestone: '15.1', reason: :alpha }
+ mount_mutation Mutations::WorkItems::Update, deprecated: { milestone: '15.1', reason: :alpha }
+ mount_mutation Mutations::WorkItems::UpdateWidgets, deprecated: { milestone: '15.1', reason: :alpha }
+ mount_mutation Mutations::WorkItems::UpdateTask, deprecated: { milestone: '15.1', reason: :alpha }
mount_mutation Mutations::SavedReplies::Create
mount_mutation Mutations::SavedReplies::Update
mount_mutation Mutations::SavedReplies::Destroy
diff --git a/app/graphql/types/packages/cleanup/keep_duplicated_package_files_enum.rb b/app/graphql/types/packages/cleanup/keep_duplicated_package_files_enum.rb
new file mode 100644
index 00000000000..bf8d625a334
--- /dev/null
+++ b/app/graphql/types/packages/cleanup/keep_duplicated_package_files_enum.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Types
+ module Packages
+ module Cleanup
+ class KeepDuplicatedPackageFilesEnum < BaseEnum
+ graphql_name 'PackagesCleanupKeepDuplicatedPackageFilesEnum'
+
+ OPTIONS_MAPPING = {
+ 'all' => 'ALL_PACKAGE_FILES',
+ '1' => 'ONE_PACKAGE_FILE',
+ '10' => 'TEN_PACKAGE_FILES',
+ '20' => 'TWENTY_PACKAGE_FILES',
+ '30' => 'THIRTY_PACKAGE_FILES',
+ '40' => 'FORTY_PACKAGE_FILES',
+ '50' => 'FIFTY_PACKAGE_FILES'
+ }.freeze
+
+ ::Packages::Cleanup::Policy::KEEP_N_DUPLICATED_PACKAGE_FILES_VALUES.each do |keep_value|
+ value OPTIONS_MAPPING[keep_value], value: keep_value, description: "Value to keep #{keep_value} package files"
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/packages/cleanup/policy_type.rb b/app/graphql/types/packages/cleanup/policy_type.rb
new file mode 100644
index 00000000000..f08aace7df9
--- /dev/null
+++ b/app/graphql/types/packages/cleanup/policy_type.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Types
+ module Packages
+ module Cleanup
+ class PolicyType < ::Types::BaseObject
+ graphql_name 'PackagesCleanupPolicy'
+ description 'A packages cleanup policy designed to keep only packages and packages assets that matter most'
+
+ authorize :admin_package
+
+ field :keep_n_duplicated_package_files,
+ Types::Packages::Cleanup::KeepDuplicatedPackageFilesEnum,
+ null: false,
+ description: 'Number of duplicated package files to retain.'
+ field :next_run_at,
+ Types::TimeType,
+ null: true,
+ description: 'Next time that this packages cleanup policy will be executed.'
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/permission_types/base_permission_type.rb b/app/graphql/types/permission_types/base_permission_type.rb
index a2cefb872c9..07e6e7a55d6 100644
--- a/app/graphql/types/permission_types/base_permission_type.rb
+++ b/app/graphql/types/permission_types/base_permission_type.rb
@@ -12,11 +12,7 @@ module Types
end
def self.ability_field(ability, **kword_args)
- unless resolving_keywords?(kword_args)
- kword_args[:resolve] ||= -> (object, args, context) do
- can?(context[:current_user], ability, object, args.to_h)
- end
- end
+ define_field_resolver_method(ability) unless resolving_keywords?(kword_args)
permission_field(ability, **kword_args)
end
@@ -31,6 +27,14 @@ module Types
field(**kword_args) # rubocop:disable Graphql/Descriptions
end
+ def self.define_field_resolver_method(ability)
+ unless self.respond_to?(ability)
+ define_method ability.to_sym do |*args|
+ Ability.allowed?(context[:current_user], ability, object, args.to_h)
+ end
+ end
+ end
+
def self.resolving_keywords?(arguments)
RESOLVING_KEYWORDS.intersect?(arguments.keys.to_set)
end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index f1de8e985b3..603d5ead540 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -4,6 +4,8 @@ module Types
class ProjectType < BaseObject
graphql_name 'Project'
+ connection_type_class(Types::CountableConnectionType)
+
authorize :read_project
expose_permissions Types::PermissionTypes::Project
@@ -142,6 +144,14 @@ module Types
extras: [:lookahead],
resolver: Resolvers::IssuesResolver
+ field :work_items,
+ Types::WorkItemType.connection_type,
+ null: true,
+ deprecated: { milestone: '15.1', reason: :alpha },
+ description: 'Work items of the project.',
+ extras: [:lookahead],
+ resolver: Resolvers::WorkItemsResolver
+
field :issue_status_counts,
Types::IssueStatusCountsType,
null: true,
@@ -179,6 +189,11 @@ module Types
description: 'Packages of the project.',
resolver: Resolvers::ProjectPackagesResolver
+ field :packages_cleanup_policy,
+ Types::Packages::Cleanup::PolicyType,
+ null: true,
+ description: 'Packages cleanup policy for the project.'
+
field :jobs,
type: Types::Ci::JobType.connection_type,
null: true,
diff --git a/app/graphql/types/query_complexity_type.rb b/app/graphql/types/query_complexity_type.rb
index 13b618cf5ce..ddcf448c64a 100644
--- a/app/graphql/types/query_complexity_type.rb
+++ b/app/graphql/types/query_complexity_type.rb
@@ -5,7 +5,7 @@ module Types
class QueryComplexityType < ::Types::BaseObject
graphql_name 'QueryComplexity'
- ANALYZER = GraphQL::Analysis::QueryComplexity.new { |_query, complexity| complexity }
+ ANALYZER = GraphQL::Analysis::AST::QueryComplexity
alias_method :query, :object
@@ -23,7 +23,7 @@ module Types
description: 'GraphQL query complexity score.'
def score
- ::GraphQL::Analysis.analyze_query(query, [ANALYZER]).first
+ ::GraphQL::Analysis::AST.analyze_query(query, [ANALYZER]).first
end
end
# rubocop: enable Graphql/AuthorizeTypes
diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb
index 01b1a71896a..46d121f6552 100644
--- a/app/graphql/types/query_type.rb
+++ b/app/graphql/types/query_type.rb
@@ -52,6 +52,7 @@ module Types
field :milestone, ::Types::MilestoneType,
null: true,
+ extras: [:lookahead],
description: 'Find a milestone.' do
argument :id, ::Types::GlobalIDType[Milestone], required: true, description: 'Find a milestone by its ID.'
end
@@ -90,8 +91,8 @@ module Types
field :work_item, Types::WorkItemType,
null: true,
resolver: Resolvers::WorkItemResolver,
- description: 'Find a work item. Returns `null` if `work_items` feature flag is disabled.' \
- ' The feature is experimental and is subject to change without notice.'
+ deprecated: { milestone: '15.1', reason: :alpha },
+ description: 'Find a work item. Returns `null` if `work_items` feature flag is disabled.'
field :merge_request, Types::MergeRequestType,
null: true,
@@ -156,8 +157,9 @@ module Types
GitlabSchema.find_by_gid(id)
end
- def milestone(id:)
- GitlabSchema.find_by_gid(id)
+ def milestone(id:, lookahead:)
+ preloads = [:releases] if lookahead.selects?(:releases)
+ Gitlab::Graphql::Loaders::BatchModelLoader.new(id.model_class, id.model_id, preloads).find
end
def container_repository(id:)
diff --git a/app/graphql/types/release_asset_link_type.rb b/app/graphql/types/release_asset_link_type.rb
index 33dcb5125e3..29738de27e5 100644
--- a/app/graphql/types/release_asset_link_type.rb
+++ b/app/graphql/types/release_asset_link_type.rb
@@ -7,6 +7,8 @@ module Types
authorize :read_release
+ present_using Releases::LinkPresenter
+
field :external, GraphQL::Types::Boolean, null: true, method: :external?,
description: 'Indicates the link points to an external resource.'
field :id, GraphQL::Types::ID, null: false,
@@ -22,12 +24,5 @@ module Types
description: 'Relative path for the direct asset link.'
field :direct_asset_url, GraphQL::Types::String, null: true,
description: 'Direct asset URL of the link.'
-
- def direct_asset_url
- return object.url unless object.filepath
-
- release = object.release.present
- release.download_url(object.filepath)
- end
end
end
diff --git a/app/graphql/types/release_type.rb b/app/graphql/types/release_type.rb
index 95b6b43bb46..43dc0c4ce85 100644
--- a/app/graphql/types/release_type.rb
+++ b/app/graphql/types/release_type.rb
@@ -13,6 +13,9 @@ module Types
present_using ReleasePresenter
+ field :id, ::Types::GlobalIDType[Release],
+ null: false,
+ description: 'Global ID of the release.'
field :assets, Types::ReleaseAssetsType, null: true, method: :itself,
description: 'Assets of the release.'
field :created_at, Types::TimeType, null: true,
diff --git a/app/graphql/types/terraform/state_type.rb b/app/graphql/types/terraform/state_type.rb
index bce34a85f85..be17fc41c2c 100644
--- a/app/graphql/types/terraform/state_type.rb
+++ b/app/graphql/types/terraform/state_type.rb
@@ -38,6 +38,10 @@ module Types
null: false,
description: 'Timestamp the Terraform state was updated.'
+ field :deleted_at, Types::TimeType,
+ null: true,
+ description: 'Timestamp the Terraform state was deleted.'
+
def locked_by_user
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.locked_by_user_id).find
end
diff --git a/app/graphql/types/time_type.rb b/app/graphql/types/time_type.rb
index 2db14953308..121515c04db 100644
--- a/app/graphql/types/time_type.rb
+++ b/app/graphql/types/time_type.rb
@@ -12,6 +12,9 @@ module Types
DESC
def self.coerce_input(value, ctx)
+ # arguments can be nil, so don't raise an error
+ return if value.nil?
+
Time.parse(value)
rescue ArgumentError, TypeError => e
raise GraphQL::CoercionError, e.message
diff --git a/app/graphql/types/todo_type.rb b/app/graphql/types/todo_type.rb
index f21b2b261a3..0de6b1d6f8a 100644
--- a/app/graphql/types/todo_type.rb
+++ b/app/graphql/types/todo_type.rb
@@ -53,6 +53,10 @@ module Types
description: 'Timestamp this to-do item was created.',
null: false
+ field :note, Types::Notes::NoteType,
+ description: 'Note which created this to-do item.',
+ null: true
+
def project
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find
end
diff --git a/app/graphql/types/work_item_sort_enum.rb b/app/graphql/types/work_item_sort_enum.rb
new file mode 100644
index 00000000000..e644313d409
--- /dev/null
+++ b/app/graphql/types/work_item_sort_enum.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Types
+ class WorkItemSortEnum < SortEnum
+ graphql_name 'WorkItemSort'
+ description 'Values for sorting work items'
+
+ value 'TITLE_ASC', 'Title by ascending order.', value: :title_asc
+ value 'TITLE_DESC', 'Title by descending order.', value: :title_desc
+ end
+end
diff --git a/app/graphql/types/work_item_type.rb b/app/graphql/types/work_item_type.rb
index cd784d54959..18b9bfd1c9a 100644
--- a/app/graphql/types/work_item_type.rb
+++ b/app/graphql/types/work_item_type.rb
@@ -18,6 +18,8 @@ module Types
description: 'State of the work item.'
field :title, GraphQL::Types::String, null: false,
description: 'Title of the work item.'
+ field :widgets, [Types::WorkItems::WidgetInterface], null: true,
+ description: 'Collection of widgets that belong to the work item.'
field :work_item_type, Types::WorkItems::TypeType, null: false,
description: 'Type assigned to the work item.'
diff --git a/app/graphql/types/work_items/updated_task_input_type.rb b/app/graphql/types/work_items/updated_task_input_type.rb
new file mode 100644
index 00000000000..9f8afa2ff1b
--- /dev/null
+++ b/app/graphql/types/work_items/updated_task_input_type.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ class UpdatedTaskInputType < BaseInputObject
+ graphql_name 'WorkItemUpdatedTaskInput'
+
+ include Mutations::WorkItems::UpdateArguments
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/widget_interface.rb b/app/graphql/types/work_items/widget_interface.rb
new file mode 100644
index 00000000000..f3cf1d74829
--- /dev/null
+++ b/app/graphql/types/work_items/widget_interface.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ module WidgetInterface
+ include Types::BaseInterface
+
+ graphql_name 'WorkItemWidget'
+
+ field :type, ::Types::WorkItems::WidgetTypeEnum, null: true,
+ description: 'Widget type.'
+
+ def self.resolve_type(object, context)
+ case object
+ when ::WorkItems::Widgets::Description
+ ::Types::WorkItems::Widgets::DescriptionType
+ when ::WorkItems::Widgets::Hierarchy
+ ::Types::WorkItems::Widgets::HierarchyType
+ else
+ raise "Unknown GraphQL type for widget #{object}"
+ end
+ end
+
+ orphan_types ::Types::WorkItems::Widgets::DescriptionType,
+ ::Types::WorkItems::Widgets::HierarchyType
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/widget_type_enum.rb b/app/graphql/types/work_items/widget_type_enum.rb
new file mode 100644
index 00000000000..4e5933bff86
--- /dev/null
+++ b/app/graphql/types/work_items/widget_type_enum.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ class WidgetTypeEnum < BaseEnum
+ graphql_name 'WorkItemWidgetType'
+ description 'Type of a work item widget'
+
+ ::WorkItems::Type.available_widgets.each do |widget|
+ value widget.type.to_s.upcase, value: widget.type, description: "#{widget.type.to_s.titleize} widget."
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/widgets/description_input_type.rb b/app/graphql/types/work_items/widgets/description_input_type.rb
new file mode 100644
index 00000000000..382cfdf659f
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/description_input_type.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ module Widgets
+ class DescriptionInputType < BaseInputObject
+ graphql_name 'WorkItemWidgetDescriptionInput'
+
+ argument :description, GraphQL::Types::String,
+ required: true,
+ description: copy_field_description(Types::WorkItemType, :description)
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/widgets/description_type.rb b/app/graphql/types/work_items/widgets/description_type.rb
new file mode 100644
index 00000000000..79192d7c3d4
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/description_type.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ module Widgets
+ # Disabling widget level authorization as it might be too granular
+ # and we already authorize the parent work item
+ # rubocop:disable Graphql/AuthorizeTypes
+ class DescriptionType < BaseObject
+ graphql_name 'WorkItemWidgetDescription'
+ description 'Represents a description widget'
+
+ implements Types::WorkItems::WidgetInterface
+
+ field :description, GraphQL::Types::String, null: true,
+ description: 'Description of the work item.'
+
+ markdown_field :description_html, null: true do |resolved_object|
+ resolved_object.work_item
+ end
+ end
+ # rubocop:enable Graphql/AuthorizeTypes
+ end
+ end
+end
diff --git a/app/graphql/types/work_items/widgets/hierarchy_type.rb b/app/graphql/types/work_items/widgets/hierarchy_type.rb
new file mode 100644
index 00000000000..057d5fbf056
--- /dev/null
+++ b/app/graphql/types/work_items/widgets/hierarchy_type.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ module Widgets
+ # Disabling widget level authorization as it might be too granular
+ # and we already authorize the parent work item
+ # rubocop:disable Graphql/AuthorizeTypes
+ class HierarchyType < BaseObject
+ graphql_name 'WorkItemWidgetHierarchy'
+ description 'Represents a hierarchy widget'
+
+ implements Types::WorkItems::WidgetInterface
+
+ field :parent, ::Types::WorkItemType, null: true,
+ description: 'Parent work item.',
+ complexity: 5
+
+ field :children, ::Types::WorkItemType.connection_type, null: true,
+ description: 'Child work items.',
+ complexity: 5
+
+ def children
+ object.children.inc_relations_for_permission_check
+ end
+ end
+ # rubocop:enable Graphql/AuthorizeTypes
+ end
+ end
+end