diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-19 14:01:45 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-19 14:01:45 +0300 |
commit | 9297025d0b7ddf095eb618dfaaab2ff8f2018d8b (patch) | |
tree | 865198c01d1824a9b098127baa3ab980c9cd2c06 /app/graphql | |
parent | 6372471f43ee03c05a7c1f8b0c6ac6b8a7431dbe (diff) |
Add latest changes from gitlab-org/gitlab@16-7-stable-eev16.7.0-rc42
Diffstat (limited to 'app/graphql')
116 files changed, 1477 insertions, 556 deletions
diff --git a/app/graphql/mutations/achievements/award.rb b/app/graphql/mutations/achievements/award.rb index b486049594d..71a46a04a1c 100644 --- a/app/graphql/mutations/achievements/award.rb +++ b/app/graphql/mutations/achievements/award.rb @@ -29,10 +29,6 @@ module Mutations result = ::Achievements::AwardService.new(current_user, achievement.id, recipient_id).execute { user_achievement: result.payload, errors: result.errors } end - - def find_object(id:) - GitlabSchema.object_from_id(id, expected_type: ::Achievements::Achievement) - end end end end diff --git a/app/graphql/mutations/achievements/create.rb b/app/graphql/mutations/achievements/create.rb index 310a653c705..497eaee9b70 100644 --- a/app/graphql/mutations/achievements/create.rb +++ b/app/graphql/mutations/achievements/create.rb @@ -41,10 +41,6 @@ module Mutations params: args).execute { achievement: result.payload, errors: result.errors } end - - def find_object(id:) - GitlabSchema.object_from_id(id, expected_type: ::Namespace) - end end end end diff --git a/app/graphql/mutations/achievements/delete.rb b/app/graphql/mutations/achievements/delete.rb index 0b510b44b4e..fc00261a176 100644 --- a/app/graphql/mutations/achievements/delete.rb +++ b/app/graphql/mutations/achievements/delete.rb @@ -24,10 +24,6 @@ module Mutations result = ::Achievements::DestroyService.new(current_user, achievement).execute { achievement: result.payload, errors: result.errors } end - - def find_object(id:) - GitlabSchema.object_from_id(id, expected_type: ::Achievements::Achievement) - end end end end diff --git a/app/graphql/mutations/achievements/delete_user_achievement.rb b/app/graphql/mutations/achievements/delete_user_achievement.rb index f1527c2981a..be4c5a1d5e2 100644 --- a/app/graphql/mutations/achievements/delete_user_achievement.rb +++ b/app/graphql/mutations/achievements/delete_user_achievement.rb @@ -24,10 +24,6 @@ module Mutations result = ::Achievements::DestroyUserAchievementService.new(current_user, user_achievement).execute { user_achievement: result.payload, errors: result.errors } end - - def find_object(id:) - GitlabSchema.object_from_id(id, expected_type: ::Achievements::UserAchievement) - end end end end diff --git a/app/graphql/mutations/achievements/revoke.rb b/app/graphql/mutations/achievements/revoke.rb index 9d21b1c3741..ac5b38cefbf 100644 --- a/app/graphql/mutations/achievements/revoke.rb +++ b/app/graphql/mutations/achievements/revoke.rb @@ -24,10 +24,6 @@ module Mutations result = ::Achievements::RevokeService.new(current_user, user_achievement).execute { user_achievement: result.payload, errors: result.errors } end - - def find_object(id:) - GitlabSchema.object_from_id(id, expected_type: ::Achievements::UserAchievement) - end end end end diff --git a/app/graphql/mutations/achievements/update.rb b/app/graphql/mutations/achievements/update.rb index 2a9e6580629..8bb95ac41f3 100644 --- a/app/graphql/mutations/achievements/update.rb +++ b/app/graphql/mutations/achievements/update.rb @@ -37,10 +37,6 @@ module Mutations result = ::Achievements::UpdateService.new(current_user, achievement, args).execute { achievement: result.payload, errors: result.errors } end - - def find_object(id:) - GitlabSchema.object_from_id(id, expected_type: ::Achievements::Achievement) - end end end end diff --git a/app/graphql/mutations/alert_management/http_integration/http_integration_base.rb b/app/graphql/mutations/alert_management/http_integration/http_integration_base.rb index 9434ac1637e..760005ae249 100644 --- a/app/graphql/mutations/alert_management/http_integration/http_integration_base.rb +++ b/app/graphql/mutations/alert_management/http_integration/http_integration_base.rb @@ -13,10 +13,6 @@ module Mutations private - def find_object(id:) - GitlabSchema.object_from_id(id, expected_class: ::AlertManagement::HttpIntegration) - end - def response(result) { integration: result.payload[:integration], diff --git a/app/graphql/mutations/alert_management/prometheus_integration/prometheus_integration_base.rb b/app/graphql/mutations/alert_management/prometheus_integration/prometheus_integration_base.rb index 28729ec70cd..19fb514d3a5 100644 --- a/app/graphql/mutations/alert_management/prometheus_integration/prometheus_integration_base.rb +++ b/app/graphql/mutations/alert_management/prometheus_integration/prometheus_integration_base.rb @@ -13,10 +13,6 @@ module Mutations private - def find_object(id:) - GitlabSchema.object_from_id(id, expected_class: ::Integrations::Prometheus) - end - def response(integration, result) { integration: integration, diff --git a/app/graphql/mutations/boards/destroy.rb b/app/graphql/mutations/boards/destroy.rb index 61e0c95f8d3..abdffaaeb90 100644 --- a/app/graphql/mutations/boards/destroy.rb +++ b/app/graphql/mutations/boards/destroy.rb @@ -26,12 +26,6 @@ module Mutations errors: response.errors } end - - private - - def find_object(id:) - GitlabSchema.object_from_id(id, expected_type: ::Board) - end end end end diff --git a/app/graphql/mutations/boards/lists/create.rb b/app/graphql/mutations/boards/lists/create.rb index 590a905ab7b..a80a6af9e2f 100644 --- a/app/graphql/mutations/boards/lists/create.rb +++ b/app/graphql/mutations/boards/lists/create.rb @@ -19,10 +19,6 @@ module Mutations private - def find_object(id:) - GitlabSchema.object_from_id(id, expected_type: ::Board) - end - def create_list(board, params) create_list_service = ::Boards::Lists::CreateService.new(board.resource_parent, current_user, params) diff --git a/app/graphql/mutations/branch_rules/update.rb b/app/graphql/mutations/branch_rules/update.rb new file mode 100644 index 00000000000..c10e11970eb --- /dev/null +++ b/app/graphql/mutations/branch_rules/update.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Mutations + module BranchRules + class Update < BaseMutation + graphql_name 'BranchRuleUpdate' + + include FindsProject + + authorize :admin_project + + argument :id, ::Types::GlobalIDType[::ProtectedBranch], + required: true, + description: 'Global ID of the protected branch.' + + argument :name, GraphQL::Types::String, + required: true, + description: 'Branch name, with wildcards, for the branch rules.' + + argument :project_path, GraphQL::Types::ID, + required: true, + description: 'Full path to the project that the branch is associated with.' + + field :branch_rule, + Types::Projects::BranchRuleType, + null: true, + description: 'Branch rule after mutation.' + + def resolve(id:, project_path:, name:) + protected_branch = ::Gitlab::Graphql::Lazy.force(GitlabSchema.object_from_id(id, + expected_type: ::ProtectedBranch)) + raise_resource_not_available_error! unless protected_branch + + project = authorized_find!(project_path) + + protected_branch = ::ProtectedBranches::UpdateService.new(project, current_user, + { name: name }).execute(protected_branch) + + if protected_branch.errors.empty? + { + branch_rule: ::Projects::BranchRule.new(project, protected_branch), + errors: [] + } + else + { errors: errors_on_object(protected_branch) } + end + end + end + end +end diff --git a/app/graphql/mutations/ci/catalog/resources/base.rb b/app/graphql/mutations/ci/catalog/resources/base.rb new file mode 100644 index 00000000000..4ff245d52ec --- /dev/null +++ b/app/graphql/mutations/ci/catalog/resources/base.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Mutations + module Ci + module Catalog + module Resources + class Base < BaseMutation + argument :project_path, GraphQL::Types::ID, + required: true, + description: 'Project path belonging to the catalog resource.' + + def find_object(project_path:) + Project.find_by_full_path(project_path) + end + end + end + end + end +end diff --git a/app/graphql/mutations/ci/catalog/resources/create.rb b/app/graphql/mutations/ci/catalog/resources/create.rb index 7f934e101c8..34d60f780ca 100644 --- a/app/graphql/mutations/ci/catalog/resources/create.rb +++ b/app/graphql/mutations/ci/catalog/resources/create.rb @@ -4,13 +4,9 @@ module Mutations module Ci module Catalog module Resources - class Create < BaseMutation + class Create < Base graphql_name 'CatalogResourcesCreate' - argument :project_path, GraphQL::Types::ID, - required: true, - description: 'Project to convert to a catalog resource.' - authorize :add_catalog_resource def resolve(project_path:) @@ -23,12 +19,6 @@ module Mutations errors: errors } end - - private - - def find_object(project_path:) - Project.find_by_full_path(project_path) - end end end end diff --git a/app/graphql/mutations/ci/catalog/resources/destroy.rb b/app/graphql/mutations/ci/catalog/resources/destroy.rb new file mode 100644 index 00000000000..d33a8c1ef70 --- /dev/null +++ b/app/graphql/mutations/ci/catalog/resources/destroy.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Mutations + module Ci + module Catalog + module Resources + class Destroy < Base + graphql_name 'CatalogResourcesDestroy' + + authorize :add_catalog_resource + + def resolve(project_path:) + project = authorized_find!(project_path: project_path) + catalog_resource = project.catalog_resource + + response = ::Ci::Catalog::Resources::DestroyService.new(project, current_user).execute(catalog_resource) + + errors = response.success? ? [] : [response.message] + + { + errors: errors + } + end + end + end + end + end +end diff --git a/app/graphql/mutations/ci/catalog/resources/unpublish.rb b/app/graphql/mutations/ci/catalog/resources/unpublish.rb deleted file mode 100644 index e45e9646147..00000000000 --- a/app/graphql/mutations/ci/catalog/resources/unpublish.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -module Mutations - module Ci - module Catalog - module Resources - class Unpublish < BaseMutation - graphql_name 'CatalogResourceUnpublish' - - authorize :add_catalog_resource - - argument :id, ::Types::GlobalIDType[::Ci::Catalog::Resource], - required: true, - description: 'Global ID of the catalog resource to unpublish.' - - def resolve(id:) - catalog_resource = ::Gitlab::Graphql::Lazy.force(GitlabSchema.find_by_gid(id)) - authorize!(catalog_resource&.project) - - catalog_resource.unpublish! - - { - errors: [] - } - end - end - end - end - end -end diff --git a/app/graphql/mutations/container_registry/protection/rule/create.rb b/app/graphql/mutations/container_registry/protection/rule/create.rb index cf8416480a2..5b01d13d8cb 100644 --- a/app/graphql/mutations/container_registry/protection/rule/create.rb +++ b/app/graphql/mutations/container_registry/protection/rule/create.rb @@ -18,12 +18,12 @@ module Mutations required: true, description: 'Full path of the project where a protection rule is located.' - argument :container_path_pattern, + argument :repository_path_pattern, GraphQL::Types::String, required: true, description: - 'ContainerRegistryname protected by the protection rule. For example `@my-scope/my-container-*`. ' \ - 'Wildcard character `*` allowed.' + 'Container repository path pattern protected by the protection rule. ' \ + 'For example `my-project/my-container-*`. Wildcard character `*` allowed.' argument :push_protected_up_to_access_level, Types::ContainerRegistry::Protection::RuleAccessLevelEnum, diff --git a/app/graphql/mutations/container_registry/protection/rule/delete.rb b/app/graphql/mutations/container_registry/protection/rule/delete.rb new file mode 100644 index 00000000000..b1673b7c43e --- /dev/null +++ b/app/graphql/mutations/container_registry/protection/rule/delete.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Mutations + module ContainerRegistry + module Protection + module Rule + class Delete < ::Mutations::BaseMutation + graphql_name 'DeleteContainerRegistryProtectionRule' + description 'Deletes a container registry protection rule. ' \ + 'Available only when feature flag `container_registry_protected_containers` is enabled.' + + authorize :admin_container_image + + argument :id, + ::Types::GlobalIDType[::ContainerRegistry::Protection::Rule], + required: true, + description: 'Global ID of the container registry protection rule to delete.' + + field :container_registry_protection_rule, + Types::ContainerRegistry::Protection::RuleType, + null: true, + description: 'Container registry protection rule that was deleted successfully.' + + def resolve(id:, **_kwargs) + if Feature.disabled?(:container_registry_protected_containers) + raise_resource_not_available_error!("'container_registry_protected_containers' feature flag is disabled") + end + + container_registry_protection_rule = authorized_find!(id: id) + + response = ::ContainerRegistry::Protection::DeleteRuleService.new(container_registry_protection_rule, + current_user: current_user).execute + + { container_registry_protection_rule: response.payload[:container_registry_protection_rule], + errors: response.errors } + end + end + end + end + end +end diff --git a/app/graphql/mutations/container_registry/protection/rule/update.rb b/app/graphql/mutations/container_registry/protection/rule/update.rb new file mode 100644 index 00000000000..b4464e5b5f4 --- /dev/null +++ b/app/graphql/mutations/container_registry/protection/rule/update.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module Mutations + module ContainerRegistry + module Protection + module Rule + class Update < ::Mutations::BaseMutation + graphql_name 'UpdateContainerRegistryProtectionRule' + description 'Updates a container registry protection rule to restrict access to project containers. ' \ + 'You can prevent users without certain roles from altering containers. ' \ + 'Available only when feature flag `container_registry_protected_containers` is enabled.' + + authorize :admin_container_image + + argument :id, + ::Types::GlobalIDType[::ContainerRegistry::Protection::Rule], + required: true, + description: 'Global ID of the container registry protection rule to be updated.' + + argument :repository_path_pattern, + GraphQL::Types::String, + required: false, + validates: { allow_blank: false }, + description: + 'Container\'s repository path pattern of the protection rule. ' \ + 'For example, `my-scope/my-project/container-dev-*`. ' \ + 'Wildcard character `*` allowed.' + + argument :delete_protected_up_to_access_level, + Types::ContainerRegistry::Protection::RuleAccessLevelEnum, + required: false, + validates: { allow_blank: false }, + description: + 'Maximum GitLab access level prevented from deleting a container. ' \ + 'For example, `DEVELOPER`, `MAINTAINER`, `OWNER`.' + + argument :push_protected_up_to_access_level, + Types::ContainerRegistry::Protection::RuleAccessLevelEnum, + required: false, + validates: { allow_blank: false }, + description: + 'Maximum GitLab access level prevented from pushing a container. ' \ + 'For example, `DEVELOPER`, `MAINTAINER`, `OWNER`.' + + field :container_registry_protection_rule, + Types::ContainerRegistry::Protection::RuleType, + null: true, + description: 'Container registry protection rule after mutation.' + + def resolve(id:, **kwargs) + container_registry_protection_rule = authorized_find!(id: id) + + if Feature.disabled?(:container_registry_protected_containers, container_registry_protection_rule.project) + raise_resource_not_available_error!("'container_registry_protected_containers' feature flag is disabled") + end + + response = ::ContainerRegistry::Protection::UpdateRuleService.new(container_registry_protection_rule, + current_user: current_user, params: kwargs).execute + + { container_registry_protection_rule: response.payload[:container_registry_protection_rule], + errors: response.errors } + end + end + end + end + end +end diff --git a/app/graphql/mutations/custom_emoji/destroy.rb b/app/graphql/mutations/custom_emoji/destroy.rb index 64e3f2ed7d3..0dd989ad841 100644 --- a/app/graphql/mutations/custom_emoji/destroy.rb +++ b/app/graphql/mutations/custom_emoji/destroy.rb @@ -29,12 +29,6 @@ module Mutations custom_emoji: custom_emoji } end - - private - - def find_object(id:) - GitlabSchema.object_from_id(id, expected_type: ::CustomEmoji) - end end end end diff --git a/app/graphql/mutations/customer_relations/contacts/create.rb b/app/graphql/mutations/customer_relations/contacts/create.rb index 5b4063fb89a..e3a8eba14c8 100644 --- a/app/graphql/mutations/customer_relations/contacts/create.rb +++ b/app/graphql/mutations/customer_relations/contacts/create.rb @@ -43,10 +43,6 @@ module Mutations 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 end end end diff --git a/app/graphql/mutations/customer_relations/organizations/create.rb b/app/graphql/mutations/customer_relations/organizations/create.rb index 43c50a9fb30..9be66830640 100644 --- a/app/graphql/mutations/customer_relations/organizations/create.rb +++ b/app/graphql/mutations/customer_relations/organizations/create.rb @@ -41,10 +41,6 @@ module Mutations result = ::CustomerRelations::Organizations::CreateService.new(group: group, current_user: current_user, params: args).execute { organization: result.payload, errors: result.errors } end - - def find_object(id:) - GitlabSchema.object_from_id(id, expected_type: ::Group) - end end end end diff --git a/app/graphql/mutations/namespace/package_settings/update.rb b/app/graphql/mutations/namespace/package_settings/update.rb index 97c16ee79fe..813c5687642 100644 --- a/app/graphql/mutations/namespace/package_settings/update.rb +++ b/app/graphql/mutations/namespace/package_settings/update.rb @@ -81,6 +81,11 @@ module Mutations required: false, description: copy_field_description(Types::Namespace::PackageSettingsType, :lock_pypi_package_requests_forwarding) + argument :nuget_symbol_server_enabled, + GraphQL::Types::Boolean, + required: false, + description: copy_field_description(Types::Namespace::PackageSettingsType, :nuget_symbol_server_enabled) + field :package_settings, Types::Namespace::PackageSettingsType, null: true, diff --git a/app/graphql/mutations/organizations/base.rb b/app/graphql/mutations/organizations/base.rb new file mode 100644 index 00000000000..112eb12f0d7 --- /dev/null +++ b/app/graphql/mutations/organizations/base.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Mutations + module Organizations + class Base < BaseMutation + field :organization, + ::Types::Organizations::OrganizationType, + null: true, + description: 'Organization after mutation.' + + argument :description, GraphQL::Types::String, + required: false, + description: 'Description of the organization.' + + argument :avatar, ApolloUploadServer::Upload, + required: false, + description: 'Avatar for the organization.' + end + end +end diff --git a/app/graphql/mutations/organizations/create.rb b/app/graphql/mutations/organizations/create.rb index 0d1b204a4c1..e64c964e8b2 100644 --- a/app/graphql/mutations/organizations/create.rb +++ b/app/graphql/mutations/organizations/create.rb @@ -2,16 +2,11 @@ module Mutations module Organizations - class Create < BaseMutation + class Create < Base graphql_name 'OrganizationCreate' authorize :create_organization - field :organization, - ::Types::Organizations::OrganizationType, - null: true, - description: 'Organization created.' - argument :name, GraphQL::Types::String, required: true, description: 'Name for the organization.' @@ -28,7 +23,7 @@ module Mutations params: args ).execute - { organization: result.payload, errors: result.errors } + { organization: result.payload[:organization], errors: result.errors } end end end diff --git a/app/graphql/mutations/organizations/update.rb b/app/graphql/mutations/organizations/update.rb new file mode 100644 index 00000000000..929f2605735 --- /dev/null +++ b/app/graphql/mutations/organizations/update.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Mutations + module Organizations + class Update < Base + graphql_name 'OrganizationUpdate' + + authorize :admin_organization + + argument :id, + Types::GlobalIDType[::Organizations::Organization], + required: true, + description: 'ID of the organization to mutate.' + + argument :name, GraphQL::Types::String, + required: false, + description: 'Name for the organization.' + + argument :path, GraphQL::Types::String, + required: false, + description: 'Path for the organization.' + + def resolve(id:, **args) + organization = authorized_find!(id: id) + + result = ::Organizations::UpdateService.new( + organization, + current_user: current_user, + params: args + ).execute + + { organization: result.payload[:organization], errors: result.errors } + end + end + end +end diff --git a/app/graphql/mutations/packages/protection/rule/update.rb b/app/graphql/mutations/packages/protection/rule/update.rb new file mode 100644 index 00000000000..dc1f78e6822 --- /dev/null +++ b/app/graphql/mutations/packages/protection/rule/update.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module Mutations + module Packages + module Protection + module Rule + class Update < ::Mutations::BaseMutation + graphql_name 'UpdatePackagesProtectionRule' + description 'Updates a package protection rule to restrict access to project packages. ' \ + 'You can prevent users without certain permissions from altering packages. ' \ + 'Available only when feature flag `packages_protected_packages` is enabled.' + + authorize :admin_package + + argument :id, + ::Types::GlobalIDType[::Packages::Protection::Rule], + required: true, + description: 'Global ID of the package protection rule to be updated.' + + argument :package_name_pattern, + GraphQL::Types::String, + required: false, + validates: { allow_blank: false }, + description: + 'Package name protected by the protection rule. For example, `@my-scope/my-package-*`. ' \ + 'Wildcard character `*` allowed.' + + argument :package_type, + Types::Packages::Protection::RulePackageTypeEnum, + required: false, + validates: { allow_blank: false }, + description: 'Package type protected by the protection rule. For example, `NPM`.' + + argument :push_protected_up_to_access_level, + Types::Packages::Protection::RuleAccessLevelEnum, + required: false, + validates: { allow_blank: false }, + description: + 'Maximum GitLab access level unable to push a package. For example, `DEVELOPER`, `MAINTAINER`, `OWNER`.' + + field :package_protection_rule, + Types::Packages::Protection::RuleType, + null: true, + description: 'Packages protection rule after mutation.' + + def resolve(id:, **kwargs) + package_protection_rule = authorized_find!(id: id) + + if Feature.disabled?(:packages_protected_packages, package_protection_rule.project) + raise_resource_not_available_error!("'packages_protected_packages' feature flag is disabled") + end + + response = ::Packages::Protection::UpdateRuleService.new(package_protection_rule, + current_user: current_user, params: kwargs).execute + + { package_protection_rule: response.payload[:package_protection_rule], errors: response.errors } + end + end + end + end + end +end diff --git a/app/graphql/mutations/projects/star.rb b/app/graphql/mutations/projects/star.rb new file mode 100644 index 00000000000..e4b64235c9a --- /dev/null +++ b/app/graphql/mutations/projects/star.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Mutations + module Projects + class Star < BaseMutation + graphql_name 'StarProject' + + authorize :read_project + + argument :project_id, + ::Types::GlobalIDType[::Project], + required: true, + description: 'Full path of the project to star or unstar.' + + argument :starred, + GraphQL::Types::Boolean, + required: true, + description: 'Indicates whether to star or unstar the project.' + + field :count, + GraphQL::Types::String, + null: false, + description: 'Number of stars for the project.' + + def resolve(project_id:, starred:) + project = authorized_find!(id: project_id) + + if current_user.starred?(project) != starred + current_user.toggle_star(project) + project.reset + end + + { + count: project.star_count + } + end + end + end +end diff --git a/app/graphql/mutations/saved_replies/base.rb b/app/graphql/mutations/saved_replies/base.rb index 79761645eb7..d89cb2d06f0 100644 --- a/app/graphql/mutations/saved_replies/base.rb +++ b/app/graphql/mutations/saved_replies/base.rb @@ -22,10 +22,6 @@ module Mutations } end end - - def find_object(id) - GitlabSchema.find_by_gid(id) - end end end end diff --git a/app/graphql/mutations/saved_replies/destroy.rb b/app/graphql/mutations/saved_replies/destroy.rb index 655ed9cb798..14223c06d0c 100644 --- a/app/graphql/mutations/saved_replies/destroy.rb +++ b/app/graphql/mutations/saved_replies/destroy.rb @@ -12,7 +12,7 @@ module Mutations description: copy_field_description(Types::SavedReplyType, :id) def resolve(id:) - saved_reply = authorized_find!(id) + saved_reply = authorized_find!(id: id) result = ::Users::SavedReplies::DestroyService.new(saved_reply: saved_reply).execute present_result(result) end diff --git a/app/graphql/mutations/saved_replies/update.rb b/app/graphql/mutations/saved_replies/update.rb index f5dc81614d2..72da66736e9 100644 --- a/app/graphql/mutations/saved_replies/update.rb +++ b/app/graphql/mutations/saved_replies/update.rb @@ -20,7 +20,7 @@ module Mutations description: copy_field_description(Types::SavedReplyType, :content) def resolve(id:, name:, content:) - saved_reply = authorized_find!(id) + saved_reply = authorized_find!(id: id) result = ::Users::SavedReplies::UpdateService.new(saved_reply: saved_reply, name: name, content: content).execute present_result(result) end diff --git a/app/graphql/mutations/snippets/base.rb b/app/graphql/mutations/snippets/base.rb index acaa7b80843..01441926396 100644 --- a/app/graphql/mutations/snippets/base.rb +++ b/app/graphql/mutations/snippets/base.rb @@ -10,10 +10,6 @@ module Mutations private - def find_object(id:) - GitlabSchema.object_from_id(id, expected_type: ::Snippet) - end - def authorized_resource?(snippet) return false if snippet.nil? diff --git a/app/graphql/mutations/user_preferences/update.rb b/app/graphql/mutations/user_preferences/update.rb index 16c7b37532c..111bd258775 100644 --- a/app/graphql/mutations/user_preferences/update.rb +++ b/app/graphql/mutations/user_preferences/update.rb @@ -5,9 +5,17 @@ module Mutations class Update < BaseMutation graphql_name 'UserPreferencesUpdate' + NON_NULLABLE_ARGS = [ + :use_web_ide_extension_marketplace, + :visibility_pipeline_id_type + ].freeze + argument :issues_sort, Types::IssueSortEnum, required: false, description: 'Sort order for issue lists.' + argument :use_web_ide_extension_marketplace, GraphQL::Types::Boolean, + required: false, + description: 'Whether Web IDE Extension Marketplace is enabled for the user.' argument :visibility_pipeline_id_type, Types::VisibilityPipelineIdTypeEnum, required: false, description: 'Determines whether the pipeline list shows ID or IID.' @@ -18,6 +26,7 @@ module Mutations description: 'User preferences after mutation.' def resolve(**attributes) + attributes.delete_if { |key, value| NON_NULLABLE_ARGS.include?(key) && value.nil? } user_preferences = current_user.user_preference user_preferences.update(attributes) diff --git a/app/graphql/mutations/work_items/convert.rb b/app/graphql/mutations/work_items/convert.rb index b1936027fdc..c4d4927fde2 100644 --- a/app/graphql/mutations/work_items/convert.rb +++ b/app/graphql/mutations/work_items/convert.rb @@ -59,10 +59,6 @@ module Mutations message = format(_('You are not allowed to change the Work Item type to %{name}.'), name: work_item_type.name) raise_resource_not_available_error! message end - - def find_object(id:) - GitlabSchema.find_by_gid(id) - end end end end diff --git a/app/graphql/mutations/work_items/delete_task.rb b/app/graphql/mutations/work_items/delete_task.rb deleted file mode 100644 index b13d7e2e3bf..00000000000 --- a/app/graphql/mutations/work_items/delete_task.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -module Mutations - module WorkItems - class DeleteTask < BaseMutation - graphql_name 'WorkItemDeleteTask' - - description "Deletes a task in a work item's description." - - authorize :update_work_item - - argument :id, ::Types::GlobalIDType[::WorkItem], - required: true, - description: 'Global ID of the work item.' - argument :lock_version, GraphQL::Types::Int, - required: true, - description: 'Current lock version of the work item containing the task in the description.' - argument :task_data, ::Types::WorkItems::DeletedTaskInputType, - required: true, - description: 'Arguments necessary to delete a task from a work item\'s description.', - prepare: ->(attributes, _ctx) { attributes.to_h } - - field :work_item, Types::WorkItemType, - null: true, - description: 'Updated work item.' - - def resolve(id:, lock_version:, task_data:) - work_item = authorized_find!(id: id) - task_data[:task] = authorized_find_task!(task_data[:id]) - - result = ::WorkItems::DeleteTaskService.new( - work_item: work_item, - current_user: current_user, - lock_version: lock_version, - task_params: task_data - ).execute - - response = { errors: result.errors } - response[:work_item] = work_item if result.success? - - response - end - - private - - def authorized_find_task!(task_id) - task = ::Gitlab::Graphql::Lazy.force(GitlabSchema.find_by_gid(task_id)) - - if current_user.can?(:delete_work_item, task) - task - else - # Fail early if user cannot delete task - raise_resource_not_available_error! - end - end - end - end -end diff --git a/app/graphql/queries/snippet/snippet.query.graphql b/app/graphql/queries/snippet/snippet.query.graphql index 8712a6f4b01..8a2b314b994 100644 --- a/app/graphql/queries/snippet/snippet.query.graphql +++ b/app/graphql/queries/snippet/snippet.query.graphql @@ -13,6 +13,7 @@ query GetSnippetQuery($ids: [SnippetID!]) { webUrl httpUrlToRepo sshUrlToRepo + hidden blobs { __typename hasUnretrievableBlobs diff --git a/app/graphql/resolvers/analytics/cycle_analytics/base_count_resolver.rb b/app/graphql/resolvers/analytics/cycle_analytics/base_count_resolver.rb index 82d38ff89d9..565638903e8 100644 --- a/app/graphql/resolvers/analytics/cycle_analytics/base_count_resolver.rb +++ b/app/graphql/resolvers/analytics/cycle_analytics/base_count_resolver.rb @@ -32,6 +32,25 @@ module Resolvers super end + + # :project level: no customization, returning the original resolver + # :group level: add the project_ids argument + def self.[](context = :project) + case context + when :project + self + when :group + Class.new(self) do + argument :project_ids, [GraphQL::Types::ID], + required: false, + description: 'Project IDs within the group hierarchy.' + + define_method :finder_params do + { group_id: object.id, include_subgroups: true } + end + end + end + end end end end diff --git a/app/graphql/resolvers/analytics/cycle_analytics/base_issue_resolver.rb b/app/graphql/resolvers/analytics/cycle_analytics/base_issue_resolver.rb index 768265752d5..587077d7fd4 100644 --- a/app/graphql/resolvers/analytics/cycle_analytics/base_issue_resolver.rb +++ b/app/graphql/resolvers/analytics/cycle_analytics/base_issue_resolver.rb @@ -25,25 +25,6 @@ module Resolvers def finder_params { project_id: object.project.id } end - - # :project level: no customization, returning the original resolver - # :group level: add the project_ids argument - def self.[](context = :project) - case context - when :project - self - when :group - Class.new(self) do - argument :project_ids, [GraphQL::Types::ID], - required: false, - description: 'Project IDs within the group hierarchy.' - - define_method :finder_params do - { group_id: object.id, include_subgroups: true } - end - end - end - end end end end diff --git a/app/graphql/resolvers/analytics/cycle_analytics/base_merge_request_resolver.rb b/app/graphql/resolvers/analytics/cycle_analytics/base_merge_request_resolver.rb new file mode 100644 index 00000000000..81b6f1f4e23 --- /dev/null +++ b/app/graphql/resolvers/analytics/cycle_analytics/base_merge_request_resolver.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Resolvers + module Analytics + module CycleAnalytics + class BaseMergeRequestResolver < BaseCountResolver + type Types::Analytics::CycleAnalytics::MetricType, null: true + + argument :assignee_usernames, [GraphQL::Types::String], + required: false, + description: 'Usernames of users assigned to the merge request.' + + argument :author_username, GraphQL::Types::String, + required: false, + description: 'Username of the author of the merge request.' + + argument :milestone_title, GraphQL::Types::String, + required: false, + description: 'Milestone applied to the merge request.' + + argument :label_names, [GraphQL::Types::String], + required: false, + description: 'Labels applied to the merge request.' + end + end + end +end diff --git a/app/graphql/resolvers/analytics/cycle_analytics/deployment_count_resolver.rb b/app/graphql/resolvers/analytics/cycle_analytics/deployment_count_resolver.rb index 2d722b02bf1..95080110699 100644 --- a/app/graphql/resolvers/analytics/cycle_analytics/deployment_count_resolver.rb +++ b/app/graphql/resolvers/analytics/cycle_analytics/deployment_count_resolver.rb @@ -28,22 +28,6 @@ module Resolvers finder.execute.count end - - # :project level: no customization, returning the original resolver - # :group level: add the project_ids argument - def self.[](context = :project) - case context - when :project - self - when :group - Class.new(self) do - argument :project_ids, [GraphQL::Types::ID], - required: false, - description: 'Project IDs within the group hierarchy.' - end - - end - end end end end diff --git a/app/graphql/resolvers/analytics/cycle_analytics/stages_resolver.rb b/app/graphql/resolvers/analytics/cycle_analytics/stages_resolver.rb new file mode 100644 index 00000000000..7f82c3dbada --- /dev/null +++ b/app/graphql/resolvers/analytics/cycle_analytics/stages_resolver.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Resolvers + module Analytics + module CycleAnalytics + class StagesResolver < BaseResolver + type [Types::Analytics::CycleAnalytics::ValueStreams::StageType], null: true + + def resolve + list_stages({ value_stream: object }) + end + + private + + def list_stages(list_service_params) + ::Analytics::CycleAnalytics::Stages::ListService.new( + parent: namespace, + current_user: current_user, + params: list_service_params + ).execute[:stages] + end + + def namespace + object.project.project_namespace + end + end + end + end +end + +Resolvers::Analytics::CycleAnalytics::StagesResolver.prepend_mod diff --git a/app/graphql/resolvers/analytics/cycle_analytics/value_streams_resolver.rb b/app/graphql/resolvers/analytics/cycle_analytics/value_streams_resolver.rb new file mode 100644 index 00000000000..f3e3da86169 --- /dev/null +++ b/app/graphql/resolvers/analytics/cycle_analytics/value_streams_resolver.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Resolvers + module Analytics + module CycleAnalytics + class ValueStreamsResolver < BaseResolver + type Types::Analytics::CycleAnalytics::ValueStreamType.connection_type, null: true + + def resolve + # FOSS only have default value stream available + [ + ::Analytics::CycleAnalytics::ValueStream.build_default_value_stream(object.project_namespace) + ] + end + end + end + end +end + +Resolvers::Analytics::CycleAnalytics::ValueStreamsResolver.prepend_mod diff --git a/app/graphql/resolvers/blame_resolver.rb b/app/graphql/resolvers/blame_resolver.rb index f8b985e6582..d411d025255 100644 --- a/app/graphql/resolvers/blame_resolver.rb +++ b/app/graphql/resolvers/blame_resolver.rb @@ -14,7 +14,7 @@ module Resolvers argument :to_line, GraphQL::Types::Int, required: false, default_value: 1, - description: 'Range ending on the line. Cannot be less than 1 or less than `to_line`.' + description: 'Range ending on the line. Cannot be smaller than `from_line` or greater than `from_line` + 100.' alias_method :blob, :object @@ -48,15 +48,18 @@ module Resolvers end def validate_line_params!(args) - if args[:from_line] <= 0 || args[:to_line] <= 0 - raise Gitlab::Graphql::Errors::ArgumentError, - '`from_line` and `to_line` must be greater than or equal to 1' - end + raise_greater_than_one unless args[:from_line] >= 1 + raise_greater_than_one unless args[:to_line] >= 1 - return unless args[:from_line] > args[:to_line] + return unless args[:to_line] < args[:from_line] || args[:to_line] >= args[:from_line] + 100 raise Gitlab::Graphql::Errors::ArgumentError, - '`to_line` must be greater than or equal to `from_line`' + '`to_line` must be greater than or equal to `from_line` and smaller than `from_line` + 100' + end + + def raise_greater_than_one + raise Gitlab::Graphql::Errors::ArgumentError, + '`from_line` and `to_line` must be greater than or equal to 1' end end end diff --git a/app/graphql/resolvers/ci/catalog/resource_resolver.rb b/app/graphql/resolvers/ci/catalog/resource_resolver.rb index 4b722bd3ec7..3ea730a5768 100644 --- a/app/graphql/resolvers/ci/catalog/resource_resolver.rb +++ b/app/graphql/resolvers/ci/catalog/resource_resolver.rb @@ -6,8 +6,6 @@ module Resolvers class ResourceResolver < BaseResolver include Gitlab::Graphql::Authorize::AuthorizeResource - authorize :read_code - type ::Types::Ci::Catalog::ResourceType, null: true argument :id, ::Types::GlobalIDType[::Ci::Catalog::Resource], @@ -28,19 +26,15 @@ module Resolvers end def resolve(id: nil, full_path: nil) - if full_path.present? - project = Project.find_by_full_path(full_path) - authorize!(project) - - raise_resource_not_available_error! unless project.catalog_resource + catalog_resource = if full_path.present? + ::Ci::Catalog::Listing.new(current_user).find_resource(full_path: full_path) + else + ::Ci::Catalog::Listing.new(current_user).find_resource(id: id.model_id) + end - project.catalog_resource - else - catalog_resource = ::Gitlab::Graphql::Lazy.force(GitlabSchema.find_by_gid(id)) - authorize!(catalog_resource&.project) + raise_resource_not_available_error! unless catalog_resource - catalog_resource - end + catalog_resource end end end diff --git a/app/graphql/resolvers/ci/catalog/resources/versions_resolver.rb b/app/graphql/resolvers/ci/catalog/resources/versions_resolver.rb new file mode 100644 index 00000000000..9332076a493 --- /dev/null +++ b/app/graphql/resolvers/ci/catalog/resources/versions_resolver.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Resolvers + module Ci + module Catalog + module Resources + class VersionsResolver < BaseResolver + type Types::Ci::Catalog::Resources::VersionType.connection_type, null: true + + # This allows a maximum of 1 call to the field that uses this resolver. If the + # field is evaluated on more than one node, it causes performance degradation. + extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1 + + argument :sort, Types::Ci::Catalog::Resources::VersionSortEnum, + required: false, + description: 'Sort versions by given criteria.' + + def resolve(sort: nil) + ::Ci::Catalog::Resources::VersionsFinder.new(object, current_user, sort: sort).execute + end + end + end + end + end +end diff --git a/app/graphql/resolvers/ci/catalog/resources_resolver.rb b/app/graphql/resolvers/ci/catalog/resources_resolver.rb index c6904dcd7f6..ec415cf25c1 100644 --- a/app/graphql/resolvers/ci/catalog/resources_resolver.rb +++ b/app/graphql/resolvers/ci/catalog/resources_resolver.rb @@ -27,17 +27,13 @@ module Resolvers description: 'Project with the namespace catalog.' def resolve_with_lookahead(scope:, project_path: nil, search: nil, sort: nil) - if project_path.present? - project = Project.find_by_full_path(project_path) - - apply_lookahead( - ::Ci::Catalog::Listing - .new(context[:current_user]) - .resources(namespace: project.root_namespace, sort: sort, search: search) - ) - elsif scope == :all - apply_lookahead(::Ci::Catalog::Listing.new(context[:current_user]).resources(sort: sort, search: search)) - end + project = Project.find_by_full_path(project_path) + + apply_lookahead( + ::Ci::Catalog::Listing + .new(context[:current_user]) + .resources(namespace: project&.root_namespace, sort: sort, search: search, scope: scope) + ) end private diff --git a/app/graphql/resolvers/ci/catalog/versions_resolver.rb b/app/graphql/resolvers/ci/catalog/versions_resolver.rb deleted file mode 100644 index 046adeb7a67..00000000000 --- a/app/graphql/resolvers/ci/catalog/versions_resolver.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module Resolvers - module Ci - module Catalog - class VersionsResolver < ::Resolvers::ReleasesResolver - type Types::ReleaseType.connection_type, null: true - - # This allows a maximum of 1 call to the field that uses this resolver. If the - # field is evaluated on more than one node, it causes performance degradation. - extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1 - - private - - def get_project - object.respond_to?(:project) ? object.project : object - end - - # Override the aliased method in ReleasesResolver - alias_method :project, :get_project - end - end - end -end diff --git a/app/graphql/resolvers/ci/runner_groups_resolver.rb b/app/graphql/resolvers/ci/runner_groups_resolver.rb index c1d9bcbb9bb..2928631c705 100644 --- a/app/graphql/resolvers/ci/runner_groups_resolver.rb +++ b/app/graphql/resolvers/ci/runner_groups_resolver.rb @@ -6,7 +6,7 @@ module Resolvers include Gitlab::Graphql::Authorize::AuthorizeResource include ResolvesGroups - type 'Types::GroupConnection', null: true + type Types::GroupType.connection_type, null: true authorize :read_runner authorizes_object! diff --git a/app/graphql/resolvers/container_repository_tags_resolver.rb b/app/graphql/resolvers/container_repository_tags_resolver.rb index bc5006ae06c..50adf98fa07 100644 --- a/app/graphql/resolvers/container_repository_tags_resolver.rb +++ b/app/graphql/resolvers/container_repository_tags_resolver.rb @@ -17,7 +17,7 @@ module Resolvers alias_method :container_repository, :object def resolve(sort:, **filters) - if container_repository.migrated? && Feature.enabled?(:use_repository_list_tags_on_graphql, container_repository.project) + if container_repository.migrated? page_size = [filters[:first], filters[:last]].map(&:to_i).max result = container_repository.tags_page( diff --git a/app/graphql/resolvers/custom_emoji_resolver.rb b/app/graphql/resolvers/custom_emoji_resolver.rb new file mode 100644 index 00000000000..1e39fafe486 --- /dev/null +++ b/app/graphql/resolvers/custom_emoji_resolver.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Resolvers + class CustomEmojiResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + authorizes_object! + + authorize :read_custom_emoji + + argument :include_ancestor_groups, + GraphQL::Types::Boolean, + required: false, + default_value: false, + description: 'Includes custom emoji from parent groups.' + + type Types::CustomEmojiType, null: true + + def resolve(**args) + Groups::CustomEmojiFinder.new(object, args).execute + end + end +end diff --git a/app/graphql/resolvers/group_issues_resolver.rb b/app/graphql/resolvers/group_issues_resolver.rb index 5a6a3d678b9..360781806a4 100644 --- a/app/graphql/resolvers/group_issues_resolver.rb +++ b/app/graphql/resolvers/group_issues_resolver.rb @@ -10,7 +10,7 @@ module Resolvers include GroupIssuableResolver before_connection_authorization do |nodes, _| - projects = nodes.map(&:project) + projects = nodes.filter_map(&:project) ActiveRecord::Associations::Preloader.new(records: projects, associations: project_associations).call end diff --git a/app/graphql/resolvers/group_milestones_resolver.rb b/app/graphql/resolvers/group_milestones_resolver.rb index 9242be7f684..d9a664b6ec2 100644 --- a/app/graphql/resolvers/group_milestones_resolver.rb +++ b/app/graphql/resolvers/group_milestones_resolver.rb @@ -2,6 +2,8 @@ module Resolvers class GroupMilestonesResolver < MilestonesResolver + include ::API::Concerns::Milestones::GroupProjectParams + argument :include_ancestors, GraphQL::Types::Boolean, required: false, description: 'Include milestones from all parent groups.' @@ -14,36 +16,7 @@ module Resolvers private def parent_id_parameters(args) - include_ancestors = args[:include_ancestors].present? - include_descendants = args[:include_descendants].present? - return { group_ids: parent.id } unless include_ancestors || include_descendants - - group_ids = if include_ancestors && include_descendants - parent.self_and_hierarchy - elsif include_ancestors - parent.self_and_ancestors - else - parent.self_and_descendants - end - - project_ids = if include_descendants - group_projects.with_issues_or_mrs_available_for_user(current_user) - else - nil - end - - { - group_ids: group_ids.public_or_visible_to_user(current_user).select(:id), - project_ids: project_ids - } - end - - def group_projects - GroupProjectsFinder.new( - group: parent, - current_user: current_user, - options: { include_subgroups: true } - ).execute + group_finder_params(parent, args) end def preloads diff --git a/app/graphql/resolvers/groups_resolver.rb b/app/graphql/resolvers/groups_resolver.rb index 902b5279364..f31f7368eeb 100644 --- a/app/graphql/resolvers/groups_resolver.rb +++ b/app/graphql/resolvers/groups_resolver.rb @@ -4,7 +4,7 @@ module Resolvers class GroupsResolver < BaseResolver include ResolvesGroups - type "Types::GroupConnection", null: true + type Types::GroupType.connection_type, null: true argument :search, GraphQL::Types::String, required: false, diff --git a/app/graphql/resolvers/issues/base_parent_resolver.rb b/app/graphql/resolvers/issues/base_parent_resolver.rb index 78ef4132baf..6b3426c5538 100644 --- a/app/graphql/resolvers/issues/base_parent_resolver.rb +++ b/app/graphql/resolvers/issues/base_parent_resolver.rb @@ -15,8 +15,7 @@ module Resolvers raise Gitlab::Graphql::Errors::ArgumentError, Types::IssuableStateEnum::INVALID_LOCKED_MESSAGE } - # see app/graphql/types/issue_connection.rb - type 'Types::IssueConnection', null: true + type Types::IssueType.connection_type, null: true def resolve_with_lookahead(**args) return Issue.none if resource_parent.nil? diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb index bc0e7334303..1b43903a508 100644 --- a/app/graphql/resolvers/issues_resolver.rb +++ b/app/graphql/resolvers/issues_resolver.rb @@ -21,8 +21,7 @@ module Resolvers raise Gitlab::Graphql::Errors::ArgumentError, Types::IssuableStateEnum::INVALID_LOCKED_MESSAGE } - # see app/graphql/types/issue_connection.rb - type 'Types::IssueConnection', null: true + type Types::IssueType.connection_type, null: true before_connection_authorization do |nodes, current_user| projects = nodes.map(&:project) diff --git a/app/graphql/resolvers/kas/agent_connections_resolver.rb b/app/graphql/resolvers/kas/agent_connections_resolver.rb index cf1a47aac75..282efb4161e 100644 --- a/app/graphql/resolvers/kas/agent_connections_resolver.rb +++ b/app/graphql/resolvers/kas/agent_connections_resolver.rb @@ -12,8 +12,8 @@ module Resolvers 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) + BatchLoader::GraphQL.for(agent.id).batch(default_value: []) do |agent_ids, loader| + agents = get_connected_agents(agent_ids).group_by(&:agent_id) agents.each do |agent_id, connections| loader.call(agent_id, connections) @@ -27,8 +27,8 @@ module Resolvers current_user.can?(:admin_cluster, project) end - def get_connected_agents - kas_client.get_connected_agents(project: project) + def get_connected_agents(agent_ids) + kas_client.get_connected_agents_by_agent_ids(agent_ids: agent_ids) rescue GRPC::BadStatus, Gitlab::Kas::Client::ConfigurationError => e raise Gitlab::Graphql::Errors::ResourceNotAvailable, e.class.name end diff --git a/app/graphql/resolvers/ml/model_detail_resolver.rb b/app/graphql/resolvers/ml/model_detail_resolver.rb new file mode 100644 index 00000000000..01c025c1d8a --- /dev/null +++ b/app/graphql/resolvers/ml/model_detail_resolver.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Resolvers + module Ml + class ModelDetailResolver < Resolvers::BaseResolver + extension ::Gitlab::Graphql::Limit::FieldCallCount, limit: 1 + + type ::Types::Ml::ModelType, null: true + + argument :id, ::Types::GlobalIDType[::Ml::Model], + required: true, + description: 'ID of the model.' + + def resolve(id:) + Gitlab::Graphql::Lazy.with_value(find_object(id: id)) do |ml_model| + ml_model if current_user.can?(:read_model_registry, ml_model&.project) + end + end + + private + + def find_object(id:) + GitlabSchema.find_by_gid(id) + end + end + end +end diff --git a/app/graphql/resolvers/namespaces/work_item_state_counts_resolver.rb b/app/graphql/resolvers/namespaces/work_item_state_counts_resolver.rb new file mode 100644 index 00000000000..099b509f77f --- /dev/null +++ b/app/graphql/resolvers/namespaces/work_item_state_counts_resolver.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Resolvers + module Namespaces + class WorkItemStateCountsResolver < WorkItemsResolver + type Types::WorkItemStateCountsType, null: true + + def ready?(**args) + # The search filter is not supported for work times at the namespace level. + # See https://gitlab.com/gitlab-org/gitlab/-/work_items/393126 + if args[:search] + raise Gitlab::Graphql::Errors::ArgumentError, + 'Searching is not available for work items at the namespace level yet' + end + + super + end + + def resolve(**args) + return if resource_parent.nil? + + Gitlab::IssuablesCountForState.new( + finder(args), + resource_parent, + store_in_redis_cache: true + ) + end + end + end +end diff --git a/app/graphql/resolvers/organizations/groups_resolver.rb b/app/graphql/resolvers/organizations/groups_resolver.rb index 0f50713b9b4..552a14fc13f 100644 --- a/app/graphql/resolvers/organizations/groups_resolver.rb +++ b/app/graphql/resolvers/organizations/groups_resolver.rb @@ -25,12 +25,18 @@ module Resolvers private + alias_method :organization, :object + def resolve_groups(**args) - return Group.none if Feature.disabled?(:resolve_organization_groups, context[:current_user]) + return Group.none if Feature.disabled?(:resolve_organization_groups, current_user) + + extra_args = { organization: organization, include_ancestors: false, all_available: false } + groups = GroupsFinder.new(current_user, args.merge(extra_args)).execute - ::Organizations::GroupsFinder - .new(organization: object, current_user: context[:current_user], params: args) - .execute + args[:sort] ||= { field: 'name', direction: :asc } + field = args[:sort][:field] + direction = args[:sort][:direction] + groups.sort_by_attribute("#{field}_#{direction}") end end end diff --git a/app/graphql/resolvers/project_milestones_resolver.rb b/app/graphql/resolvers/project_milestones_resolver.rb index cb4e9a5cdf7..0a078836a6a 100644 --- a/app/graphql/resolvers/project_milestones_resolver.rb +++ b/app/graphql/resolvers/project_milestones_resolver.rb @@ -2,6 +2,8 @@ module Resolvers class ProjectMilestonesResolver < MilestonesResolver + include ::API::Concerns::Milestones::GroupProjectParams + argument :include_ancestors, GraphQL::Types::Boolean, required: false, description: "Also return milestones in the project's parent group and its ancestors." @@ -11,12 +13,7 @@ module Resolvers private def parent_id_parameters(args) - return { project_ids: parent.id } unless args[:include_ancestors].present? && parent.group.present? - - { - group_ids: parent.group.self_and_ancestors.select(:id), - project_ids: parent.id - } + project_finder_params(parent, args) end end end diff --git a/app/graphql/resolvers/timelog_resolver.rb b/app/graphql/resolvers/timelog_resolver.rb index 4f52db6801d..c2be582742b 100644 --- a/app/graphql/resolvers/timelog_resolver.rb +++ b/app/graphql/resolvers/timelog_resolver.rb @@ -3,6 +3,7 @@ module Resolvers class TimelogResolver < BaseResolver include LooksAhead + include Gitlab::Graphql::Authorize::AuthorizeResource type ::Types::TimelogType.connection_type, null: false @@ -42,21 +43,30 @@ module Resolvers def resolve_with_lookahead(**args) validate_args!(object, args) - timelogs = object&.timelogs || Timelog.all - args = parse_datetime_args(args) - timelogs = apply_user_filter(timelogs, args) - timelogs = apply_project_filter(timelogs, args) - timelogs = apply_time_filter(timelogs, args) - timelogs = apply_group_filter(timelogs, args) - timelogs = apply_sorting(timelogs, args) + timelogs = Timelogs::TimelogsFinder.new(object, finder_params(args)).execute apply_lookahead(timelogs) + rescue ArgumentError => e + raise_argument_error(e.message) + rescue ActiveRecord::RecordNotFound + raise_resource_not_available_error! end private + def finder_params(args) + { + username: args[:username], + start_time: args[:start_time], + end_time: args[:end_time], + group_id: args[:group_id]&.model_id, + project_id: args[:project_id]&.model_id, + sort: args[:sort] + } + end + def preloads { note: [:note] @@ -95,58 +105,6 @@ module Resolvers args[:start_time] && args[:end_time] end - def validate_time_difference!(args) - return unless end_time_before_start_time?(args) - - raise_argument_error('Start argument must be before End argument') - end - - def end_time_before_start_time?(args) - times_provided?(args) && args[:end_time] < args[:start_time] - end - - def apply_project_filter(timelogs, args) - return timelogs unless args[:project_id] - - timelogs.in_project(args[:project_id].model_id) - end - - def apply_group_filter(timelogs, args) - return timelogs unless args[:group_id] - - group = Group.find_by_id(args[:group_id].model_id) - timelogs.in_group(group) - end - - def apply_user_filter(timelogs, args) - return timelogs unless args[:username] - - user = UserFinder.new(args[:username]).find_by_username - timelogs.for_user(user) - end - - def apply_time_filter(timelogs, args) - return timelogs unless args[:start_time] || args[:end_time] - - validate_time_difference!(args) - - if args[:start_time] - timelogs = timelogs.at_or_after(args[:start_time]) - end - - if args[:end_time] - timelogs = timelogs.at_or_before(args[:end_time]) - end - - timelogs - end - - def apply_sorting(timelogs, args) - return timelogs unless args[:sort] - - timelogs.sort_by_field(args[:sort]) - end - def raise_argument_error(message) raise Gitlab::Graphql::Errors::ArgumentError, message end diff --git a/app/graphql/resolvers/users/frecent_groups_resolver.rb b/app/graphql/resolvers/users/frecent_groups_resolver.rb index 2fc757e31ab..f6b43297898 100644 --- a/app/graphql/resolvers/users/frecent_groups_resolver.rb +++ b/app/graphql/resolvers/users/frecent_groups_resolver.rb @@ -10,12 +10,6 @@ module Resolvers def resolve return unless current_user.present? - if Feature.disabled?(:frecent_namespaces_suggestions, current_user) - raise_resource_not_available_error!("'frecent_namespaces_suggestions' feature flag is disabled") - end - - return unless Feature.enabled?(:frecent_namespaces_suggestions, current_user) - ::Users::GroupVisit.frecent_groups(user_id: current_user.id) end end diff --git a/app/graphql/resolvers/users/frecent_projects_resolver.rb b/app/graphql/resolvers/users/frecent_projects_resolver.rb index 397d4ca0cfd..9508195800a 100644 --- a/app/graphql/resolvers/users/frecent_projects_resolver.rb +++ b/app/graphql/resolvers/users/frecent_projects_resolver.rb @@ -10,10 +10,6 @@ module Resolvers def resolve return unless current_user.present? - if Feature.disabled?(:frecent_namespaces_suggestions, current_user) - raise_resource_not_available_error!("'frecent_namespaces_suggestions' feature flag is disabled") - end - ::Users::ProjectVisit.frecent_projects(user_id: current_user.id) end end diff --git a/app/graphql/resolvers/work_item_references_resolver.rb b/app/graphql/resolvers/work_item_references_resolver.rb new file mode 100644 index 00000000000..4aa071519db --- /dev/null +++ b/app/graphql/resolvers/work_item_references_resolver.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module Resolvers + class WorkItemReferencesResolver < BaseResolver + prepend ::WorkItems::LookAheadPreloads + include Gitlab::Graphql::Authorize::AuthorizeResource + + REFERENCES_LIMIT = 10 + + authorize :read_work_item + + type ::Types::WorkItemType.connection_type, null: true + + argument :context_namespace_path, GraphQL::Types::ID, + required: false, + description: 'Full path of the context namespace (project or group).' + + argument :refs, [GraphQL::Types::String], required: true, + description: 'Work item references. Can be either a short reference or URL.' + + def ready?(**args) + if args[:refs].size > REFERENCES_LIMIT + raise Gitlab::Graphql::Errors::ArgumentError, + format( + _('Number of references exceeds the limit. ' \ + 'Please provide no more than %{refs_limit} references at the same time.'), + refs_limit: REFERENCES_LIMIT + ) + end + + super + end + + def resolve_with_lookahead(context_namespace_path: nil, refs: []) + return WorkItem.none if refs.empty? + + @container = authorized_find!(context_namespace_path) + # Only ::Project is supported at the moment, future iterations will include ::Group. + # See https://gitlab.com/gitlab-org/gitlab/-/issues/432555 + return WorkItem.none if container.is_a?(::Group) + + apply_lookahead(find_work_items(refs)) + end + + private + + attr_reader :container + + # rubocop: disable CodeReuse/ActiveRecord -- #references is not an ActiveRecord method + def find_work_items(references) + links, short_references = references.partition { |r| r.include?('/work_items/') } + + item_ids = references_extractor(short_references).references(:issue, ids_only: true) + item_ids << references_extractor(links).references(:work_item, ids_only: true) if links.any? + + WorkItem.id_in(item_ids.flatten) + end + # rubocop: enable CodeReuse/ActiveRecord + + def references_extractor(refs) + extractor = ::Gitlab::ReferenceExtractor.new(container, context[:current_user]) + extractor.analyze(refs.join(' '), {}) + + extractor + end + + def find_object(full_path) + Routable.find_by_full_path(full_path) + end + end +end diff --git a/app/graphql/resolvers/work_item_state_counts_resolver.rb b/app/graphql/resolvers/work_item_state_counts_resolver.rb new file mode 100644 index 00000000000..93551a57694 --- /dev/null +++ b/app/graphql/resolvers/work_item_state_counts_resolver.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Resolvers + class WorkItemStateCountsResolver < WorkItemsResolver + type Types::WorkItemStateCountsType, null: true + + def resolve(**args) + return if resource_parent.nil? + + work_item_finder = finder(prepare_finder_params(args)) + work_item_finder.parent_param = resource_parent + + Gitlab::IssuablesCountForState.new(work_item_finder, resource_parent) + end + end +end diff --git a/app/graphql/resolvers/work_items/ancestors_resolver.rb b/app/graphql/resolvers/work_items/ancestors_resolver.rb index 33adbfc9c86..efd01700a31 100644 --- a/app/graphql/resolvers/work_items/ancestors_resolver.rb +++ b/app/graphql/resolvers/work_items/ancestors_resolver.rb @@ -38,6 +38,8 @@ module Resolvers end def preload_resource_parents(work_items) + return unless current_user + projects = work_items.filter_map(&:project) namespaces = work_items.filter_map(&:namespace) group_namespaces = namespaces.select { |n| n.type == ::Group.sti_name } @@ -46,7 +48,11 @@ module Resolvers return unless projects.any? ::Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects, current_user).execute - ::Preloaders::GroupPolicyPreloader.new(projects.filter_map(&:namespace), current_user).execute + + if group_namespaces.any? + ::Preloaders::GroupPolicyPreloader.new(projects.filter_map(&:namespace), current_user).execute + end + ActiveRecord::Associations::Preloader.new(records: projects, associations: [:namespace]).call end diff --git a/app/graphql/resolvers/work_items/types_resolver.rb b/app/graphql/resolvers/work_items/types_resolver.rb index 2508125d392..11db096b5ba 100644 --- a/app/graphql/resolvers/work_items/types_resolver.rb +++ b/app/graphql/resolvers/work_items/types_resolver.rb @@ -3,6 +3,8 @@ module Resolvers module WorkItems class TypesResolver < BaseResolver + include LooksAhead + type Types::WorkItems::TypeType.connection_type, null: true argument :taskable, ::GraphQL::Types::Boolean, @@ -10,13 +12,29 @@ module Resolvers description: 'If `true`, only taskable work item types will be returned.' \ ' Argument is experimental and can be removed in the future without notice.' - def resolve(taskable: nil) + def resolve_with_lookahead(taskable: nil) + context.scoped_set!(:resource_parent, object) + # This will require a finder in the future when groups/projects get their work item types # All groups/projects use the default types for now base_scope = ::WorkItems::Type.default base_scope = base_scope.by_type(:task) if taskable - base_scope.order_by_name_asc + apply_lookahead(base_scope.order_by_name_asc) + end + + private + + def preloads + { + widget_definitions: :enabled_widget_definitions + } + end + + def nested_preloads + { + widget_definitions: { allowed_child_types: :allowed_child_types_by_name } + } end end end diff --git a/app/graphql/types/abuse_report_type.rb b/app/graphql/types/abuse_report_type.rb index 2532530cfa9..dc40800af94 100644 --- a/app/graphql/types/abuse_report_type.rb +++ b/app/graphql/types/abuse_report_type.rb @@ -10,8 +10,6 @@ module Types authorize :read_abuse_report - expose_permissions Types::PermissionTypes::AbuseReport - field :id, Types::GlobalIDType[::AbuseReport], null: false, description: 'Global ID of the abuse report.' diff --git a/app/graphql/types/analytics/cycle_analytics/value_stream_type.rb b/app/graphql/types/analytics/cycle_analytics/value_stream_type.rb index 16ce9b82718..900d2873789 100644 --- a/app/graphql/types/analytics/cycle_analytics/value_stream_type.rb +++ b/app/graphql/types/analytics/cycle_analytics/value_stream_type.rb @@ -26,6 +26,11 @@ module Types null: true, description: 'Project the value stream belongs to, returns empty if it belongs to a group.', alpha: { milestone: '15.6' } + + field :stages, + null: true, + resolver: Resolvers::Analytics::CycleAnalytics::StagesResolver, + description: 'Value Stream stages.' end end end diff --git a/app/graphql/types/analytics/cycle_analytics/value_streams/stage_event_enum.rb b/app/graphql/types/analytics/cycle_analytics/value_streams/stage_event_enum.rb new file mode 100644 index 00000000000..f7fd1121e4a --- /dev/null +++ b/app/graphql/types/analytics/cycle_analytics/value_streams/stage_event_enum.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Types + module Analytics + module CycleAnalytics + module ValueStreams + class StageEventEnum < BaseEnum + graphql_name 'ValueStreamStageEvent' + description 'Stage event identifiers' + + Gitlab::Analytics::CycleAnalytics::StageEvents.to_enum.each do |key, value| + value(key.to_s.upcase, description: "#{key.to_s.humanize} event.", value: value) + end + end + end + end + end +end diff --git a/app/graphql/types/analytics/cycle_analytics/value_streams/stage_type.rb b/app/graphql/types/analytics/cycle_analytics/value_streams/stage_type.rb new file mode 100644 index 00000000000..c8fdf8513be --- /dev/null +++ b/app/graphql/types/analytics/cycle_analytics/value_streams/stage_type.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module Types + module Analytics + module CycleAnalytics + module ValueStreams + # rubocop: disable Graphql/AuthorizeTypes -- # Already authorized in parent value stream type. + class StageType < BaseObject + graphql_name 'ValueStreamStage' + + field :name, + GraphQL::Types::String, + null: false, + description: 'Name of the stage.' + + field :hidden, + GraphQL::Types::Boolean, + null: false, + description: 'Whether the stage is hidden.' + + field :custom, + GraphQL::Types::Boolean, + null: false, + description: 'Whether the stage is customized.' + + field :start_event_identifier, + StageEventEnum, + null: false, + description: 'Start event identifier.' + + field :end_event_identifier, + StageEventEnum, + null: false, + description: 'End event identifier.' + + def start_event_identifier + events_enum[object.start_event_identifier] + end + + def end_event_identifier + events_enum[object.end_event_identifier] + end + + def events_enum + Gitlab::Analytics::CycleAnalytics::StageEvents.to_enum.with_indifferent_access + end + end + # rubocop: enable Graphql/AuthorizeTypes + end + end + end +end + +Types::Analytics::CycleAnalytics::ValueStreams::StageType.prepend_mod diff --git a/app/graphql/types/ci/catalog/resource_scope_enum.rb b/app/graphql/types/ci/catalog/resource_scope_enum.rb index b825c3a7925..728670ba913 100644 --- a/app/graphql/types/ci/catalog/resource_scope_enum.rb +++ b/app/graphql/types/ci/catalog/resource_scope_enum.rb @@ -8,6 +8,7 @@ module Types description 'Values for scoping catalog resources' value 'ALL', 'All catalog resources visible to the current user.', value: :all + value 'NAMESPACES', 'Catalog resources belonging to authorized namespaces of the user.', value: :namespaces end end end diff --git a/app/graphql/types/ci/catalog/resource_type.rb b/app/graphql/types/ci/catalog/resource_type.rb index 119313ae52b..44dec23b347 100644 --- a/app/graphql/types/ci/catalog/resource_type.rb +++ b/app/graphql/types/ci/catalog/resource_type.rb @@ -32,13 +32,14 @@ module Types field :web_path, GraphQL::Types::String, null: true, description: 'Web path of the catalog resource.', alpha: { milestone: '16.1' } - field :versions, Types::ReleaseType.connection_type, null: true, + field :versions, Types::Ci::Catalog::Resources::VersionType.connection_type, null: true, description: 'Versions of the catalog resource. This field can only be ' \ 'resolved for one catalog resource in any single request.', - resolver: Resolvers::Ci::Catalog::VersionsResolver, + resolver: Resolvers::Ci::Catalog::Resources::VersionsResolver, alpha: { milestone: '16.2' } - field :latest_version, Types::ReleaseType, null: true, description: 'Latest version of the catalog resource.', + field :latest_version, Types::Ci::Catalog::Resources::VersionType, null: true, + description: 'Latest version of the catalog resource.', alpha: { milestone: '16.1' } field :latest_released_at, Types::TimeType, null: true, @@ -49,14 +50,6 @@ module Types description: 'Number of times the catalog resource has been starred.', alpha: { milestone: '16.1' } - field :forks_count, GraphQL::Types::Int, null: false, calls_gitaly: true, - description: 'Number of times the catalog resource has been forked.', - alpha: { milestone: '16.1' } - - field :root_namespace, Types::NamespaceType, null: true, - description: 'Root namespace of the catalog resource.', - alpha: { milestone: '16.1' } - markdown_field :readme_html, null: false, alpha: { milestone: '16.1' } @@ -73,42 +66,16 @@ module Types end def latest_version - BatchLoader::GraphQL.for(object.project).batch do |projects, loader| - latest_releases = ReleasesFinder.new(projects, current_user, latest: true).execute + BatchLoader::GraphQL.for(object).batch do |catalog_resources, loader| + latest_versions = ::Ci::Catalog::Resources::VersionsFinder.new( + catalog_resources, current_user, latest: true).execute - latest_releases.index_by(&:project).each do |project, latest_release| - loader.call(project, latest_release) + latest_versions.index_by(&:catalog_resource).each do |catalog_resource, latest_version| + loader.call(catalog_resource, latest_version) end end end - def forks_count - BatchLoader::GraphQL.wrap(object.forks_count) - end - - def root_namespace - BatchLoader::GraphQL.for(object.project_id).batch do |project_ids, loader| - projects = Project.id_in(project_ids) - - # This preloader uses traversal_ids to obtain Group-type root namespaces. - # It also preloads each project's immediate parent namespace, which effectively - # preloads the User-type root namespaces since they cannot be nested (parent == root). - Preloaders::ProjectRootAncestorPreloader.new(projects, :group).execute - root_namespaces = projects.map(&:root_ancestor) - - # NamespaceType requires the `:read_namespace` ability. We must preload the policy for - # Group-type namespaces to avoid N+1 queries caused by the authorization requests. - group_root_namespaces = root_namespaces.select { |n| n.type == ::Group.sti_name } - Preloaders::GroupPolicyPreloader.new(group_root_namespaces, current_user).execute - - # For User-type namespaces, the authorization request requires preloading the owner objects. - user_root_namespaces = root_namespaces.select { |n| n.type == ::Namespaces::UserNamespace.sti_name } - ActiveRecord::Associations::Preloader.new(records: user_root_namespaces, associations: :owner).call - - projects.each { |project| loader.call(project.id, project.root_ancestor) } - end - end - def readme_html_resolver markdown_context = context.to_h.dup.merge(project: object.project) ::MarkupHelper.markdown(object.project.repository.readme&.data, markdown_context) diff --git a/app/graphql/types/ci/catalog/resources/component_type.rb b/app/graphql/types/ci/catalog/resources/component_type.rb new file mode 100644 index 00000000000..3b4771446cb --- /dev/null +++ b/app/graphql/types/ci/catalog/resources/component_type.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Types + module Ci + module Catalog + module Resources + # rubocop: disable Graphql/AuthorizeTypes -- Authorization is handled by VersionType + class ComponentType < BaseObject + graphql_name 'CiCatalogResourceComponent' + + field :id, ::Types::GlobalIDType[::Ci::Catalog::Resources::Component], null: false, + description: 'ID of the component.', + alpha: { milestone: '16.7' } + + field :name, GraphQL::Types::String, null: true, + description: 'Name of the component.', + alpha: { milestone: '16.7' } + + field :path, GraphQL::Types::String, null: true, + description: 'Path used to include the component.', + alpha: { milestone: '16.7' } + + field :inputs, [Types::Ci::Catalog::Resources::Components::InputType], null: true, + description: 'Inputs for the component.', + alpha: { milestone: '16.7' } + + def inputs + object.inputs.map do |key, value| + { + name: key, + required: !value&.key?('default'), + default: value&.dig('default') + } + end + end + end + end + # rubocop: enable Graphql/AuthorizeTypes + end + end +end diff --git a/app/graphql/types/ci/catalog/resources/components/input_type.rb b/app/graphql/types/ci/catalog/resources/components/input_type.rb new file mode 100644 index 00000000000..4b20c564ea7 --- /dev/null +++ b/app/graphql/types/ci/catalog/resources/components/input_type.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Types + module Ci + module Catalog + module Resources + module Components + # rubocop: disable Graphql/AuthorizeTypes -- Authorization hanlded by ComponentType -> VersionType + class InputType < BaseObject + graphql_name 'CiCatalogResourceComponentInput' + + field :name, GraphQL::Types::String, null: true, + description: 'Name of the input.', + alpha: { milestone: '16.7' } + + field :default, GraphQL::Types::String, null: true, + description: 'Default value for the input.', + alpha: { milestone: '16.7' } + + field :required, GraphQL::Types::Boolean, null: true, + description: 'Indicates if an input is required.', + alpha: { milestone: '16.7' } + end + end + end + # rubocop: enable Graphql/AuthorizeTypes + end + end +end diff --git a/app/graphql/types/ci/catalog/resources/version_sort_enum.rb b/app/graphql/types/ci/catalog/resources/version_sort_enum.rb new file mode 100644 index 00000000000..c5a5f46605a --- /dev/null +++ b/app/graphql/types/ci/catalog/resources/version_sort_enum.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Types + module Ci + module Catalog + module Resources + class VersionSortEnum < Types::ReleaseSortEnum + graphql_name 'CiCatalogResourceVersionSort' + description 'Values for sorting catalog resource versions' + end + end + end + end +end diff --git a/app/graphql/types/ci/catalog/resources/version_type.rb b/app/graphql/types/ci/catalog/resources/version_type.rb new file mode 100644 index 00000000000..689f649afc5 --- /dev/null +++ b/app/graphql/types/ci/catalog/resources/version_type.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module Types + module Ci + module Catalog + module Resources + # rubocop: disable Graphql/AuthorizeTypes -- Authorization is handled by Ci::Catalog::Resources::VersionsFinder in the resolver. + class VersionType < BaseObject + graphql_name 'CiCatalogResourceVersion' + + connection_type_class Types::CountableConnectionType + + field :id, ::Types::GlobalIDType[::Ci::Catalog::Resources::Version], null: false, + description: 'Global ID of the version.', + alpha: { milestone: '16.7' } + + field :created_at, Types::TimeType, null: true, description: 'Timestamp of when the version was created.', + alpha: { milestone: '16.7' } + + field :released_at, Types::TimeType, null: true, description: 'Timestamp of when the version was released.', + alpha: { milestone: '16.7' } + + field :tag_name, GraphQL::Types::String, null: true, method: :name, + description: 'Name of the tag associated with the version.', + alpha: { milestone: '16.7' } + + field :tag_path, GraphQL::Types::String, null: true, + description: 'Relative web path to the tag associated with the version.', + alpha: { milestone: '16.7' } + + field :author, Types::UserType, null: true, description: 'User that created the version.', + alpha: { milestone: '16.7' } + + field :commit, Types::CommitType, null: true, complexity: 10, calls_gitaly: true, + description: 'Commit associated with the version.', + alpha: { milestone: '16.7' } + + field :components, Types::Ci::Catalog::Resources::ComponentType.connection_type, null: true, + description: 'Components belonging to the catalog resource.', + alpha: { milestone: '16.7' } + + def author + Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find + end + + def tag_path + Gitlab::Routing.url_helpers.project_tag_path(object.project, object.name) + end + end + # rubocop: enable Graphql/AuthorizeTypes + end + end + end +end diff --git a/app/graphql/types/container_registry/protection/rule_type.rb b/app/graphql/types/container_registry/protection/rule_type.rb index 387f0202d2d..b80439b4de2 100644 --- a/app/graphql/types/container_registry/protection/rule_type.rb +++ b/app/graphql/types/container_registry/protection/rule_type.rb @@ -15,12 +15,12 @@ module Types null: false, description: 'ID of the container registry protection rule.' - field :container_path_pattern, + field :repository_path_pattern, GraphQL::Types::String, null: false, description: 'Container repository path pattern protected by the protection rule. ' \ - 'For example `@my-scope/my-container-*`. Wildcard character `*` allowed.' + 'For example `my-project/my-container-*`. Wildcard character `*` allowed.' field :push_protected_up_to_access_level, Types::ContainerRegistry::Protection::RuleAccessLevelEnum, diff --git a/app/graphql/types/container_repository_tag_type.rb b/app/graphql/types/container_repository_tag_type.rb index d9665175449..cf8796410d3 100644 --- a/app/graphql/types/container_repository_tag_type.rb +++ b/app/graphql/types/container_repository_tag_type.rb @@ -8,7 +8,15 @@ module Types authorize :read_container_image - field :can_delete, GraphQL::Types::Boolean, null: false, description: 'Can the current user delete this tag.' + expose_permissions Types::PermissionTypes::ContainerRepositoryTag + + field :can_delete, GraphQL::Types::Boolean, + null: false, + deprecated: { + reason: 'Use `userPermissions` field. See `ContainerRepositoryTagPermissions` type', + milestone: '16.7' + }, + description: 'Can the current user delete this tag.' field :created_at, Types::TimeType, null: true, description: 'Timestamp when the tag was created.' field :digest, GraphQL::Types::String, null: true, description: 'Digest of the tag.' field :location, GraphQL::Types::String, null: false, description: 'URL of the tag.' diff --git a/app/graphql/types/container_repository_type.rb b/app/graphql/types/container_repository_type.rb index dfa599e798c..c2a7eae5f94 100644 --- a/app/graphql/types/container_repository_type.rb +++ b/app/graphql/types/container_repository_type.rb @@ -8,7 +8,15 @@ module Types authorize :read_container_image - field :can_delete, GraphQL::Types::Boolean, null: false, description: 'Can the current user delete the container repository.' + expose_permissions Types::PermissionTypes::ContainerRepository + + field :can_delete, GraphQL::Types::Boolean, + null: false, + deprecated: { + reason: 'Use `userPermissions` field. See `ContainerRepositoryPermissions` type', + milestone: '16.7' + }, + description: 'Can the current user delete the container repository.' field :created_at, Types::TimeType, null: false, description: 'Timestamp when the container repository was created.' field :expiration_policy_cleanup_status, Types::ContainerRepositoryCleanupStatusEnum, null: true, description: 'Tags cleanup status for the container repository.' field :expiration_policy_started_at, Types::TimeType, null: true, description: 'Timestamp when the cleanup done by the expiration policy was started on the container repository.' diff --git a/app/graphql/types/current_user_type.rb b/app/graphql/types/current_user_type.rb new file mode 100644 index 00000000000..d5ecdeba9e2 --- /dev/null +++ b/app/graphql/types/current_user_type.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Types + # rubocop:disable Graphql/AuthorizeTypes -- This is not necessary because the superclass declares the authorization + class CurrentUserType < ::Types::UserType + graphql_name 'CurrentUser' + description 'The currently authenticated GitLab user.' + end + # rubocop:enable Graphql/AuthorizeTypes +end + +::Types::CurrentUserType.prepend_mod diff --git a/app/graphql/types/group_connection.rb b/app/graphql/types/group_connection.rb deleted file mode 100644 index e4332e24302..00000000000 --- a/app/graphql/types/group_connection.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -# Normally this wouldn't be needed and we could use -# -# type Types::GroupType.connection_type, null: true -# -# in a resolver. However we can end up with cyclic definitions. -# Running the spec locally can result in errors like -# -# NameError: uninitialized constant Types::GroupType -# -# or other errors. To fix this, we created this file and use -# -# type "Types::GroupConnection", null: true -# -# which gives a delayed resolution, and the proper connection type. -# -# See gitlab/app/graphql/types/ci/runner_type.rb -# Reference: https://github.com/rmosolgo/graphql-ruby/issues/3974#issuecomment-1084444214 -# and https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#testing-tips-and-tricks -# -Types::GroupConnection = Types::GroupType.connection_type diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb index 74e7f256b44..7234948033b 100644 --- a/app/graphql/types/group_type.rb +++ b/app/graphql/types/group_type.rb @@ -21,7 +21,8 @@ module Types field :custom_emoji, type: Types::CustomEmojiType.connection_type, null: true, - description: 'Custom emoji within this namespace.', + resolver: Resolvers::CustomEmojiResolver, + description: 'Custom emoji in this namespace.', alpha: { milestone: '13.6' } field :share_with_group_lock, @@ -274,6 +275,14 @@ module Types description: 'Find a work item by IID directly associated with the group. Returns `null` if the ' \ '`namespace_level_work_items` feature flag is disabled.' + field :work_item_state_counts, + Types::WorkItemStateCountsType, + null: true, + alpha: { milestone: '16.7' }, + description: 'Counts of work items by state for the namespace. Returns `null` if the ' \ + '`namespace_level_work_items` feature flag is disabled.', + resolver: Resolvers::Namespaces::WorkItemStateCountsResolver + field :autocomplete_users, null: true, resolver: Resolvers::AutocompleteUsersResolver, @@ -330,10 +339,6 @@ module Types group.dependency_proxy_setting || group.create_dependency_proxy_setting end - def custom_emoji - object.custom_emoji if Feature.enabled?(:custom_emoji) - end - private def group diff --git a/app/graphql/types/issue_connection.rb b/app/graphql/types/issue_connection.rb deleted file mode 100644 index 2f07888b43e..00000000000 --- a/app/graphql/types/issue_connection.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -# Normally this wouldn't be needed and we could use -# -# type Types::IssueType.connection_type, null: true -# -# in a resolver. However we can end up with cyclic definitions. -# Running the spec locally can result in errors like -# -# NameError: uninitialized constant Resolvers::GroupIssuesResolver -# -# or other errors. To fix this, we created this file and use -# -# type "Types::IssueConnection", null: true -# -# which gives a delayed resolution, and the proper connection type. -# -# See app/graphql/resolvers/base_issues_resolver.rb -# Reference: https://github.com/rmosolgo/graphql-ruby/issues/3974#issuecomment-1084444214 -# and https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#testing-tips-and-tricks -# -Types::IssueConnection = Types::IssueType.connection_type diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index 7c7d559e05d..76590f95687 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -91,13 +91,13 @@ module Types description: 'Web URL of the issue.' field :emails_disabled, GraphQL::Types::Boolean, null: false, - method: :project_emails_disabled?, - description: 'Indicates if a project has email notifications disabled: `true` if email notifications are disabled.', + method: :parent_emails_disabled?, + description: 'Indicates if the parent project or group has email notifications disabled: `true` if email notifications are disabled.', deprecated: { reason: 'Use `emails_enabled`', milestone: '16.3' } field :emails_enabled, GraphQL::Types::Boolean, null: false, - method: :project_emails_enabled?, - description: 'Indicates if a project has email notifications disabled: `false` if email notifications are disabled.' + method: :parent_emails_enabled?, + description: 'Indicates if the parent project or group has email notifications disabled: `false` if email notifications are disabled.' field :human_time_estimate, GraphQL::Types::String, null: true, description: 'Human-readable time estimate of the issue.' @@ -162,7 +162,7 @@ module Types field :timelogs, Types::TimelogType.connection_type, null: false, description: 'Timelogs on the issue.' - field :project_id, GraphQL::Types::Int, null: false, method: :project_id, + field :project_id, GraphQL::Types::Int, null: true, method: :project_id, description: 'ID of the issue project.' field :customer_relations_contacts, Types::CustomerRelations::ContactType.connection_type, null: true, diff --git a/app/graphql/types/issue_type_enum.rb b/app/graphql/types/issue_type_enum.rb index d7f587ff03d..491fc3d7fac 100644 --- a/app/graphql/types/issue_type_enum.rb +++ b/app/graphql/types/issue_type_enum.rb @@ -9,16 +9,16 @@ module Types value issue_type.upcase, value: issue_type, description: "#{issue_type.titleize} issue type" end - value 'TASK', value: 'task', - description: 'Task issue type.', - alpha: { milestone: '15.2' } - value 'OBJECTIVE', value: 'objective', - description: 'Objective issue type. Available only when feature flag `okrs_mvc` is enabled.', - alpha: { milestone: '15.6' } + description: 'Objective issue type. Available only when feature flag `okrs_mvc` is enabled.', + alpha: { milestone: '15.6' } value 'KEY_RESULT', value: 'key_result', - description: 'Key Result issue type. Available only when feature flag `okrs_mvc` is enabled.', - alpha: { milestone: '15.7' } + description: 'Key Result issue type. Available only when feature flag `okrs_mvc` is enabled.', + alpha: { milestone: '15.7' } + value 'EPIC', value: 'epic', + description: 'Epic issue type. ' \ + 'Available only when feature flag `namespace_level_work_items` is enabled.', + alpha: { milestone: '16.7' } end end diff --git a/app/graphql/types/merge_requests/detailed_merge_status_enum.rb b/app/graphql/types/merge_requests/detailed_merge_status_enum.rb index e7246068a05..d2d0cfd23a4 100644 --- a/app/graphql/types/merge_requests/detailed_merge_status_enum.rb +++ b/app/graphql/types/merge_requests/detailed_merge_status_enum.rb @@ -48,6 +48,9 @@ module Types value 'PREPARING', value: :preparing, description: 'Merge request diff is being created.' + value 'JIRA_ASSOCIATION', + value: :jira_association_missing, + description: 'Either the title or description must reference a Jira issue.' end end end diff --git a/app/graphql/types/merge_requests/mergeability_check_identifier_enum.rb b/app/graphql/types/merge_requests/mergeability_check_identifier_enum.rb index ac25c98941c..d5e63d9c9ca 100644 --- a/app/graphql/types/merge_requests/mergeability_check_identifier_enum.rb +++ b/app/graphql/types/merge_requests/mergeability_check_identifier_enum.rb @@ -11,7 +11,7 @@ module Types value identifier.upcase, value: identifier, - description: "Mergeability check identifier is #{identifier}." + description: check_class.description end end end diff --git a/app/graphql/types/ml/candidate_links_type.rb b/app/graphql/types/ml/candidate_links_type.rb new file mode 100644 index 00000000000..f14ab6bbc4a --- /dev/null +++ b/app/graphql/types/ml/candidate_links_type.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Types + module Ml + # rubocop: disable Graphql/AuthorizeTypes -- authorization in ModelDetailsResolver + class CandidateLinksType < BaseObject + graphql_name 'MLCandidateLinks' + description 'Represents links to perform actions on the candidate' + + present_using ::Ml::CandidatePresenter + + field :show_path, GraphQL::Types::String, + null: true, description: 'Path to the details page of the candidate.', method: :path + + field :artifact_path, GraphQL::Types::String, + null: true, description: 'Path to the artifact.', method: :artifact_path + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/ml/candidate_type.rb b/app/graphql/types/ml/candidate_type.rb new file mode 100644 index 00000000000..bee045c47bf --- /dev/null +++ b/app/graphql/types/ml/candidate_type.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Types + module Ml + # rubocop: disable Graphql/AuthorizeTypes -- authorization in ModelDetailsResolver + class CandidateType < ::Types::BaseObject + graphql_name 'MlCandidate' + description 'Candidate for a model version in the model registry' + + connection_type_class Types::LimitedCountableConnectionType + + field :id, ::Types::GlobalIDType[::Ml::Candidate], null: false, description: 'ID of the candidate.' + + field :name, ::GraphQL::Types::String, null: false, description: 'Name of the candidate.' + + field :created_at, Types::TimeType, null: false, description: 'Date of creation.' + + field :_links, ::Types::Ml::CandidateLinksType, null: false, method: :itself, + description: 'Map of links to perform actions on the candidate.' + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/ml/model_type.rb b/app/graphql/types/ml/model_type.rb new file mode 100644 index 00000000000..ca63918b370 --- /dev/null +++ b/app/graphql/types/ml/model_type.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Types + module Ml + # rubocop: disable Graphql/AuthorizeTypes -- authorization in ModelDetailsResolver + class ModelType < ::Types::BaseObject + graphql_name 'MlModel' + description 'Machine learning model in the model registry' + + field :id, ::Types::GlobalIDType[::Ml::Model], null: false, description: 'ID of the model.' + + field :name, ::GraphQL::Types::String, null: false, description: 'Name of the model.' + + field :versions, ::Types::Ml::ModelVersionType.connection_type, null: true, + description: 'Versions of the model.' + + field :candidates, ::Types::Ml::CandidateType.connection_type, null: true, + description: 'Version candidates of the model.' + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/ml/model_version_links_type.rb b/app/graphql/types/ml/model_version_links_type.rb new file mode 100644 index 00000000000..142f62bfad2 --- /dev/null +++ b/app/graphql/types/ml/model_version_links_type.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Types + module Ml + # rubocop: disable Graphql/AuthorizeTypes -- authorization in ModelDetailsResolver + class ModelVersionLinksType < BaseObject + graphql_name 'MLModelVersionLinks' + description 'Represents links to perform actions on the model version' + + present_using ::Ml::ModelVersionPresenter + + field :show_path, GraphQL::Types::String, + null: true, description: 'Path to the details page of the model version.', method: :path + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/ml/model_version_type.rb b/app/graphql/types/ml/model_version_type.rb new file mode 100644 index 00000000000..15c36a7a0d8 --- /dev/null +++ b/app/graphql/types/ml/model_version_type.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Types + module Ml + # rubocop: disable Graphql/AuthorizeTypes -- authorization in ModelDetailsResolver + class ModelVersionType < ::Types::BaseObject + graphql_name 'MlModelVersion' + description 'Version of a machine learning model' + + connection_type_class Types::LimitedCountableConnectionType + + field :id, ::Types::GlobalIDType[::Ml::ModelVersion], null: false, description: 'ID of the model version.' + + field :created_at, Types::TimeType, null: false, description: 'Date of creation.' + field :version, ::GraphQL::Types::String, null: false, description: 'Name of the version.' + + field :_links, ::Types::Ml::ModelVersionLinksType, null: false, method: :itself, + description: 'Map of links to perform actions on the model version.' + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index e1bd1f603ad..590bc0ed282 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -107,7 +107,10 @@ module Types mount_mutation Mutations::Notes::RepositionImageDiffNote mount_mutation Mutations::Notes::Destroy mount_mutation Mutations::Organizations::Create, alpha: { milestone: '16.6' } + mount_mutation Mutations::Organizations::Update, alpha: { milestone: '16.7' } mount_mutation Mutations::Projects::SyncFork, calls_gitaly: true, alpha: { milestone: '15.9' } + mount_mutation Mutations::Projects::Star, alpha: { milestone: '16.7' } + mount_mutation Mutations::BranchRules::Update, alpha: { milestone: '16.7' } mount_mutation Mutations::Releases::Create mount_mutation Mutations::Releases::Update mount_mutation Mutations::Releases::Delete @@ -136,10 +139,12 @@ module Types mount_mutation Mutations::DesignManagement::Update mount_mutation Mutations::ContainerExpirationPolicies::Update mount_mutation Mutations::ContainerRegistry::Protection::Rule::Create, alpha: { milestone: '16.6' } + mount_mutation Mutations::ContainerRegistry::Protection::Rule::Delete, alpha: { milestone: '16.7' } + mount_mutation Mutations::ContainerRegistry::Protection::Rule::Update, alpha: { milestone: '16.7' } mount_mutation Mutations::ContainerRepositories::Destroy mount_mutation Mutations::ContainerRepositories::DestroyTags mount_mutation Mutations::Ci::Catalog::Resources::Create, alpha: { milestone: '15.11' } - mount_mutation Mutations::Ci::Catalog::Resources::Unpublish, alpha: { milestone: '16.6' } + mount_mutation Mutations::Ci::Catalog::Resources::Destroy, alpha: { milestone: '16.6' } mount_mutation Mutations::Ci::Job::Cancel mount_mutation Mutations::Ci::Job::Play mount_mutation Mutations::Ci::Job::Retry @@ -176,13 +181,13 @@ module Types mount_mutation Mutations::Packages::DestroyFile mount_mutation Mutations::Packages::Protection::Rule::Create, alpha: { milestone: '16.5' } mount_mutation Mutations::Packages::Protection::Rule::Delete, alpha: { milestone: '16.6' } + mount_mutation Mutations::Packages::Protection::Rule::Update, alpha: { milestone: '16.6' } mount_mutation Mutations::Packages::DestroyFiles mount_mutation Mutations::Packages::Cleanup::Policy::Update mount_mutation Mutations::Echo mount_mutation Mutations::WorkItems::Create, alpha: { milestone: '15.1' } mount_mutation Mutations::WorkItems::CreateFromTask, alpha: { milestone: '15.1' } mount_mutation Mutations::WorkItems::Delete, alpha: { milestone: '15.1' } - mount_mutation Mutations::WorkItems::DeleteTask, alpha: { milestone: '15.1' } mount_mutation Mutations::WorkItems::Update, alpha: { milestone: '15.1' } mount_mutation Mutations::WorkItems::UpdateTask, alpha: { milestone: '15.1' } mount_mutation Mutations::WorkItems::Export, alpha: { milestone: '15.10' } diff --git a/app/graphql/types/namespace/package_settings_type.rb b/app/graphql/types/namespace/package_settings_type.rb index 6c6144f2357..7bf76ae7de5 100644 --- a/app/graphql/types/namespace/package_settings_type.rb +++ b/app/graphql/types/namespace/package_settings_type.rb @@ -31,7 +31,7 @@ module Types description: 'When nuget_duplicates_allowed is false, you can publish duplicate packages with names that match this regex. Otherwise, this setting has no effect. ' field :nuget_duplicates_allowed, GraphQL::Types::Boolean, null: false, - description: 'Indicates whether duplicate NuGet packages are allowed for this namespace. ' + description: 'Indicates whether duplicate NuGet packages are allowed for this namespace.' field :pypi_package_requests_forwarding, GraphQL::Types::Boolean, null: true, description: 'Indicates whether PyPI package forwarding is allowed for this namespace.' @@ -58,5 +58,9 @@ module Types null: false, method: :pypi_package_requests_forwarding_locked?, description: 'Indicates whether PyPI package forwarding settings are locked by a parent namespace.' + + field :nuget_symbol_server_enabled, GraphQL::Types::Boolean, + null: false, + description: 'Indicates wheather the NuGet symbol server is enabled for this namespace.' end end diff --git a/app/graphql/types/organizations/organization_type.rb b/app/graphql/types/organizations/organization_type.rb index e7ba8de527c..379bf9956a3 100644 --- a/app/graphql/types/organizations/organization_type.rb +++ b/app/graphql/types/organizations/organization_type.rb @@ -7,6 +7,16 @@ module Types authorize :read_organization + field :avatar_url, + type: GraphQL::Types::String, + null: true, + description: 'Avatar URL of the organization.', + alpha: { milestone: '16.7' } + field :description, + GraphQL::Types::String, + null: true, + description: 'Description of the organization.', + alpha: { milestone: '16.7' } field :groups, Types::GroupType.connection_type, null: false, @@ -33,10 +43,17 @@ module Types null: false, description: 'Path of the organization.', alpha: { milestone: '16.4' } - field :web_url, GraphQL::Types::String, + field :web_url, + GraphQL::Types::String, null: false, description: 'Web URL of the organization.', alpha: { milestone: '16.6' } + + markdown_field :description_html, null: true, alpha: { milestone: '16.7' }, &:organization_detail + + def avatar_url + object.avatar_url(only_path: false) + end end end end diff --git a/app/graphql/types/permission_types/abuse_report.rb b/app/graphql/types/permission_types/abuse_report.rb deleted file mode 100644 index abd5d545d02..00000000000 --- a/app/graphql/types/permission_types/abuse_report.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Types - module PermissionTypes - class AbuseReport < BasePermissionType - graphql_name 'AbuseReportPermissions' - - abilities :read_abuse_report, :create_note - end - end -end diff --git a/app/graphql/types/permission_types/container_repository.rb b/app/graphql/types/permission_types/container_repository.rb new file mode 100644 index 00000000000..f6a76fb4d94 --- /dev/null +++ b/app/graphql/types/permission_types/container_repository.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Types + module PermissionTypes + class ContainerRepository < BasePermissionType + graphql_name 'ContainerRepositoryPermissions' + + ability_field :destroy_container_image, + name: 'destroy_container_repository', + resolver_method: :destroy_container_image + end + end +end diff --git a/app/graphql/types/permission_types/container_repository_tag.rb b/app/graphql/types/permission_types/container_repository_tag.rb new file mode 100644 index 00000000000..e2317ccd7d7 --- /dev/null +++ b/app/graphql/types/permission_types/container_repository_tag.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Types + module PermissionTypes + class ContainerRepositoryTag < BasePermissionType + graphql_name 'ContainerRepositoryTagPermissions' + + ability_field :destroy_container_image, + name: 'destroy_container_repository_tag', + resolver_method: :destroy_container_image + end + end +end diff --git a/app/graphql/types/project_feature_access_level_enum.rb b/app/graphql/types/project_feature_access_level_enum.rb new file mode 100644 index 00000000000..a107dbedc52 --- /dev/null +++ b/app/graphql/types/project_feature_access_level_enum.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Types + class ProjectFeatureAccessLevelEnum < BaseEnum + graphql_name 'ProjectFeatureAccessLevel' + description 'Access level of a project feature' + + value 'DISABLED', value: ProjectFeature::DISABLED, description: 'Not enabled for anyone.' + value 'PRIVATE', value: ProjectFeature::PRIVATE, description: 'Enabled only for team members.' + value 'ENABLED', value: ProjectFeature::ENABLED, description: 'Enabled for everyone able to access the project.' + end +end diff --git a/app/graphql/types/project_feature_access_level_type.rb b/app/graphql/types/project_feature_access_level_type.rb new file mode 100644 index 00000000000..d6d9fdefa70 --- /dev/null +++ b/app/graphql/types/project_feature_access_level_type.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# rubocop:disable Graphql/AuthorizeTypes -- It just returns the value of an enum as an integer and a string +module Types + class ProjectFeatureAccessLevelType < Types::BaseObject + graphql_name 'ProjectFeatureAccess' + description 'Represents the access level required by the user to access a project feature' + + field :integer_value, GraphQL::Types::Int, null: true, + description: 'Integer representation of access level.', + method: :to_i + + field :string_value, Types::ProjectFeatureAccessLevelEnum, null: true, + description: 'String representation of access level.', + method: :to_i + end +end +# rubocop:enable Graphql/AuthorizeTypes diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index ec87f133843..8e84605cb05 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -151,6 +151,10 @@ module Types null: true, description: 'Number of open issues for the project.' + field :open_merge_requests_count, GraphQL::Types::Int, + null: true, + description: 'Number of open merge requests for the project.' + field :allow_merge_on_skipped_pipeline, GraphQL::Types::Boolean, null: true, description: 'If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of ' \ @@ -250,6 +254,13 @@ module Types extras: [:lookahead], resolver: Resolvers::WorkItemsResolver + field :work_item_state_counts, + Types::WorkItemStateCountsType, + null: true, + alpha: { milestone: '16.7' }, + description: 'Counts of work items by state for the project.', + resolver: Resolvers::WorkItemStateCountsResolver + field :issue_status_counts, Types::IssueStatusCountsType, null: true, @@ -647,6 +658,11 @@ module Types description: 'Detailed import status of the project.', method: :import_state + field :value_streams, + description: 'Value streams available to the project.', + null: true, + resolver: Resolvers::Analytics::CycleAnalytics::ValueStreamsResolver + def timelog_categories object.project_namespace.timelog_categories if Feature.enabled?(:timelog_categories) end @@ -675,6 +691,19 @@ module Types end end + [:issues, :forking, :merge_requests].each do |feature| + field_name = "#{feature}_access_level" + feature_name = feature.to_s.tr("_", " ") + + field field_name, Types::ProjectFeatureAccessLevelType, + null: true, + description: "Access level required for #{feature_name} access." + + define_method field_name do + project.project_feature&.access_level(feature) + end + end + markdown_field :description_html, null: true def avatar_url @@ -689,6 +718,12 @@ module Types BatchLoader::GraphQL.wrap(object.open_issues_count) if object.feature_available?(:issues, context[:current_user]) end + def open_merge_requests_count + return unless object.feature_available?(:merge_requests, context[:current_user]) + + BatchLoader::GraphQL.wrap(object.open_merge_requests_count) + end + def forks_count BatchLoader::GraphQL.wrap(object.forks_count) end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 173e877d86c..0e39ff2c030 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -48,7 +48,7 @@ module Types required: true, description: 'Global ID of the container repository.' end - field :current_user, Types::UserType, + field :current_user, Types::CurrentUserType, null: true, description: "Get information about current user." field :design_management, Types::DesignManagementType, @@ -57,12 +57,10 @@ module Types field :echo, resolver: Resolvers::EchoResolver field :frecent_groups, [Types::GroupType], resolver: Resolvers::Users::FrecentGroupsResolver, - description: "A user's frecently visited groups. Requires the `frecent_namespaces_suggestions` feature flag to be enabled.", - alpha: { milestone: '16.6' } + description: "A user's frecently visited groups" field :frecent_projects, [Types::ProjectType], resolver: Resolvers::Users::FrecentProjectsResolver, - description: "A user's frecently visited projects. Requires the `frecent_namespaces_suggestions` feature flag to be enabled.", - alpha: { milestone: '16.6' } + description: "A user's frecently visited projects" field :gitpod_enabled, GraphQL::Types::Boolean, null: true, description: "Whether Gitpod is enabled in application settings." @@ -212,6 +210,19 @@ module Types description: 'Abuse report labels.', resolver: Resolvers::AbuseReportLabelsResolver + field :ml_model, ::Types::Ml::ModelType, + null: true, + alpha: { milestone: '16.7' }, + description: 'Find machine learning models.', + resolver: Resolvers::Ml::ModelDetailResolver + + field :work_items_by_reference, + null: true, + alpha: { milestone: '16.7' }, + description: 'Find work items by their reference.', + extras: [:lookahead], + resolver: Resolvers::WorkItemReferencesResolver + def design_management DesignManagementObject.new(nil) end diff --git a/app/graphql/types/security/codequality_reports_comparer/degradation_type.rb b/app/graphql/types/security/codequality_reports_comparer/degradation_type.rb index 7dd47611a2e..d4aca0a3792 100644 --- a/app/graphql/types/security/codequality_reports_comparer/degradation_type.rb +++ b/app/graphql/types/security/codequality_reports_comparer/degradation_type.rb @@ -35,7 +35,7 @@ module Types description: 'URL to the file along with line number.' field :engine_name, GraphQL::Types::String, - null: false, + null: true, description: 'Code quality plugin that reported the degradation.' end # rubocop: enable Graphql/AuthorizeTypes diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb index 040711b5f58..7687da35baa 100644 --- a/app/graphql/types/user_interface.rb +++ b/app/graphql/types/user_interface.rb @@ -178,7 +178,7 @@ module Types field :twitter, type: ::GraphQL::Types::String, null: true, - description: 'Twitter username of the user.' + description: 'X (formerly Twitter) username of the user.' field :discord, type: ::GraphQL::Types::String, @@ -229,5 +229,3 @@ module Types end end end - -Types::UserInterface.prepend_mod diff --git a/app/graphql/types/user_preferences_type.rb b/app/graphql/types/user_preferences_type.rb index 094c7352c96..e9ac3a28a53 100644 --- a/app/graphql/types/user_preferences_type.rb +++ b/app/graphql/types/user_preferences_type.rb @@ -14,6 +14,10 @@ module Types description: 'Determines whether the pipeline list shows ID or IID.', null: true + field :use_web_ide_extension_marketplace, GraphQL::Types::Boolean, + description: 'Whether Web IDE Extension Marketplace is enabled for the user.', + null: false + def issues_sort object.issues_sort.to_sym end diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb index 87ca5fddf14..c5910236d51 100644 --- a/app/graphql/types/user_type.rb +++ b/app/graphql/types/user_type.rb @@ -14,3 +14,5 @@ module Types present_using UserPresenter end end + +Types::UserType.prepend_mod diff --git a/app/graphql/types/work_item_state_counts_type.rb b/app/graphql/types/work_item_state_counts_type.rb new file mode 100644 index 00000000000..a5fdf542464 --- /dev/null +++ b/app/graphql/types/work_item_state_counts_type.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Types + # rubocop: disable Graphql/AuthorizeTypes -- Parent node applies authorization + class WorkItemStateCountsType < BaseObject + graphql_name 'WorkItemStateCountsType' + description 'Represents total number of work items for the represented states' + + field :all, + GraphQL::Types::Int, + null: true, + description: 'Number of work items for the project or group.' + + field :closed, + GraphQL::Types::Int, + null: true, + description: 'Number of work items with state CLOSED for the project or group.' + + field :opened, + GraphQL::Types::Int, + null: true, + description: 'Number of work items with state OPENED for the project or group.' + end + # rubocop: enable Graphql/AuthorizeTypes +end diff --git a/app/graphql/types/work_item_type.rb b/app/graphql/types/work_item_type.rb index 103a1c0ec9b..b42684e650b 100644 --- a/app/graphql/types/work_item_type.rb +++ b/app/graphql/types/work_item_type.rb @@ -67,6 +67,12 @@ module Types expose_permissions Types::PermissionTypes::WorkItem + def work_item_type + context.scoped_set!(:resource_parent, object.resource_parent) + + object.work_item_type + end + def web_url Gitlab::UrlBuilder.build(object) end diff --git a/app/graphql/types/work_items/deleted_task_input_type.rb b/app/graphql/types/work_items/deleted_task_input_type.rb deleted file mode 100644 index 92297876c89..00000000000 --- a/app/graphql/types/work_items/deleted_task_input_type.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module Types - module WorkItems - class DeletedTaskInputType < BaseInputObject - graphql_name 'WorkItemDeletedTaskInput' - - argument :id, ::Types::GlobalIDType[::WorkItem], - required: true, - description: 'Global ID of the task referenced in the work item\'s description.' - argument :line_number_end, GraphQL::Types::Int, - required: true, - description: 'Last line in the Markdown source that defines the list item task.' - argument :line_number_start, GraphQL::Types::Int, - required: true, - description: 'First line in the Markdown source that defines the list item task.' - end - end -end diff --git a/app/graphql/types/work_items/type_type.rb b/app/graphql/types/work_items/type_type.rb index 4d008a21b9c..b42d73544dc 100644 --- a/app/graphql/types/work_items/type_type.rb +++ b/app/graphql/types/work_items/type_type.rb @@ -7,12 +7,20 @@ module Types authorize :read_work_item_type - field :icon_name, GraphQL::Types::String, null: true, - description: 'Icon name of the work item type.' - field :id, Types::GlobalIDType[::WorkItems::Type], null: false, - description: 'Global ID of the work item type.' - field :name, GraphQL::Types::String, null: false, - description: 'Name of the work item type.' + field :icon_name, GraphQL::Types::String, + null: true, + description: 'Icon name of the work item type.' + field :id, Types::GlobalIDType[::WorkItems::Type], + null: false, + description: 'Global ID of the work item type.' + field :name, GraphQL::Types::String, + null: false, + description: 'Name of the work item type.' + field :widget_definitions, [Types::WorkItems::WidgetDefinitionInterface], + null: true, + description: 'Available widgets for the work item type.', + method: :widgets, + alpha: { milestone: '16.7' } end end end diff --git a/app/graphql/types/work_items/widget_definition_interface.rb b/app/graphql/types/work_items/widget_definition_interface.rb new file mode 100644 index 00000000000..032e78779e7 --- /dev/null +++ b/app/graphql/types/work_items/widget_definition_interface.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Types + module WorkItems + module WidgetDefinitionInterface + include Types::BaseInterface + + graphql_name 'WorkItemWidgetDefinition' + + field :type, ::Types::WorkItems::WidgetTypeEnum, + null: false, + description: 'Widget type.' + + ORPHAN_TYPES = [ + ::Types::WorkItems::WidgetDefinitions::AssigneesType, + ::Types::WorkItems::WidgetDefinitions::GenericType, + ::Types::WorkItems::WidgetDefinitions::HierarchyType + ].freeze + + def self.ce_orphan_types + ORPHAN_TYPES + end + + def self.resolve_type(object, _context) + if object == ::WorkItems::Widgets::Assignees + ::Types::WorkItems::WidgetDefinitions::AssigneesType + elsif object == ::WorkItems::Widgets::Hierarchy + ::Types::WorkItems::WidgetDefinitions::HierarchyType + else + ::Types::WorkItems::WidgetDefinitions::GenericType + end + end + + orphan_types(*ce_orphan_types) + end + end +end + +Types::WorkItems::WidgetDefinitionInterface.prepend_mod diff --git a/app/graphql/types/work_items/widget_definitions/assignees_type.rb b/app/graphql/types/work_items/widget_definitions/assignees_type.rb new file mode 100644 index 00000000000..6f30148e9aa --- /dev/null +++ b/app/graphql/types/work_items/widget_definitions/assignees_type.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Types + module WorkItems + module WidgetDefinitions + # rubocop:disable Graphql/AuthorizeTypes -- Authorization too granular, parent type is authorized + class AssigneesType < BaseObject + graphql_name 'WorkItemWidgetDefinitionAssignees' + description 'Represents an assignees widget definition' + + implements Types::WorkItems::WidgetDefinitionInterface + + field :can_invite_members, GraphQL::Types::Boolean, + null: false, + description: 'Indicates whether the current user can invite members to the work item\'s parent.' + + def can_invite_members + object.can_invite_members?(current_user, resource_parent) + end + + private + + def resource_parent + context[:resource_parent] + end + end + # rubocop:enable Graphql/AuthorizeTypes + end + end +end + +Types::WorkItems::WidgetDefinitions::AssigneesType.prepend_mod diff --git a/app/graphql/types/work_items/widget_definitions/generic_type.rb b/app/graphql/types/work_items/widget_definitions/generic_type.rb new file mode 100644 index 00000000000..f3817ade654 --- /dev/null +++ b/app/graphql/types/work_items/widget_definitions/generic_type.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Types + module WorkItems + module WidgetDefinitions + # rubocop:disable Graphql/AuthorizeTypes -- Authorization too granular, parent type is authorized + class GenericType < BaseObject + graphql_name 'WorkItemWidgetDefinitionGeneric' + description 'Represents a generic widget definition' + + implements Types::WorkItems::WidgetDefinitionInterface + end + # rubocop:enable Graphql/AuthorizeTypes + end + end +end diff --git a/app/graphql/types/work_items/widget_definitions/hierarchy_type.rb b/app/graphql/types/work_items/widget_definitions/hierarchy_type.rb new file mode 100644 index 00000000000..8bd70050c7c --- /dev/null +++ b/app/graphql/types/work_items/widget_definitions/hierarchy_type.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Types + module WorkItems + module WidgetDefinitions + # rubocop:disable Graphql/AuthorizeTypes -- authorized in work item type entity + class HierarchyType < BaseObject + graphql_name 'WorkItemWidgetDefinitionHierarchy' + description 'Represents a hierarchy widget definition' + + implements Types::WorkItems::WidgetDefinitionInterface + + field :allowed_child_types, Types::WorkItems::TypeType.connection_type, + null: true, + complexity: 5, + extras: [:parent], + description: 'Allowed child types for the work item type.' + + def allowed_child_types(parent:) + parent.allowed_child_types(cache: true) + end + end + # rubocop:enable Graphql/AuthorizeTypes + end + end +end diff --git a/app/graphql/types/work_items/widgets/assignees_type.rb b/app/graphql/types/work_items/widgets/assignees_type.rb index 74da3264567..ecc0bc390d3 100644 --- a/app/graphql/types/work_items/widgets/assignees_type.rb +++ b/app/graphql/types/work_items/widgets/assignees_type.rb @@ -18,11 +18,21 @@ module Types field :allows_multiple_assignees, GraphQL::Types::Boolean, null: true, method: :allows_multiple_assignees?, - description: 'Indicates whether multiple assignees are allowed.' + description: 'Indicates whether multiple assignees are allowed.', + deprecated: { + milestone: '16.7', + replacement: 'workitemWidgetDefinitionAssignees.allowsMultipleAssignees', + reason: 'Field moved to workItemType widget definition interface' + } field :can_invite_members, GraphQL::Types::Boolean, null: false, resolver_method: :can_invite_members?, - description: 'Indicates whether the current user can invite members to the work item\'s project.' + description: 'Indicates whether the current user can invite members to the work item\'s project.', + deprecated: { + milestone: '16.7', + replacement: 'workitemWidgetDefinitionAssignees.canInviteMembers', + reason: 'Field moved to workItemType widget definition interface' + } def can_invite_members? Ability.allowed?(current_user, :admin_project_member, object.work_item.project) diff --git a/app/graphql/types/work_items/widgets/labels_type.rb b/app/graphql/types/work_items/widgets/labels_type.rb index 20574b3e3bc..5e5d324341d 100644 --- a/app/graphql/types/work_items/widgets/labels_type.rb +++ b/app/graphql/types/work_items/widgets/labels_type.rb @@ -19,7 +19,12 @@ module Types field :allows_scoped_labels, GraphQL::Types::Boolean, null: true, method: :allows_scoped_labels?, - description: 'Indicates whether a scoped label is allowed.' + description: 'Indicates whether a scoped label is allowed.', + deprecated: { + milestone: '16.7', + replacement: 'WorkItemWidgetDefinitionLabels.allowsScopedLabels', + reason: 'Field moved to workItemType widget definition interface' + } end # rubocop:enable Graphql/AuthorizeTypes end |