diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 08:43:02 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 08:43:02 +0000 |
commit | d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch) | |
tree | 2341ef426af70ad1e289c38036737e04b0aa5007 /app/graphql | |
parent | d6e514dd13db8947884cd58fe2a9c2a063400a9b (diff) | |
download | gitlab-ce-d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb.tar.gz |
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'app/graphql')
50 files changed, 946 insertions, 34 deletions
diff --git a/app/graphql/mutations/ci/runner/delete.rb b/app/graphql/mutations/ci/runner/delete.rb index 8d9a5f15505..88dc426398b 100644 --- a/app/graphql/mutations/ci/runner/delete.rb +++ b/app/graphql/mutations/ci/runner/delete.rb @@ -28,7 +28,7 @@ module Mutations def authenticate_delete_runner!(runner) return if current_user.can_admin_all_resources? - "Runner #{runner.to_global_id} associated with more than one project" if runner.projects.count > 1 + "Runner #{runner.to_global_id} associated with more than one project" if runner.runner_projects.count > 1 end def find_object(id) diff --git a/app/graphql/mutations/clusters/agent_tokens/create.rb b/app/graphql/mutations/clusters/agent_tokens/create.rb new file mode 100644 index 00000000000..07bf2536065 --- /dev/null +++ b/app/graphql/mutations/clusters/agent_tokens/create.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module Mutations + module Clusters + module AgentTokens + class Create < BaseMutation + graphql_name 'ClusterAgentTokenCreate' + + authorize :create_cluster + + ClusterAgentID = ::Types::GlobalIDType[::Clusters::Agent] + + argument :cluster_agent_id, + ClusterAgentID, + required: true, + description: 'Global ID of the cluster agent that will be associated with the new token.' + + argument :description, + GraphQL::Types::String, + required: false, + description: 'Description of the token.' + + argument :name, + GraphQL::Types::String, + required: true, + description: 'Name of the token.' + + field :secret, + GraphQL::Types::String, + null: true, + description: "Token secret value. Make sure you save it - you won't be able to access it again." + + field :token, + Types::Clusters::AgentTokenType, + null: true, + description: 'Token created after mutation.' + + def resolve(args) + cluster_agent = authorized_find!(id: args[:cluster_agent_id]) + + result = ::Clusters::AgentTokens::CreateService + .new( + container: cluster_agent.project, + current_user: current_user, + params: args.merge(agent_id: cluster_agent.id) + ) + .execute + + payload = result.payload + + { + secret: payload[:secret], + token: payload[:token], + errors: Array.wrap(result.message) + } + end + + private + + def find_object(id:) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = ClusterAgentID.coerce_isolated_input(id) + GitlabSchema.find_by_gid(id) + end + end + end + end +end diff --git a/app/graphql/mutations/clusters/agent_tokens/delete.rb b/app/graphql/mutations/clusters/agent_tokens/delete.rb new file mode 100644 index 00000000000..603b6b30910 --- /dev/null +++ b/app/graphql/mutations/clusters/agent_tokens/delete.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Mutations + module Clusters + module AgentTokens + class Delete < BaseMutation + graphql_name 'ClusterAgentTokenDelete' + + authorize :admin_cluster + + TokenID = ::Types::GlobalIDType[::Clusters::AgentToken] + + argument :id, TokenID, + required: true, + description: 'Global ID of the cluster agent token that will be deleted.' + + def resolve(id:) + token = authorized_find!(id: id) + token.destroy + + { errors: errors_on_object(token) } + end + + private + + def find_object(id:) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = TokenID.coerce_isolated_input(id) + GitlabSchema.find_by_gid(id) + end + end + end + end +end diff --git a/app/graphql/mutations/clusters/agents/create.rb b/app/graphql/mutations/clusters/agents/create.rb new file mode 100644 index 00000000000..0896cc7b203 --- /dev/null +++ b/app/graphql/mutations/clusters/agents/create.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Mutations + module Clusters + module Agents + class Create < BaseMutation + include FindsProject + + authorize :create_cluster + + graphql_name 'CreateClusterAgent' + + argument :project_path, GraphQL::Types::ID, + required: true, + description: 'Full path of the associated project for this cluster agent.' + + argument :name, GraphQL::Types::String, + required: true, + description: 'Name of the cluster agent.' + + field :cluster_agent, + Types::Clusters::AgentType, + null: true, + description: 'Cluster agent created after mutation.' + + def resolve(project_path:, name:) + project = authorized_find!(project_path) + result = ::Clusters::Agents::CreateService.new(project, current_user).execute(name: name) + + { + cluster_agent: result[:cluster_agent], + errors: Array.wrap(result[:message]) + } + end + end + end + end +end diff --git a/app/graphql/mutations/clusters/agents/delete.rb b/app/graphql/mutations/clusters/agents/delete.rb new file mode 100644 index 00000000000..9ada1f31f60 --- /dev/null +++ b/app/graphql/mutations/clusters/agents/delete.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Mutations + module Clusters + module Agents + class Delete < BaseMutation + graphql_name 'ClusterAgentDelete' + + authorize :admin_cluster + + AgentID = ::Types::GlobalIDType[::Clusters::Agent] + + argument :id, AgentID, + required: true, + description: 'Global ID of the cluster agent that will be deleted.' + + def resolve(id:) + cluster_agent = authorized_find!(id: id) + result = ::Clusters::Agents::DeleteService + .new(container: cluster_agent.project, current_user: current_user) + .execute(cluster_agent) + + { + errors: Array.wrap(result.message) + } + end + + private + + def find_object(id:) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = AgentID.coerce_isolated_input(id) + GitlabSchema.find_by_gid(id) + end + end + end + end +end diff --git a/app/graphql/mutations/customer_relations/contacts/create.rb b/app/graphql/mutations/customer_relations/contacts/create.rb new file mode 100644 index 00000000000..77b4864468b --- /dev/null +++ b/app/graphql/mutations/customer_relations/contacts/create.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module Mutations + module CustomerRelations + module Contacts + class Create < BaseMutation + include ResolvesIds + include Gitlab::Graphql::Authorize::AuthorizeResource + + graphql_name 'CustomerRelationsContactCreate' + + field :contact, + Types::CustomerRelations::ContactType, + null: true, + description: 'Contact after the mutation.' + + argument :group_id, ::Types::GlobalIDType[::Group], + required: true, + description: 'Group for the contact.' + + argument :organization_id, ::Types::GlobalIDType[::CustomerRelations::Organization], + required: false, + description: 'Organization for the contact.' + + argument :first_name, GraphQL::Types::String, + required: true, + description: 'First name of the contact.' + + argument :last_name, GraphQL::Types::String, + required: true, + description: 'Last name of the contact.' + + argument :phone, GraphQL::Types::String, + required: false, + description: 'Phone number of the contact.' + + argument :email, GraphQL::Types::String, + required: false, + description: 'Email address of the contact.' + + argument :description, GraphQL::Types::String, + required: false, + description: 'Description of or notes for the contact.' + + authorize :admin_contact + + def resolve(args) + group = authorized_find!(id: args[:group_id]) + + raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'Feature disabled' unless Feature.enabled?(:customer_relations, group, default_enabled: :yaml) + + set_organization!(args) + result = ::CustomerRelations::Contacts::CreateService.new(group: group, current_user: current_user, params: args).execute + { contact: result.payload, errors: result.errors } + end + + def find_object(id:) + GitlabSchema.object_from_id(id, expected_type: ::Group) + end + + def set_organization!(args) + return unless args[:organization_id] + + args[:organization_id] = resolve_ids(args[:organization_id], ::Types::GlobalIDType[::CustomerRelations::Organization])[0] + end + end + end + end +end diff --git a/app/graphql/mutations/customer_relations/contacts/update.rb b/app/graphql/mutations/customer_relations/contacts/update.rb new file mode 100644 index 00000000000..e9e7c9b6abd --- /dev/null +++ b/app/graphql/mutations/customer_relations/contacts/update.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module Mutations + module CustomerRelations + module Contacts + class Update < Mutations::BaseMutation + include ResolvesIds + + graphql_name 'CustomerRelationsContactUpdate' + + authorize :admin_contact + + field :contact, + Types::CustomerRelations::ContactType, + null: true, + description: 'Contact after the mutation.' + + argument :id, ::Types::GlobalIDType[::CustomerRelations::Contact], + required: true, + description: 'Global ID of the contact.' + + argument :organization_id, ::Types::GlobalIDType[::CustomerRelations::Organization], + required: false, + description: 'Organization of the contact.' + + argument :first_name, GraphQL::Types::String, + required: false, + description: 'First name of the contact.' + + argument :last_name, GraphQL::Types::String, + required: false, + description: 'Last name of the contact.' + + argument :phone, GraphQL::Types::String, + required: false, + description: 'Phone number of the contact.' + + argument :email, GraphQL::Types::String, + required: false, + description: 'Email address of the contact.' + + argument :description, GraphQL::Types::String, + required: false, + description: 'Description of or notes for the contact.' + + def resolve(args) + contact = ::Gitlab::Graphql::Lazy.force(GitlabSchema.object_from_id(args.delete(:id), expected_type: ::CustomerRelations::Contact)) + raise_resource_not_available_error! unless contact + + group = contact.group + raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'Feature disabled' unless Feature.enabled?(:customer_relations, group, default_enabled: :yaml) + + authorize!(group) + + result = ::CustomerRelations::Contacts::UpdateService.new(group: group, current_user: current_user, params: args).execute(contact) + { contact: result.payload, errors: result.errors } + end + end + end + end +end diff --git a/app/graphql/mutations/customer_relations/organizations/create.rb b/app/graphql/mutations/customer_relations/organizations/create.rb index 3fa7b0327ca..bb02e1f7346 100644 --- a/app/graphql/mutations/customer_relations/organizations/create.rb +++ b/app/graphql/mutations/customer_relations/organizations/create.rb @@ -31,7 +31,7 @@ module Mutations argument :description, GraphQL::Types::String, required: false, - description: 'Description or notes for the organization.' + description: 'Description of or notes for the organization.' authorize :admin_organization diff --git a/app/graphql/mutations/customer_relations/organizations/update.rb b/app/graphql/mutations/customer_relations/organizations/update.rb index c6ae62193f9..d8eb55d77e9 100644 --- a/app/graphql/mutations/customer_relations/organizations/update.rb +++ b/app/graphql/mutations/customer_relations/organizations/update.rb @@ -32,7 +32,7 @@ module Mutations argument :description, GraphQL::Types::String, required: false, - description: 'Description or notes for the organization.' + description: 'Description of or notes for the organization.' def resolve(args) organization = ::Gitlab::Graphql::Lazy.force(GitlabSchema.object_from_id(args.delete(:id), expected_type: ::CustomerRelations::Organization)) diff --git a/app/graphql/mutations/dependency_proxy/group_settings/update.rb b/app/graphql/mutations/dependency_proxy/group_settings/update.rb new file mode 100644 index 00000000000..d10e43cde29 --- /dev/null +++ b/app/graphql/mutations/dependency_proxy/group_settings/update.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Mutations + module DependencyProxy + module GroupSettings + class Update < Mutations::BaseMutation + include Mutations::ResolvesGroup + + graphql_name 'UpdateDependencyProxySettings' + + authorize :admin_dependency_proxy + + argument :group_path, + GraphQL::Types::ID, + required: true, + description: 'Group path for the group dependency proxy.' + + argument :enabled, + GraphQL::Types::Boolean, + required: false, + description: copy_field_description(Types::DependencyProxy::ImageTtlGroupPolicyType, :enabled) + + field :dependency_proxy_setting, + Types::DependencyProxy::GroupSettingType, + null: true, + description: 'Group dependency proxy settings after mutation.' + + def resolve(group_path:, **args) + group = authorized_find!(group_path: group_path) + + result = ::DependencyProxy::GroupSettings::UpdateService + .new(container: group, current_user: current_user, params: args) + .execute + + { + dependency_proxy_setting: result.payload[:dependency_proxy_setting], + errors: result.errors + } + end + + private + + def find_object(group_path:) + resolve_group(full_path: group_path) + end + end + end + end +end diff --git a/app/graphql/mutations/issues/create.rb b/app/graphql/mutations/issues/create.rb index 32f96f1bfe6..70a8f539ccf 100644 --- a/app/graphql/mutations/issues/create.rb +++ b/app/graphql/mutations/issues/create.rb @@ -71,7 +71,7 @@ module Mutations def resolve(project_path:, **attributes) project = authorized_find!(project_path) - params = build_create_issue_params(attributes.merge(author_id: current_user.id)) + params = build_create_issue_params(attributes.merge(author_id: current_user.id), project) spam_params = ::Spam::SpamParams.new_from_request(request: context[:request]) issue = ::Issues::CreateService.new(project: project, current_user: current_user, params: params, spam_params: spam_params).execute @@ -88,7 +88,8 @@ module Mutations private - def build_create_issue_params(params) + # _project argument is unused here, but it is necessary on the EE version of the method + def build_create_issue_params(params, _project) params[:milestone_id] &&= params[:milestone_id]&.model_id params[:assignee_ids] &&= params[:assignee_ids].map { |assignee_id| assignee_id&.model_id } params[:label_ids] &&= params[:label_ids].map { |label_id| label_id&.model_id } diff --git a/app/graphql/resolvers/board_list_issues_resolver.rb b/app/graphql/resolvers/board_list_issues_resolver.rb index 7c85dd8fb9b..d70acdf7ca0 100644 --- a/app/graphql/resolvers/board_list_issues_resolver.rb +++ b/app/graphql/resolvers/board_list_issues_resolver.rb @@ -18,11 +18,8 @@ module Resolvers filter_params = filters.merge(board_id: list.board.id, id: list.id) service = ::Boards::Issues::ListService.new(list.board.resource_parent, context[:current_user], filter_params) - pagination_connections = Gitlab::Graphql::Pagination::Keyset::Connection.new(service.execute) - ::Boards::Issues::ListService.initialize_relative_positions(list.board, current_user, pagination_connections.items) - - pagination_connections + service.execute end # https://gitlab.com/gitlab-org/gitlab/-/issues/235681 diff --git a/app/graphql/resolvers/board_list_resolver.rb b/app/graphql/resolvers/board_list_resolver.rb new file mode 100644 index 00000000000..d853846b674 --- /dev/null +++ b/app/graphql/resolvers/board_list_resolver.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Resolvers + class BoardListResolver < BaseResolver.single + include Gitlab::Graphql::Authorize::AuthorizeResource + include BoardItemFilterable + + type Types::BoardListType, null: true + description 'Find an issue board list.' + + authorize :read_issue_board_list + + argument :id, Types::GlobalIDType[List], + required: true, + description: 'Global ID of the list.' + + argument :issue_filters, Types::Boards::BoardIssueInputType, + required: false, + description: 'Filters applied when getting issue metadata in the board list.' + + def resolve(id: nil, issue_filters: {}) + context.scoped_set!(:issue_filters, item_filters(issue_filters)) + + Gitlab::Graphql::Lazy.with_value(find_list(id: id)) do |list| + list if authorized_resource?(list) + end + end + + private + + def find_list(id:) + GitlabSchema.object_from_id(id, expected_type: ::List) + end + end +end diff --git a/app/graphql/resolvers/clusters/agent_tokens_resolver.rb b/app/graphql/resolvers/clusters/agent_tokens_resolver.rb new file mode 100644 index 00000000000..5ae19700fd5 --- /dev/null +++ b/app/graphql/resolvers/clusters/agent_tokens_resolver.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Resolvers + module Clusters + class AgentTokensResolver < BaseResolver + type Types::Clusters::AgentTokenType, null: true + + alias_method :agent, :object + + delegate :project, to: :agent + + def resolve(**args) + return ::Clusters::AgentToken.none unless can_read_agent_tokens? + + agent.last_used_agent_tokens + end + + private + + def can_read_agent_tokens? + current_user.can?(:admin_cluster, project) + end + end + end +end diff --git a/app/graphql/resolvers/clusters/agents_resolver.rb b/app/graphql/resolvers/clusters/agents_resolver.rb new file mode 100644 index 00000000000..9b8cea52e3b --- /dev/null +++ b/app/graphql/resolvers/clusters/agents_resolver.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Resolvers + module Clusters + class AgentsResolver < BaseResolver + include LooksAhead + + type Types::Clusters::AgentType.connection_type, null: true + + extras [:lookahead] + + when_single do + argument :name, GraphQL::Types::String, + required: true, + description: 'Name of the cluster agent.' + end + + alias_method :project, :object + + def resolve_with_lookahead(**args) + apply_lookahead( + ::Clusters::AgentsFinder + .new(project, current_user, params: args) + .execute + ) + end + + private + + def preloads + { tokens: :last_used_agent_tokens } + end + end + end +end diff --git a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb index 9de36b5b7d1..855877110e5 100644 --- a/app/graphql/resolvers/concerns/issue_resolver_arguments.rb +++ b/app/graphql/resolvers/concerns/issue_resolver_arguments.rb @@ -4,6 +4,7 @@ module IssueResolverArguments extend ActiveSupport::Concern prepended do + include SearchArguments include LooksAhead argument :iid, GraphQL::Types::String, @@ -49,9 +50,6 @@ module IssueResolverArguments argument :closed_after, Types::TimeType, required: false, description: 'Issues closed after this date.' - argument :search, GraphQL::Types::String, - required: false, - description: 'Search query for issue title or description.' argument :types, [Types::IssueTypeEnum], as: :issue_types, description: 'Filter issues by the given issue types.', @@ -62,6 +60,10 @@ module IssueResolverArguments argument :my_reaction_emoji, GraphQL::Types::String, required: false, description: 'Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported.' + argument :confidential, + GraphQL::Types::Boolean, + required: false, + 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 }, @@ -91,6 +93,7 @@ module IssueResolverArguments 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) + validate_anonymous_search_access! if args[:search].present? super end diff --git a/app/graphql/resolvers/concerns/search_arguments.rb b/app/graphql/resolvers/concerns/search_arguments.rb new file mode 100644 index 00000000000..7f480f9d0b6 --- /dev/null +++ b/app/graphql/resolvers/concerns/search_arguments.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module SearchArguments + extend ActiveSupport::Concern + + included do + argument :search, GraphQL::Types::String, + required: false, + description: 'Search query for title or description.' + end + + def validate_anonymous_search_access! + return if current_user.present? || Feature.disabled?(:disable_anonymous_search, type: :ops) + + raise ::Gitlab::Graphql::Errors::ArgumentError, + "User must be authenticated to include the `search` argument." + end +end diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb index 47e4e3c0b32..b556964ae0c 100644 --- a/app/graphql/resolvers/issues_resolver.rb +++ b/app/graphql/resolvers/issues_resolver.rb @@ -47,7 +47,8 @@ module Resolvers alert_management_alert: [:alert_management_alert], labels: [:labels], assignees: [:assignees], - timelogs: [:timelogs] + timelogs: [:timelogs], + customer_relations_contacts: { customer_relations_contacts: [:group] } } end diff --git a/app/graphql/resolvers/kas/agent_configurations_resolver.rb b/app/graphql/resolvers/kas/agent_configurations_resolver.rb new file mode 100644 index 00000000000..238dae0bf12 --- /dev/null +++ b/app/graphql/resolvers/kas/agent_configurations_resolver.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Resolvers + module Kas + class AgentConfigurationsResolver < BaseResolver + type Types::Kas::AgentConfigurationType, null: true + + # Calls Gitaly via KAS + calls_gitaly! + + alias_method :project, :object + + def resolve + return [] unless can_read_agent_configuration? + + kas_client.list_agent_config_files(project: project) + rescue GRPC::BadStatus => e + raise Gitlab::Graphql::Errors::ResourceNotAvailable, e.class.name + end + + private + + def can_read_agent_configuration? + current_user.can?(:admin_cluster, project) + end + + def kas_client + @kas_client ||= Gitlab::Kas::Client.new + end + end + end +end diff --git a/app/graphql/resolvers/kas/agent_connections_resolver.rb b/app/graphql/resolvers/kas/agent_connections_resolver.rb new file mode 100644 index 00000000000..8b7c4003598 --- /dev/null +++ b/app/graphql/resolvers/kas/agent_connections_resolver.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Resolvers + module Kas + class AgentConnectionsResolver < BaseResolver + type Types::Kas::AgentConnectionType, null: true + + alias_method :agent, :object + + delegate :project, to: :agent + + def resolve + return [] unless can_read_connected_agents? + + BatchLoader::GraphQL.for(agent.id).batch(key: project, default_value: []) do |agent_ids, loader| + agents = get_connected_agents.group_by(&:agent_id).slice(*agent_ids) + + agents.each do |agent_id, connections| + loader.call(agent_id, connections) + end + end + end + + private + + def can_read_connected_agents? + current_user.can?(:admin_cluster, project) + end + + def get_connected_agents + kas_client.get_connected_agents(project: project) + rescue GRPC::BadStatus => e + raise Gitlab::Graphql::Errors::ResourceNotAvailable, e.class.name + end + + def kas_client + @kas_client ||= Gitlab::Kas::Client.new + end + end + end +end diff --git a/app/graphql/resolvers/project_pipeline_resolver.rb b/app/graphql/resolvers/project_pipeline_resolver.rb index ce4b6ac6b0c..5acd7f95606 100644 --- a/app/graphql/resolvers/project_pipeline_resolver.rb +++ b/app/graphql/resolvers/project_pipeline_resolver.rb @@ -2,6 +2,8 @@ module Resolvers class ProjectPipelineResolver < BaseResolver + include LooksAhead + type ::Types::Ci::PipelineType, null: true alias_method :project, :object @@ -14,7 +16,7 @@ module Resolvers required: false, description: 'SHA of the Pipeline. For example, "dyd0f15ay83993f5ab66k927w28673882x99100b".' - def ready?(iid: nil, sha: nil) + def ready?(iid: nil, sha: nil, **args) unless iid.present? ^ sha.present? raise Gitlab::Graphql::Errors::ArgumentError, 'Provide one of an IID or SHA' end @@ -22,18 +24,21 @@ module Resolvers super end - def resolve(iid: nil, sha: nil) + # the preloads are defined on ee/app/graphql/ee/resolvers/project_pipeline_resolver.rb + def resolve(iid: nil, sha: nil, **args) + self.lookahead = args.delete(:lookahead) + if iid - BatchLoader::GraphQL.for(iid).batch(key: project) do |iids, loader, args| + BatchLoader::GraphQL.for(iid).batch(key: project) do |iids, loader| finder = ::Ci::PipelinesFinder.new(project, current_user, iids: iids) - finder.execute.each { |pipeline| loader.call(pipeline.iid.to_s, pipeline) } + apply_lookahead(finder.execute).each { |pipeline| loader.call(pipeline.iid.to_s, pipeline) } end else - BatchLoader::GraphQL.for(sha).batch(key: project) do |shas, loader, args| + BatchLoader::GraphQL.for(sha).batch(key: project) do |shas, loader| finder = ::Ci::PipelinesFinder.new(project, current_user, sha: shas) - finder.execute.each { |pipeline| loader.call(pipeline.sha.to_s, pipeline) } + apply_lookahead(finder.execute).each { |pipeline| loader.call(pipeline.sha.to_s, pipeline) } end end end diff --git a/app/graphql/resolvers/project_pipelines_resolver.rb b/app/graphql/resolvers/project_pipelines_resolver.rb index 0171473a77f..5a1e92efc96 100644 --- a/app/graphql/resolvers/project_pipelines_resolver.rb +++ b/app/graphql/resolvers/project_pipelines_resolver.rb @@ -26,3 +26,5 @@ module Resolvers end end # rubocop: enable Graphql/ResolverType + +Resolvers::ProjectPipelinesResolver.prepend_mod diff --git a/app/graphql/types/base_field.rb b/app/graphql/types/base_field.rb index 9c27f0f8138..93e17ea6dfc 100644 --- a/app/graphql/types/base_field.rb +++ b/app/graphql/types/base_field.rb @@ -9,7 +9,6 @@ module Types DEFAULT_COMPLEXITY = 1 attr_reader :deprecation, :doc_reference - attr_writer :max_page_size # Can be removed with :performance_roadmap feature flag: https://gitlab.com/gitlab-org/gitlab/-/issues/337198 def initialize(**kwargs, &block) @calls_gitaly = !!kwargs.delete(:calls_gitaly) @@ -21,6 +20,7 @@ module Types @feature_flag = kwargs[:feature_flag] kwargs = check_feature_flag(kwargs) @deprecation = gitlab_deprecation(kwargs) + after_connection_extensions = kwargs.delete(:late_extensions) || [] super(**kwargs, &block) @@ -28,6 +28,8 @@ module Types extension ::Gitlab::Graphql::CallsGitaly::FieldExtension if Gitlab.dev_or_test_env? extension ::Gitlab::Graphql::Present::FieldExtension extension ::Gitlab::Graphql::Authorize::ConnectionFilterExtension + + after_connection_extensions.each { extension _1 } if after_connection_extensions.any? end def may_call_gitaly? diff --git a/app/graphql/types/board_list_type.rb b/app/graphql/types/board_list_type.rb index 762e03973d9..8c67803e39e 100644 --- a/app/graphql/types/board_list_type.rb +++ b/app/graphql/types/board_list_type.rb @@ -10,8 +10,10 @@ module Types alias_method :list, :object - field :id, GraphQL::Types::ID, null: false, + field :id, GraphQL::Types::ID, + null: false, description: 'ID (global ID) of the list.' + field :title, GraphQL::Types::String, null: false, description: 'Title of the list.' field :list_type, GraphQL::Types::String, null: false, @@ -27,6 +29,7 @@ module Types field :issues, ::Types::IssueType.connection_type, null: true, description: 'Board issues.', + late_extensions: [Gitlab::Graphql::Board::IssuesConnectionExtension], resolver: ::Resolvers::BoardListIssuesResolver def issues_count @@ -46,6 +49,16 @@ module Types .metadata end end + + # board lists have a data dependency on label - so we batch load them here + def title + BatchLoader::GraphQL.for(object).batch do |lists, callback| + ActiveRecord::Associations::Preloader.new.preload(lists, :label) # rubocop: disable CodeReuse/ActiveRecord + + # all list titles are preloaded at this point + lists.each { |list| callback.call(list, list.title) } + end + end end # rubocop: enable Graphql/AuthorizeTypes end diff --git a/app/graphql/types/ci/runner_status_enum.rb b/app/graphql/types/ci/runner_status_enum.rb index ad69175e44a..8501ce20204 100644 --- a/app/graphql/types/ci/runner_status_enum.rb +++ b/app/graphql/types/ci/runner_status_enum.rb @@ -6,8 +6,21 @@ module Types graphql_name 'CiRunnerStatus' ::Ci::Runner::AVAILABLE_STATUSES.each do |status| + description = case status + when 'active' + "A runner that is not paused." + when 'online' + "A runner that contacted this instance within the last #{::Ci::Runner::ONLINE_CONTACT_TIMEOUT.inspect}." + when 'offline' + "A runner that has not contacted this instance within the last #{::Ci::Runner::ONLINE_CONTACT_TIMEOUT.inspect}." + when 'not_connected' + "A runner that has never contacted this instance." + else + "A runner that is #{status.to_s.tr('_', ' ')}." + end + value status.to_s.upcase, - description: "A runner that is #{status.to_s.tr('_', ' ')}.", + description: description, value: status.to_sym end end diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb index e2c8070af0c..9bf98aa7e86 100644 --- a/app/graphql/types/ci/runner_type.rb +++ b/app/graphql/types/ci/runner_type.rb @@ -3,10 +3,13 @@ module Types module Ci class RunnerType < BaseObject + edge_type_class(RunnerWebUrlEdge) graphql_name 'CiRunner' authorize :read_runner present_using ::Ci::RunnerPresenter + expose_permissions Types::PermissionTypes::Ci::Runner + JOB_COUNT_LIMIT = 1000 alias_method :runner, :object @@ -46,12 +49,18 @@ module Types description: 'Number of projects that the runner is associated with.' field :job_count, GraphQL::Types::Int, null: true, description: "Number of jobs processed by the runner (limited to #{JOB_COUNT_LIMIT}, plus one to indicate that more items exist)." + field :admin_url, GraphQL::Types::String, null: true, + description: 'Admin URL of the runner. Only available for adminstrators.' def job_count # We limit to 1 above the JOB_COUNT_LIMIT to indicate that more items exist after JOB_COUNT_LIMIT runner.builds.limit(JOB_COUNT_LIMIT + 1).count end + def admin_url + Gitlab::Routing.url_helpers.admin_runner_url(runner) if can_admin_runners? + end + # rubocop: disable CodeReuse/ActiveRecord def project_count BatchLoader::GraphQL.for(runner.id).batch(key: :runner_project_count) do |ids, loader, args| @@ -68,6 +77,12 @@ module Types end end # rubocop: enable CodeReuse/ActiveRecord + + private + + def can_admin_runners? + context[:current_user]&.can_admin_all_resources? + end 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 new file mode 100644 index 00000000000..3b9fdfd1571 --- /dev/null +++ b/app/graphql/types/ci/runner_web_url_edge.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Types + module Ci + # rubocop: disable Graphql/AuthorizeTypes + class RunnerWebUrlEdge < GraphQL::Types::Relay::BaseEdge + include FindClosest + + field :web_url, GraphQL::Types::String, null: true, + description: 'Web URL of the runner. The value depends on where you put this field in the query. You can use it for projects or groups.', + extras: [:parent] + + def initialize(node, connection) + super + + @runner = node.node + end + + def web_url(parent:) + owner = closest_parent([::Types::ProjectType, ::Types::GroupType], parent) + + case owner + when ::Group + Gitlab::Routing.url_helpers.group_runner_url(owner, @runner) + when ::Project + Gitlab::Routing.url_helpers.project_runner_url(owner, @runner) + end + end + end + end +end diff --git a/app/graphql/types/clusters/agent_token_type.rb b/app/graphql/types/clusters/agent_token_type.rb new file mode 100644 index 00000000000..94c5fc46a5d --- /dev/null +++ b/app/graphql/types/clusters/agent_token_type.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Types + module Clusters + class AgentTokenType < BaseObject + graphql_name 'ClusterAgentToken' + + authorize :admin_cluster + + connection_type_class(Types::CountableConnectionType) + + field :cluster_agent, + Types::Clusters::AgentType, + description: 'Cluster agent this token is associated with.', + null: true + + field :created_at, + Types::TimeType, + null: true, + description: 'Timestamp the token was created.' + + field :created_by_user, + Types::UserType, + null: true, + description: 'User who created the token.' + + field :description, + GraphQL::Types::String, + null: true, + description: 'Description of the token.' + + field :last_used_at, + Types::TimeType, + null: true, + description: 'Timestamp the token was last used.' + + field :id, + ::Types::GlobalIDType[::Clusters::AgentToken], + null: false, + description: 'Global ID of the token.' + + field :name, + GraphQL::Types::String, + null: true, + description: 'Name given to the token.' + + def cluster_agent + Gitlab::Graphql::Loaders::BatchModelLoader.new(::Clusters::Agent, object.agent_id).find + end + end + end +end diff --git a/app/graphql/types/clusters/agent_type.rb b/app/graphql/types/clusters/agent_type.rb new file mode 100644 index 00000000000..ce748f6e8ae --- /dev/null +++ b/app/graphql/types/clusters/agent_type.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module Types + module Clusters + class AgentType < BaseObject + graphql_name 'ClusterAgent' + + authorize :admin_cluster + + connection_type_class(Types::CountableConnectionType) + + field :created_at, + Types::TimeType, + null: true, + description: 'Timestamp the cluster agent was created.' + + field :created_by_user, + Types::UserType, + null: true, + description: 'User object, containing information about the person who created the agent.' + + field :id, GraphQL::Types::ID, + null: false, + description: 'ID of the cluster agent.' + + field :name, + GraphQL::Types::String, + null: true, + description: 'Name of the cluster agent.' + + field :project, Types::ProjectType, + description: 'Project this cluster agent is associated with.', + null: true, + authorize: :read_project + + field :tokens, Types::Clusters::AgentTokenType.connection_type, + description: 'Tokens associated with the cluster agent.', + null: true, + resolver: ::Resolvers::Clusters::AgentTokensResolver + + field :updated_at, + Types::TimeType, + null: true, + description: 'Timestamp the cluster agent was updated.' + + field :web_path, + GraphQL::Types::String, + null: true, + description: 'Web path of the cluster agent.' + + field :connections, + Types::Kas::AgentConnectionType.connection_type, + null: true, + description: 'Active connections for the cluster agent', + complexity: 5, + resolver: ::Resolvers::Kas::AgentConnectionsResolver + + def project + Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find + end + + def web_path + ::Gitlab::Routing.url_helpers.project_cluster_agent_path(object.project, object.name) + end + end + end +end diff --git a/app/graphql/types/concerns/find_closest.rb b/app/graphql/types/concerns/find_closest.rb index 1d76e872364..3064db19ea0 100644 --- a/app/graphql/types/concerns/find_closest.rb +++ b/app/graphql/types/concerns/find_closest.rb @@ -1,11 +1,15 @@ # frozen_string_literal: true module FindClosest - # Find the closest node of a given type above this node, and return the domain object - def closest_parent(type, parent) - parent = parent.try(:parent) while parent && parent.object.class != type - return unless parent + # 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 - parent.object.object + 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/container_expiration_policy_older_than_enum.rb b/app/graphql/types/container_expiration_policy_older_than_enum.rb index 7364910f8cd..9c32d767caf 100644 --- a/app/graphql/types/container_expiration_policy_older_than_enum.rb +++ b/app/graphql/types/container_expiration_policy_older_than_enum.rb @@ -6,6 +6,7 @@ module Types '7d': 'SEVEN_DAYS', '14d': 'FOURTEEN_DAYS', '30d': 'THIRTY_DAYS', + '60d': 'SIXTY_DAYS', '90d': 'NINETY_DAYS' }.freeze diff --git a/app/graphql/types/container_repository_details_type.rb b/app/graphql/types/container_repository_details_type.rb index 1a9f57e701f..8190cc9bc25 100644 --- a/app/graphql/types/container_repository_details_type.rb +++ b/app/graphql/types/container_repository_details_type.rb @@ -17,5 +17,11 @@ module Types def can_delete Ability.allowed?(current_user, :destroy_container_image, object) end + + def tags + object.tags + rescue Faraday::Error + raise ::Gitlab::Graphql::Errors::ResourceNotAvailable, 'We are having trouble connecting to the Container Registry. If this error persists, please review the troubleshooting documentation.' + end end end diff --git a/app/graphql/types/container_repository_type.rb b/app/graphql/types/container_repository_type.rb index 67093f57862..1fe5cf112f0 100644 --- a/app/graphql/types/container_repository_type.rb +++ b/app/graphql/types/container_repository_type.rb @@ -28,5 +28,11 @@ module Types def project Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find end + + def tags_count + object.tags_count + rescue Faraday::Error + raise ::Gitlab::Graphql::Errors::ResourceNotAvailable, 'We are having trouble connecting to the Container Registry. If this error persists, please review the troubleshooting documentation.' + end end end diff --git a/app/graphql/types/customer_relations/contact_type.rb b/app/graphql/types/customer_relations/contact_type.rb index 35b5bf45698..b5224a3e239 100644 --- a/app/graphql/types/customer_relations/contact_type.rb +++ b/app/graphql/types/customer_relations/contact_type.rb @@ -39,7 +39,7 @@ module Types field :description, GraphQL::Types::String, null: true, - description: 'Description or notes for the contact.' + description: 'Description of or notes for the contact.' field :created_at, Types::TimeType, diff --git a/app/graphql/types/customer_relations/organization_type.rb b/app/graphql/types/customer_relations/organization_type.rb index 0e091d4a9a3..9b22fa35b11 100644 --- a/app/graphql/types/customer_relations/organization_type.rb +++ b/app/graphql/types/customer_relations/organization_type.rb @@ -25,7 +25,7 @@ module Types field :description, GraphQL::Types::String, null: true, - description: 'Description or notes for the organization.' + description: 'Description of or notes for the organization.' field :created_at, Types::TimeType, diff --git a/app/graphql/types/error_tracking/sentry_detailed_error_type.rb b/app/graphql/types/error_tracking/sentry_detailed_error_type.rb index 79e789d3f8b..826ae61a1a3 100644 --- a/app/graphql/types/error_tracking/sentry_detailed_error_type.rb +++ b/app/graphql/types/error_tracking/sentry_detailed_error_type.rb @@ -13,6 +13,9 @@ module Types field :id, GraphQL::Types::ID, null: false, description: 'ID (global ID) of the error.' + field :integrated, GraphQL::Types::Boolean, + null: true, + description: 'Error tracking backend.' field :sentry_id, GraphQL::Types::String, method: :id, null: false, diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb index 8fe4ba557ea..b1bbabcdaed 100644 --- a/app/graphql/types/group_type.rb +++ b/app/graphql/types/group_type.rb @@ -234,6 +234,10 @@ module Types ) end + def dependency_proxy_setting + group.dependency_proxy_setting || group.create_dependency_proxy_setting + end + private def group diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index c8db2b84ff2..3b0f93d8dc1 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -136,6 +136,9 @@ module Types field :project_id, GraphQL::Types::Int, null: false, method: :project_id, description: 'ID of the issue project.' + field :customer_relations_contacts, Types::CustomerRelations::ContactType.connection_type, null: true, + description: 'Customer relations contacts of the issue.' + def author Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find end diff --git a/app/graphql/types/issues/negated_issue_filter_input_type.rb b/app/graphql/types/issues/negated_issue_filter_input_type.rb index 4f620a5b3d9..c8b7cdaa68e 100644 --- a/app/graphql/types/issues/negated_issue_filter_input_type.rb +++ b/app/graphql/types/issues/negated_issue_filter_input_type.rb @@ -29,6 +29,10 @@ module Types argument :my_reaction_emoji, GraphQL::Types::String, required: false, description: 'Filter by reaction emoji applied by the current user.' + argument :types, [Types::IssueTypeEnum], + as: :issue_types, + description: 'Filters out issues by the given issue types.', + required: false end end end diff --git a/app/graphql/types/kas/agent_configuration_type.rb b/app/graphql/types/kas/agent_configuration_type.rb new file mode 100644 index 00000000000..397a5739671 --- /dev/null +++ b/app/graphql/types/kas/agent_configuration_type.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Types + module Kas + # rubocop: disable Graphql/AuthorizeTypes + class AgentConfigurationType < BaseObject + graphql_name 'AgentConfiguration' + description 'Configuration details for an Agent' + + field :agent_name, + GraphQL::Types::String, + null: true, + description: 'Name of the agent.' + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/kas/agent_connection_type.rb b/app/graphql/types/kas/agent_connection_type.rb new file mode 100644 index 00000000000..9c6321bece9 --- /dev/null +++ b/app/graphql/types/kas/agent_connection_type.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Types + module Kas + # rubocop: disable Graphql/AuthorizeTypes + class AgentConnectionType < BaseObject + graphql_name 'ConnectedAgent' + description 'Connection details for an Agent' + + field :connected_at, + Types::TimeType, + null: true, + description: 'When the connection was established.' + + field :connection_id, + GraphQL::Types::BigInt, + null: true, + description: 'ID of the connection.' + + field :metadata, + Types::Kas::AgentMetadataType, + method: :agent_meta, + null: true, + description: 'Information about the Agent.' + + def connected_at + Time.at(object.connected_at.seconds) + end + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/kas/agent_metadata_type.rb b/app/graphql/types/kas/agent_metadata_type.rb new file mode 100644 index 00000000000..4a3bb09b9e1 --- /dev/null +++ b/app/graphql/types/kas/agent_metadata_type.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Types + module Kas + # rubocop: disable Graphql/AuthorizeTypes + class AgentMetadataType < BaseObject + graphql_name 'AgentMetadata' + description 'Information about a connected Agent' + + field :version, + GraphQL::Types::String, + null: true, + description: 'Agent version tag.' + + field :commit, + GraphQL::Types::String, + method: :commit_id, + null: true, + description: 'Agent version commit.' + + field :pod_namespace, + GraphQL::Types::String, + null: true, + description: 'Namespace of the pod running the Agent.' + + field :pod_name, + GraphQL::Types::String, + null: true, + description: 'Name of the pod running the Agent.' + end + # rubocop: enable Graphql/AuthorizeTypes + 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 d685ac4d3c9..d4a1f2faa8d 100644 --- a/app/graphql/types/merge_requests/interacts_with_merge_request.rb +++ b/app/graphql/types/merge_requests/interacts_with_merge_request.rb @@ -14,7 +14,7 @@ module Types end def merge_request_interaction(parent:) - merge_request = closest_parent(::Types::MergeRequestType, parent) + merge_request = closest_parent([::Types::MergeRequestType], parent) return unless merge_request Users::MergeRequestInteraction.new(user: object, merge_request: merge_request) diff --git a/app/graphql/types/milestone_wildcard_id_enum.rb b/app/graphql/types/milestone_wildcard_id_enum.rb index 12e8e07fb05..ad9651a26dc 100644 --- a/app/graphql/types/milestone_wildcard_id_enum.rb +++ b/app/graphql/types/milestone_wildcard_id_enum.rb @@ -8,6 +8,6 @@ module Types value 'NONE', 'No milestone is assigned.' value 'ANY', 'Milestone is assigned.' value 'STARTED', 'Milestone assigned is open and started (start date <= today).' - value 'UPCOMING', 'Milestone assigned is due closest in the future (due date > today).' + value 'UPCOMING', 'Milestone assigned is due in the future (due date > today).' end end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index ea50af1c554..cd4c45d2942 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -31,13 +31,20 @@ module Types mount_mutation Mutations::Boards::Lists::Update mount_mutation Mutations::Boards::Lists::Destroy mount_mutation Mutations::Branches::Create, calls_gitaly: true + mount_mutation Mutations::Clusters::Agents::Create + mount_mutation Mutations::Clusters::Agents::Delete + mount_mutation Mutations::Clusters::AgentTokens::Create + mount_mutation Mutations::Clusters::AgentTokens::Delete mount_mutation Mutations::Commits::Create, calls_gitaly: true mount_mutation Mutations::CustomEmoji::Create, feature_flag: :custom_emoji mount_mutation Mutations::CustomEmoji::Destroy, feature_flag: :custom_emoji + mount_mutation Mutations::CustomerRelations::Contacts::Create + mount_mutation Mutations::CustomerRelations::Contacts::Update mount_mutation Mutations::CustomerRelations::Organizations::Create mount_mutation Mutations::CustomerRelations::Organizations::Update mount_mutation Mutations::Discussions::ToggleResolve mount_mutation Mutations::DependencyProxy::ImageTtlGroupPolicy::Update + mount_mutation Mutations::DependencyProxy::GroupSettings::Update mount_mutation Mutations::Environments::CanaryIngress::Update mount_mutation Mutations::Issues::Create mount_mutation Mutations::Issues::SetAssignees diff --git a/app/graphql/types/packages/nuget/metadatum_type.rb b/app/graphql/types/packages/nuget/metadatum_type.rb index ed9d97724af..b58fd954a74 100644 --- a/app/graphql/types/packages/nuget/metadatum_type.rb +++ b/app/graphql/types/packages/nuget/metadatum_type.rb @@ -10,9 +10,9 @@ module Types authorize :read_package field :id, ::Types::GlobalIDType[::Packages::Nuget::Metadatum], null: false, description: 'ID of the metadatum.' - field :license_url, GraphQL::Types::String, null: false, description: 'License URL of the Nuget package.' - field :project_url, GraphQL::Types::String, null: false, description: 'Project URL of the Nuget package.' - field :icon_url, GraphQL::Types::String, null: false, description: 'Icon URL of the Nuget package.' + field :license_url, GraphQL::Types::String, null: true, description: 'License URL of the Nuget package.' + field :project_url, GraphQL::Types::String, null: true, description: 'Project URL of the Nuget package.' + field :icon_url, GraphQL::Types::String, null: true, description: 'Icon URL of the Nuget package.' end end end diff --git a/app/graphql/types/packages/package_type.rb b/app/graphql/types/packages/package_type.rb index f3fa79cc08c..9851c6aec7e 100644 --- a/app/graphql/types/packages/package_type.rb +++ b/app/graphql/types/packages/package_type.rb @@ -6,6 +6,8 @@ module Types graphql_name 'Package' description 'Represents a package in the Package Registry. Note that this type is in beta and susceptible to changes' + connection_type_class(Types::CountableConnectionType) + authorize :read_package field :id, ::Types::GlobalIDType[::Packages::Package], null: false, @@ -26,6 +28,7 @@ module Types description: 'Other versions of the package.', deprecated: { reason: 'This field is now only returned in the PackageDetailsType', milestone: '13.11' } field :status, Types::Packages::PackageStatusEnum, null: false, description: 'Package status.' + field :can_destroy, GraphQL::Types::Boolean, null: false, description: 'Whether the user can destroy the package.' def project Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find @@ -35,6 +38,10 @@ module Types [] end + def can_destroy + Ability.allowed?(current_user, :destroy_package, object) + end + # NOTE: This method must be kept in sync with the union # type: `Types::Packages::MetadataType`. # diff --git a/app/graphql/types/permission_types/ci/runner.rb b/app/graphql/types/permission_types/ci/runner.rb new file mode 100644 index 00000000000..2e92a4011e9 --- /dev/null +++ b/app/graphql/types/permission_types/ci/runner.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Types + module PermissionTypes + module Ci + class Runner < BasePermissionType + graphql_name 'RunnerPermissions' + + abilities :read_runner, :update_runner, :delete_runner + end + end + end +end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index aef46a05a2f..791875242df 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -208,6 +208,7 @@ module Types Types::Ci::PipelineType, null: true, description: 'Build pipeline of the project.', + extras: [:lookahead], resolver: Resolvers::ProjectPipelineResolver field :ci_cd_settings, @@ -361,6 +362,25 @@ module Types complexity: 5, resolver: ::Resolvers::TimelogResolver + field :agent_configurations, + ::Types::Kas::AgentConfigurationType.connection_type, + null: true, + description: 'Agent configurations defined by the project', + resolver: ::Resolvers::Kas::AgentConfigurationsResolver + + field :cluster_agent, + ::Types::Clusters::AgentType, + null: true, + description: 'Find a single cluster agent by name.', + resolver: ::Resolvers::Clusters::AgentsResolver.single + + field :cluster_agents, + ::Types::Clusters::AgentType.connection_type, + extras: [:lookahead], + null: true, + description: 'Cluster agents associated with the project.', + resolver: ::Resolvers::Clusters::AgentsResolver + def label(title:) BatchLoader::GraphQL.for(title).batch(key: project) do |titles, loader, args| LabelsFinder diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index e02191fbf3e..ed4ddbb982b 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -136,6 +136,10 @@ module Types complexity: 5, resolver: ::Resolvers::TimelogResolver + field :board_list, ::Types::BoardListType, + null: true, + resolver: Resolvers::BoardListResolver + def design_management DesignManagementObject.new(nil) end |