From 5d22ba4cf210eb73930f7d64575d8bf57bdc0bf0 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 13 May 2025 09:09:06 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/ci/rails.gitlab-ci.yml | 2 +- GITLAB_KAS_VERSION | 2 +- .../graphql_shared/possible_types.json | 5 + .../components/create_work_item.vue | 29 ++- .../namespace_work_item_types.query.graphql | 1 + .../mutations/ci/runner/assign_to_project.rb | 38 ++++ .../ci/runner/unassign_from_project.rb | 37 ++++ app/graphql/types/mutation_type.rb | 2 + app/graphql/types/namespace_type.rb | 7 + app/graphql/types/namespaces/link_paths.rb | 88 ++++++++ .../link_paths/group_namespace_links_type.rb | 36 ++++ .../project_namespace_links_type.rb | 40 ++++ .../link_paths/user_namespace_links_type.rb | 12 ++ app/helpers/issuables_helper.rb | 2 +- .../protection/concerns/tag_rule.rb | 28 +++ .../cleanup_tags_base_service.rb | 16 +- .../gitlab/delete_tags_service.rb | 15 +- config/initializers/7_prometheus_metrics.rb | 16 +- ...fill_resource_link_events_namespace_id.yml | 2 +- ...kfill_resource_link_events_namespace_id.rb | 21 ++ db/schema_migrations/20250511231623 | 1 + doc/administration/get_started.md | 3 - doc/api/discussions.md | 2 +- doc/api/graphql/reference/_index.md | 124 +++++++++++ doc/editor_extensions/eclipse/_index.md | 1 - doc/editor_extensions/jetbrains_ide/_index.md | 1 - doc/editor_extensions/visual_studio/_index.md | 1 - .../advanced_search/elasticsearch.md | 2 - doc/solutions/cloud/aws/gitaly_sre_for_aws.md | 2 +- doc/tutorials/reviews/_index.md | 2 - doc/update/terminology.md | 4 +- doc/user/gitlab_com/_index.md | 6 +- locale/gitlab.pot | 9 + qa/qa/support/wait_for_requests.rb | 7 +- qa/spec/page/logging_spec.rb | 2 + qa/tasks/ci.rake | 25 ++- .../components/create_work_item_spec.js | 33 ++- .../group_namespace_links_type_spec.rb | 7 + .../project_namespace_links_type_spec.rb | 7 + .../user_namespace_links_type_spec.rb | 7 + .../types/namespaces/link_paths_spec.rb | 40 ++++ .../ci/runner/assign_to_project_spec.rb | 188 ++++++++++++++++ .../ci/runner/unassign_from_project_spec.rb | 200 ++++++++++++++++++ .../protection/concerns/tag_rule_spec.rb | 81 +++++++ .../gitlab/cleanup_tags_service_spec.rb | 44 +--- .../gitlab/delete_tags_service_spec.rb | 64 +----- .../namespaces/link_paths_shared_examples.rb | 29 +++ ...t_usage_data_collection_shared_examples.rb | 2 + .../cleanup_tags_service_shared_examples.rb | 16 +- 49 files changed, 1128 insertions(+), 181 deletions(-) create mode 100644 app/graphql/mutations/ci/runner/assign_to_project.rb create mode 100644 app/graphql/mutations/ci/runner/unassign_from_project.rb create mode 100644 app/graphql/types/namespaces/link_paths.rb create mode 100644 app/graphql/types/namespaces/link_paths/group_namespace_links_type.rb create mode 100644 app/graphql/types/namespaces/link_paths/project_namespace_links_type.rb create mode 100644 app/graphql/types/namespaces/link_paths/user_namespace_links_type.rb create mode 100644 app/services/container_registry/protection/concerns/tag_rule.rb create mode 100644 db/post_migrate/20250511231623_finalize_hk_backfill_resource_link_events_namespace_id.rb create mode 100644 db/schema_migrations/20250511231623 create mode 100644 spec/graphql/types/namespaces/link_paths/group_namespace_links_type_spec.rb create mode 100644 spec/graphql/types/namespaces/link_paths/project_namespace_links_type_spec.rb create mode 100644 spec/graphql/types/namespaces/link_paths/user_namespace_links_type_spec.rb create mode 100644 spec/graphql/types/namespaces/link_paths_spec.rb create mode 100644 spec/requests/api/graphql/mutations/ci/runner/assign_to_project_spec.rb create mode 100644 spec/requests/api/graphql/mutations/ci/runner/unassign_from_project_spec.rb create mode 100644 spec/services/container_registry/protection/concerns/tag_rule_spec.rb create mode 100644 spec/support/shared_examples/graphql/types/namespaces/link_paths_shared_examples.rb diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 0b55d4b9956..02c710fa92c 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -1061,7 +1061,7 @@ rspec unit pg17: rspec integration pg17: extends: - - .rspec-base-pg16 + - .rspec-base-pg17 - .rails:rules:default-branch-schedule-nightly--code-backstage - .rspec-integration-parallel diff --git a/GITLAB_KAS_VERSION b/GITLAB_KAS_VERSION index c448ab2fdf4..e2c978ceb81 100644 --- a/GITLAB_KAS_VERSION +++ b/GITLAB_KAS_VERSION @@ -1 +1 @@ -7b5e739ea26a4d484d2986595626caf7cf02b002 +9f9e01592618d35f9acd9573970fc39a1177fbc3 diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json index 5b458a92b44..0b89a6021e1 100644 --- a/app/assets/javascripts/graphql_shared/possible_types.json +++ b/app/assets/javascripts/graphql_shared/possible_types.json @@ -143,6 +143,11 @@ "CiDeletedNamespace", "Namespace" ], + "NamespacesLinkPaths": [ + "GroupNamespaceLinks", + "ProjectNamespaceLinks", + "UserNamespaceLinks" + ], "NoteableInterface": [ "AlertManagementAlert", "BoardEpic", diff --git a/app/assets/javascripts/work_items/components/create_work_item.vue b/app/assets/javascripts/work_items/components/create_work_item.vue index 7796df302a9..cfd35e2a289 100644 --- a/app/assets/javascripts/work_items/components/create_work_item.vue +++ b/app/assets/javascripts/work_items/components/create_work_item.vue @@ -11,6 +11,7 @@ import { GlIcon, } from '@gitlab/ui'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; + import { createAlert } from '~/alert'; import { clearDraft } from '~/lib/utils/autosave'; import { isMetaEnterKeyPair, parseBoolean } from '~/lib/utils/common_utils'; @@ -30,7 +31,11 @@ import { getNewWorkItemAutoSaveKey, newWorkItemFullPath, } from '~/work_items/utils'; -import { TYPENAME_MERGE_REQUEST, TYPENAME_VULNERABILITY } from '~/graphql_shared/constants'; +import { + TYPENAME_MERGE_REQUEST, + TYPENAME_VULNERABILITY, + TYPENAME_GROUP, +} from '~/graphql_shared/constants'; import { I18N_WORK_ITEM_ERROR_CREATING, i18n, @@ -198,7 +203,7 @@ export default { localTitle: this.title || '', error: null, workItem: {}, - workItemTypes: [], + namespace: null, selectedProjectFullPath: this.initialSelectedProject(), selectedWorkItemTypeId: null, loading: false, @@ -234,7 +239,7 @@ export default { this.error = i18n.fetchError; }, }, - workItemTypes: { + namespace: { query() { return namespaceWorkItemTypesQuery; }, @@ -244,7 +249,7 @@ export default { }; }, update(data) { - return data.workspace?.workItemTypes?.nodes; + return data.workspace; }, async result() { this.initialLoadingWorkItemTypes = false; @@ -291,6 +296,9 @@ export default { }, }, computed: { + workItemTypes() { + return this.namespace?.workItemTypes?.nodes ?? []; + }, newWorkItemPath() { return newWorkItemFullPath(this.selectedProjectFullPath, this.selectedWorkItemTypeName); }, @@ -532,6 +540,16 @@ export default { showWorkItemStatus() { return this.workItemStatus && this.glFeatures.workItemStatusFeatureFlag; }, + isGroupWorkItem() { + return this.namespace?.id.includes(TYPENAME_GROUP); + }, + uploadsPath() { + const rootPath = this.namespace?.webUrl; + if (!rootPath) { + return window.uploads_path; + } + return this.isGroupWorkItem ? `${rootPath}/-/uploads` : `${rootPath}/uploads`; + }, }, watch: { shouldDiscardDraft: { @@ -871,7 +889,7 @@ export default { /> - + diff --git a/app/assets/javascripts/work_items/graphql/namespace_work_item_types.query.graphql b/app/assets/javascripts/work_items/graphql/namespace_work_item_types.query.graphql index 55264d23185..586686a66a9 100644 --- a/app/assets/javascripts/work_items/graphql/namespace_work_item_types.query.graphql +++ b/app/assets/javascripts/work_items/graphql/namespace_work_item_types.query.graphql @@ -3,6 +3,7 @@ query namespaceWorkItemTypes($fullPath: ID!, $name: IssueType) { workspace: namespace(fullPath: $fullPath) { id + webUrl workItemTypes(name: $name) { nodes { ...WorkItemTypeFragment diff --git a/app/graphql/mutations/ci/runner/assign_to_project.rb b/app/graphql/mutations/ci/runner/assign_to_project.rb new file mode 100644 index 00000000000..b086b6edd09 --- /dev/null +++ b/app/graphql/mutations/ci/runner/assign_to_project.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Mutations + module Ci + module Runner + class AssignToProject < BaseMutation + graphql_name 'RunnerAssignToProject' + + authorize :assign_runner + + argument :runner_id, ::Types::GlobalIDType[::Ci::Runner], + required: true, + description: 'ID of the runner to assign to the project .' + + argument :project_path, GraphQL::Types::ID, + required: true, + description: 'Full path of the project to which the runner will be assigned.' + + def resolve(**args) + project, runner = find_project_and_runner!(args) + result = ::Ci::Runners::AssignRunnerService.new(runner, project, current_user).execute + + { errors: result.errors } + end + + def find_project_and_runner!(args) + project = ::Project.find_by_full_path(args[:project_path]) + raise_resource_not_available_error! unless project + + runner = authorized_find!(id: args[:runner_id]) + raise_resource_not_available_error!("Runner is not a project runner") unless runner.project_type? + + [project, runner] + end + end + end + end +end diff --git a/app/graphql/mutations/ci/runner/unassign_from_project.rb b/app/graphql/mutations/ci/runner/unassign_from_project.rb new file mode 100644 index 00000000000..2cd6843a68f --- /dev/null +++ b/app/graphql/mutations/ci/runner/unassign_from_project.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Mutations + module Ci + module Runner + class UnassignFromProject < BaseMutation + graphql_name 'RunnerUnassignFromProject' + + include FindsProject + + authorize :admin_project_runners + + argument :runner_id, ::Types::GlobalIDType[::Ci::Runner], + required: true, + description: 'ID of the runner to unassign from the project.' + + argument :project_path, GraphQL::Types::ID, + required: true, + description: 'Full path of the project from which the runner will be unassigned.' + + def resolve(**args) + project = authorized_find!(args[:project_path]) + runner_id = GitlabSchema.parse_gid(args[:runner_id], expected_type: ::Ci::Runner).model_id + runner_project = project.runner_projects.find_by_runner_id(runner_id) + + unless runner_project&.runner + raise_resource_not_available_error! "Runner does not exist or is not assigned to this project" + end + + result = ::Ci::Runners::UnassignRunnerService.new(runner_project, current_user).execute + + { errors: result.errors } + end + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index b0d673c8241..0a1efdee237 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -208,6 +208,8 @@ module Types mount_mutation Mutations::Ci::Runner::Create, experiment: { milestone: '15.10' } mount_mutation Mutations::Ci::Runner::Delete mount_mutation Mutations::Ci::Runner::Update + mount_mutation Mutations::Ci::Runner::AssignToProject, experiment: { milestone: '18.1' } + mount_mutation Mutations::Ci::Runner::UnassignFromProject, experiment: { milestone: '18.1' } mount_mutation Mutations::Ci::RunnersRegistrationToken::Reset, deprecated: { reason: 'Underlying feature was deprecated in 15.6 and will be removed in 18.0', milestone: '17.7' diff --git a/app/graphql/types/namespace_type.rb b/app/graphql/types/namespace_type.rb index 3ac3607fcc8..cc0674fc93f 100644 --- a/app/graphql/types/namespace_type.rb +++ b/app/graphql/types/namespace_type.rb @@ -133,6 +133,13 @@ module Types calls_gitaly: true, description: 'Work item description templates available to the namespace.' + field :link_paths, + Types::Namespaces::LinkPaths, + null: true, + description: 'Namespace relevant paths to create links on the UI.', + method: :itself, + experiment: { milestone: '18.1' } + markdown_field :description_html, null: true def achievements_path diff --git a/app/graphql/types/namespaces/link_paths.rb b/app/graphql/types/namespaces/link_paths.rb new file mode 100644 index 00000000000..7f1645ddad5 --- /dev/null +++ b/app/graphql/types/namespaces/link_paths.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +module Types + module Namespaces + module LinkPaths + include ::Types::BaseInterface + # required for the new_comment_template_paths + include ::IssuablesHelper + + graphql_name 'NamespacesLinkPaths' + + TYPE_MAPPINGS = { + ::Group => ::Types::Namespaces::LinkPaths::GroupNamespaceLinksType, + ::Namespaces::ProjectNamespace => ::Types::Namespaces::LinkPaths::ProjectNamespaceLinksType, + ::Namespaces::UserNamespace => ::Types::Namespaces::LinkPaths::UserNamespaceLinksType + }.freeze + + field :issues_list, + GraphQL::Types::String, + null: true, + description: 'Namespace issues_list.', + fallback_value: nil + + field :labels_manage, + GraphQL::Types::String, + null: true, + description: 'Namespace labels_manage.', + fallback_value: nil + + field :new_project, + GraphQL::Types::String, + null: true, + description: 'Namespace new_project.', + fallback_value: nil + + field :new_comment_template, + GraphQL::Types::String, + null: true, + description: 'Namespace new_comment_template_paths.', + fallback_value: nil + + field :register, + GraphQL::Types::String, + null: true, + description: 'Namespace register_path.' + + field :report_abuse, + GraphQL::Types::String, + null: true, + description: 'Namespace report_abuse.' + + field :sign_in, + GraphQL::Types::String, + null: true, + description: 'Namespace sign_in_path.' + + def self.type_mappings + TYPE_MAPPINGS + end + + def self.resolve_type(object, _context) + type_mappings[object.class] || raise("Unknown GraphQL type for namespace type #{object.class}") + end + + orphan_types(*type_mappings.values) + + def register + url_helpers.new_user_registration_path(redirect_to_referer: 'yes') + end + + def report_abuse + url_helpers.add_category_abuse_reports_path + end + + def sign_in + url_helpers.new_user_session_path(redirect_to_referer: 'yes') + end + + private + + def url_helpers + Gitlab::Routing.url_helpers + end + end + end +end + +::Types::Namespaces::LinkPaths.prepend_mod diff --git a/app/graphql/types/namespaces/link_paths/group_namespace_links_type.rb b/app/graphql/types/namespaces/link_paths/group_namespace_links_type.rb new file mode 100644 index 00000000000..b09bbba3da1 --- /dev/null +++ b/app/graphql/types/namespaces/link_paths/group_namespace_links_type.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Types + module Namespaces + module LinkPaths + class GroupNamespaceLinksType < BaseObject # rubocop:disable Graphql/AuthorizeTypes -- parent is already authorized + graphql_name 'GroupNamespaceLinks' + implements ::Types::Namespaces::LinkPaths + + alias_method :group, :object + + def issues_list + url_helpers.issues_group_path(group) + end + + def labels_manage + url_helpers.group_labels_path(group) + end + + def new_project + url_helpers.new_project_path(namespace_id: group&.id) + end + + def report_abuse + url_helpers.add_category_abuse_reports_path + end + + def new_comment_template + new_comment_template_paths(group)&.dig(0, :href) + end + end + end + end +end + +::Types::Namespaces::LinkPaths::GroupNamespaceLinksType.prepend_mod diff --git a/app/graphql/types/namespaces/link_paths/project_namespace_links_type.rb b/app/graphql/types/namespaces/link_paths/project_namespace_links_type.rb new file mode 100644 index 00000000000..73607993b8a --- /dev/null +++ b/app/graphql/types/namespaces/link_paths/project_namespace_links_type.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Types + module Namespaces + module LinkPaths + class ProjectNamespaceLinksType < BaseObject # rubocop:disable Graphql/AuthorizeTypes -- parent is already authorized + graphql_name 'ProjectNamespaceLinks' + implements ::Types::Namespaces::LinkPaths + + def issues_list + url_helpers.project_issues_path(project) + end + + def labels_manage + url_helpers.project_labels_path(project) + end + + def new_project + url_helpers.new_project_path(namespace_id: group&.id) + end + + def new_comment_template + new_comment_template_paths(group, project)&.dig(0, :href) + end + + private + + def project + @project ||= object.project + end + + def group + @group ||= project.group + end + end + end + end +end + +::Types::Namespaces::LinkPaths::ProjectNamespaceLinksType.prepend_mod diff --git a/app/graphql/types/namespaces/link_paths/user_namespace_links_type.rb b/app/graphql/types/namespaces/link_paths/user_namespace_links_type.rb new file mode 100644 index 00000000000..9092a799f02 --- /dev/null +++ b/app/graphql/types/namespaces/link_paths/user_namespace_links_type.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Types + module Namespaces + module LinkPaths + class UserNamespaceLinksType < BaseObject # rubocop:disable Graphql/AuthorizeTypes -- parent is already authorized + graphql_name 'UserNamespaceLinks' + implements ::Types::Namespaces::LinkPaths + end + end + end +end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index ea7ea845647..2699fa221b7 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -408,7 +408,7 @@ module IssuablesHelper def new_comment_template_paths(group, project = nil) [{ text: _('Your comment templates'), - href: profile_comment_templates_path + href: ::Gitlab::Routing.url_helpers.profile_comment_templates_path }] end end diff --git a/app/services/container_registry/protection/concerns/tag_rule.rb b/app/services/container_registry/protection/concerns/tag_rule.rb new file mode 100644 index 00000000000..a5719924987 --- /dev/null +++ b/app/services/container_registry/protection/concerns/tag_rule.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ContainerRegistry + module Protection + module Concerns + module TagRule + extend ActiveSupport::Concern + + private + + def protected_patterns_for_delete(project:, current_user: nil) + tag_rules = ContainerRegistry::Protection::TagRule.tag_name_patterns_for_project(project.id) + + if current_user + return if current_user.can_admin_all_resources? + + user_access_level = project.team.max_member_access(current_user.id) + tag_rules = tag_rules.for_delete_and_access(user_access_level) + end + + return if tag_rules.blank? + + tag_rules.map { |rule| ::Gitlab::UntrustedRegexp.new(rule.tag_name_pattern) } + end + end + end + end +end diff --git a/app/services/projects/container_repository/cleanup_tags_base_service.rb b/app/services/projects/container_repository/cleanup_tags_base_service.rb index b9d52f5dbb2..1f0eac0c735 100644 --- a/app/services/projects/container_repository/cleanup_tags_base_service.rb +++ b/app/services/projects/container_repository/cleanup_tags_base_service.rb @@ -5,6 +5,8 @@ module Projects class CleanupTagsBaseService < BaseContainerRepositoryService private + include ContainerRegistry::Protection::Concerns::TagRule + def filter_out_latest!(tags) return unless keep_latest @@ -22,18 +24,8 @@ module Projects end def filter_out_protected!(tags) - tag_rules = ::ContainerRegistry::Protection::TagRule.tag_name_patterns_for_project(project.id) - - if current_user - return if current_user.can_admin_all_resources? - - user_access_level = project.team.max_member_access(current_user.id) - tag_rules = tag_rules.for_delete_and_access(user_access_level) - end - - return if tag_rules.blank? - - patterns = tag_rules.map { |rule| ::Gitlab::UntrustedRegexp.new(rule.tag_name_pattern) } + patterns = protected_patterns_for_delete(project:, current_user:) + return if patterns.blank? tags.reject! do |tag| patterns.detect do |pattern| diff --git a/app/services/projects/container_repository/gitlab/delete_tags_service.rb b/app/services/projects/container_repository/gitlab/delete_tags_service.rb index ed7f3326f6a..5bdf61da6c6 100644 --- a/app/services/projects/container_repository/gitlab/delete_tags_service.rb +++ b/app/services/projects/container_repository/gitlab/delete_tags_service.rb @@ -7,6 +7,7 @@ module Projects include BaseServiceUtility include ::Gitlab::Utils::StrongMemoize include ::Projects::ContainerRepository::Gitlab::Timeoutable + include ContainerRegistry::Protection::Concerns::TagRule PROTECTED_TAGS_ERROR_MESSAGE = 'cannot delete protected tag(s)' @@ -52,18 +53,8 @@ module Projects end def filter_out_protected! - tag_rules = ::ContainerRegistry::Protection::TagRule.tag_name_patterns_for_project(project.id) - - if current_user - return if current_user.can_admin_all_resources? - - user_access_level = project.team.max_member_access(current_user.id) - tag_rules = tag_rules.for_delete_and_access(user_access_level) - end - - return if tag_rules.blank? - - patterns = tag_rules.map { |rule| ::Gitlab::UntrustedRegexp.new(rule.tag_name_pattern) } + patterns = protected_patterns_for_delete(project:, current_user:) + return if patterns.blank? tag_names.reject! do |tag_name| patterns.detect do |pattern| diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb index 7855f399c8f..d024cb651a1 100644 --- a/config/initializers/7_prometheus_metrics.rb +++ b/config/initializers/7_prometheus_metrics.rb @@ -99,9 +99,19 @@ Gitlab::Cluster::LifecycleEvents.on_worker_start do end if Gitlab::Runtime.sidekiq? - Gitlab::Metrics::Samplers::ConcurrencyLimitSampler.instance(logger: logger).start - Gitlab::Metrics::Samplers::StatActivitySampler.instance(logger: logger).start - Gitlab::Metrics::Samplers::GlobalSearchSampler.instance(logger: logger).start if Gitlab.ee? + @samplers_started = false + + Rails.application.config.after_routes_loaded do + # Rails will reload this hook every time routes are changed. + unless @samplers_started + # These samplers may attempt to retrieve database connections (e.g. for feature flag checks) + # in the background, so wait until all the code is loaded before starting. + Gitlab::Metrics::Samplers::ConcurrencyLimitSampler.instance(logger: logger).start + Gitlab::Metrics::Samplers::StatActivitySampler.instance(logger: logger).start + Gitlab::Metrics::Samplers::GlobalSearchSampler.instance(logger: logger).start if Gitlab.ee? + @samplers_started = true + end + end end Gitlab::Ci::Parsers.instrument! diff --git a/db/docs/batched_background_migrations/backfill_resource_link_events_namespace_id.yml b/db/docs/batched_background_migrations/backfill_resource_link_events_namespace_id.yml index 3571385de89..1d2e4a0df03 100644 --- a/db/docs/batched_background_migrations/backfill_resource_link_events_namespace_id.yml +++ b/db/docs/batched_background_migrations/backfill_resource_link_events_namespace_id.yml @@ -5,4 +5,4 @@ feature_category: team_planning introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/174402 milestone: '17.7' queued_migration_version: 20241202141411 -finalized_by: # version of the migration that finalized this BBM +finalized_by: '20250511231623' diff --git a/db/post_migrate/20250511231623_finalize_hk_backfill_resource_link_events_namespace_id.rb b/db/post_migrate/20250511231623_finalize_hk_backfill_resource_link_events_namespace_id.rb new file mode 100644 index 00000000000..6b01ebf6a9d --- /dev/null +++ b/db/post_migrate/20250511231623_finalize_hk_backfill_resource_link_events_namespace_id.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class FinalizeHkBackfillResourceLinkEventsNamespaceId < Gitlab::Database::Migration[2.3] + milestone '18.0' + + disable_ddl_transaction! + + restrict_gitlab_migration gitlab_schema: :gitlab_main_cell + + def up + ensure_batched_background_migration_is_finished( + job_class_name: 'BackfillResourceLinkEventsNamespaceId', + table_name: :resource_link_events, + column_name: :id, + job_arguments: [:namespace_id, :issues, :namespace_id, :issue_id], + finalize: true + ) + end + + def down; end +end diff --git a/db/schema_migrations/20250511231623 b/db/schema_migrations/20250511231623 new file mode 100644 index 00000000000..30889a81aac --- /dev/null +++ b/db/schema_migrations/20250511231623 @@ -0,0 +1 @@ +54cc277ec4f8dd21e5627c0d5d1bfd42dca452e40675bec5e55a0045c338ce4f \ No newline at end of file diff --git a/doc/administration/get_started.md b/doc/administration/get_started.md index 2914070a69c..2e31644844d 100644 --- a/doc/administration/get_started.md +++ b/doc/administration/get_started.md @@ -170,8 +170,6 @@ All backups are encrypted. After 90 days, backups are deleted. - Labels - Additional items -For more information about GitLab SaaS backups, see our [Backup FAQ page](https://handbook.gitlab.com/handbook/engineering/infrastructure/faq/#gitlabcom-backups). - {{< alert type="note" >}} You should not use [direct transfer](../user/group/import/_index.md) or @@ -306,7 +304,6 @@ You can learn more about how to administer GitLab. ### Paid GitLab training - GitLab education services: Learn more about [GitLab and DevOps best practices](https://about.gitlab.com/services/education/) through our specialized training courses. See our full course catalog. -- GitLab technical certifications: Explore our [certification options](https://handbook.gitlab.com/handbook/customer-success/professional-services-engineering/gitlab-technical-certifications/) that focus on key GitLab and DevOps skills. ### Free GitLab training diff --git a/doc/api/discussions.md b/doc/api/discussions.md index b6b0af10821..2a29b8d3b15 100644 --- a/doc/api/discussions.md +++ b/doc/api/discussions.md @@ -502,7 +502,7 @@ curl --request DELETE \ The Epics REST API was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/460668) in GitLab 17.0 and is planned for removal in v5 of the API. In GitLab 17.4 or later, if [the new look for epics](../user/group/epics/epic_work_items.md) is enabled, use the -[Work Items API](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/work_items/) instead. For more information, see the [guide how to migrate your existing APIs](graphql/epic_work_items_api_migration_guide.md). +Work Items API instead. For more information, see the [guide how to migrate your existing APIs](graphql/epic_work_items_api_migration_guide.md). This change is a breaking change. {{< /alert >}} diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md index 7740b54d20f..1af8364c1dc 100644 --- a/doc/api/graphql/reference/_index.md +++ b/doc/api/graphql/reference/_index.md @@ -10054,6 +10054,30 @@ Input type: `RestorePagesDeploymentInput` | `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | | `pagesDeployment` | [`PagesDeployment!`](#pagesdeployment) | Restored Pages Deployment. | +### `Mutation.runnerAssignToProject` + +{{< details >}} +**Introduced** in GitLab 18.1. +**Status**: Experiment. +{{< /details >}} + +Input type: `RunnerAssignToProjectInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| `projectPath` | [`ID!`](#id) | Full path of the project to which the runner will be assigned. | +| `runnerId` | [`CiRunnerID!`](#cirunnerid) | ID of the runner to assign to the project . | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | + ### `Mutation.runnerBulkPause` {{< details >}} @@ -10152,6 +10176,30 @@ Input type: `RunnerDeleteInput` | `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +### `Mutation.runnerUnassignFromProject` + +{{< details >}} +**Introduced** in GitLab 18.1. +**Status**: Experiment. +{{< /details >}} + +Input type: `RunnerUnassignFromProjectInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| `projectPath` | [`ID!`](#id) | Full path of the project from which the runner will be unassigned. | +| `runnerId` | [`CiRunnerID!`](#cirunnerid) | ID of the runner to unassign from the project. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | + ### `Mutation.runnerUpdate` Input type: `RunnerUpdateInput` @@ -27678,6 +27726,7 @@ GPG signature for a signed commit. | `isAdjournedDeletionEnabled` {{< icon name="warning-solid" >}} | [`Boolean!`](#boolean) | **Introduced** in GitLab 16.11. **Status**: Experiment. Indicates if delayed group deletion is enabled. | | `isLinkedToSubscription` | [`Boolean`](#boolean) | Indicates if group is linked to a subscription. | | `lfsEnabled` | [`Boolean`](#boolean) | Indicates if Large File Storage (LFS) is enabled for namespace. | +| `linkPaths` {{< icon name="warning-solid" >}} | [`NamespacesLinkPaths`](#namespaceslinkpaths) | **Introduced** in GitLab 18.1. **Status**: Experiment. Namespace relevant paths to create links on the UI. | | `lockDuoFeaturesEnabled` {{< icon name="warning-solid" >}} | [`Boolean`](#boolean) | **Introduced** in GitLab 16.10. **Status**: Experiment. Indicates if the GitLab Duo features enabled setting is enforced for all subgroups. | | `lockMathRenderingLimitsEnabled` | [`Boolean`](#boolean) | Indicates if math rendering limits are locked for all descendant groups. | | `markedForDeletionOn` {{< icon name="warning-solid" >}} | [`Time`](#time) | **Introduced** in GitLab 16.11. **Status**: Experiment. Date when group was scheduled to be deleted. | @@ -29603,6 +29652,23 @@ Limited group data accessible to users without full group read access (e.g. non- | `name` | [`String!`](#string) | Name of the group. | | `webUrl` | [`String`](#string) | Web URL of the group. | +### `GroupNamespaceLinks` + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `epicsList` | [`String`](#string) | Namespace epics_list. | +| `groupIssues` | [`String`](#string) | Namespace group_issues. | +| `issuesList` | [`String`](#string) | Namespace issues_list. | +| `labelsFetch` | [`String`](#string) | Namespace labels_fetch. | +| `labelsManage` | [`String`](#string) | Namespace labels_manage. | +| `newCommentTemplate` | [`String`](#string) | Namespace new_comment_template_paths. | +| `newProject` | [`String`](#string) | Namespace new_project. | +| `register` | [`String`](#string) | Namespace register_path. | +| `reportAbuse` | [`String`](#string) | Namespace report_abuse. | +| `signIn` | [`String`](#string) | Namespace sign_in_path. | + ### `GroupPermissions` #### Fields @@ -33156,6 +33222,7 @@ Product analytics events for a specific month and year. | `fullPath` | [`ID!`](#id) | Full path of the namespace. | | `id` | [`ID!`](#id) | ID of the namespace. | | `lfsEnabled` | [`Boolean`](#boolean) | Indicates if Large File Storage (LFS) is enabled for namespace. | +| `linkPaths` {{< icon name="warning-solid" >}} | [`NamespacesLinkPaths`](#namespaceslinkpaths) | **Introduced** in GitLab 18.1. **Status**: Experiment. Namespace relevant paths to create links on the UI. | | `name` | [`String!`](#string) | Name of the namespace. | | `packageSettings` | [`PackageSettings`](#packagesettings) | Package settings for the namespace. | | `path` | [`String!`](#string) | Path of the namespace. | @@ -37513,6 +37580,23 @@ Returns [`UserMergeRequestInteraction`](#usermergerequestinteraction). | `nameWithNamespace` | [`String!`](#string) | Name of the project including the namespace. | | `webUrl` | [`String`](#string) | Web URL of the project. | +### `ProjectNamespaceLinks` + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `epicsList` | [`String`](#string) | Namespace epics_list. | +| `groupIssues` | [`String`](#string) | Namespace group_issues. | +| `issuesList` | [`String`](#string) | Namespace issues_list. | +| `labelsFetch` | [`String`](#string) | Namespace labels_fetch. | +| `labelsManage` | [`String`](#string) | Namespace labels_manage. | +| `newCommentTemplate` | [`String`](#string) | Namespace new_comment_template_paths. | +| `newProject` | [`String`](#string) | Namespace new_project. | +| `register` | [`String`](#string) | Namespace register_path. | +| `reportAbuse` | [`String`](#string) | Namespace report_abuse. | +| `signIn` | [`String`](#string) | Namespace sign_in_path. | + ### `ProjectPermissions` #### Fields @@ -40280,6 +40364,23 @@ fields relate to interactions between the two entities. | `reviewState` | [`MergeRequestReviewState`](#mergerequestreviewstate) | State of the review by the user. | | `reviewed` | [`Boolean!`](#boolean) | Whether the user has provided a review for the merge request. | +### `UserNamespaceLinks` + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `epicsList` | [`String`](#string) | Namespace epics_list. | +| `groupIssues` | [`String`](#string) | Namespace group_issues. | +| `issuesList` | [`String`](#string) | Namespace issues_list. | +| `labelsFetch` | [`String`](#string) | Namespace labels_fetch. | +| `labelsManage` | [`String`](#string) | Namespace labels_manage. | +| `newCommentTemplate` | [`String`](#string) | Namespace new_comment_template_paths. | +| `newProject` | [`String`](#string) | Namespace new_project. | +| `register` | [`String`](#string) | Namespace register_path. | +| `reportAbuse` | [`String`](#string) | Namespace report_abuse. | +| `signIn` | [`String`](#string) | Namespace sign_in_path. | + ### `UserPermissions` #### Fields @@ -48099,6 +48200,29 @@ Returns [`UserMergeRequestInteraction`](#usermergerequestinteraction). | ---- | ---- | ----------- | | `id` | [`MergeRequestID!`](#mergerequestid) | Global ID of the merge request. | +#### `NamespacesLinkPaths` + +Implementations: + +- [`GroupNamespaceLinks`](#groupnamespacelinks) +- [`ProjectNamespaceLinks`](#projectnamespacelinks) +- [`UserNamespaceLinks`](#usernamespacelinks) + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `epicsList` | [`String`](#string) | Namespace epics_list. | +| `groupIssues` | [`String`](#string) | Namespace group_issues. | +| `issuesList` | [`String`](#string) | Namespace issues_list. | +| `labelsFetch` | [`String`](#string) | Namespace labels_fetch. | +| `labelsManage` | [`String`](#string) | Namespace labels_manage. | +| `newCommentTemplate` | [`String`](#string) | Namespace new_comment_template_paths. | +| `newProject` | [`String`](#string) | Namespace new_project. | +| `register` | [`String`](#string) | Namespace register_path. | +| `reportAbuse` | [`String`](#string) | Namespace report_abuse. | +| `signIn` | [`String`](#string) | Namespace sign_in_path. | + #### `NoteableInterface` Implementations: diff --git a/doc/editor_extensions/eclipse/_index.md b/doc/editor_extensions/eclipse/_index.md index 3c4d80c3d8a..f743ba3550a 100644 --- a/doc/editor_extensions/eclipse/_index.md +++ b/doc/editor_extensions/eclipse/_index.md @@ -46,6 +46,5 @@ Use the `Bug` or `Feature Proposal` template. - [Code Suggestions](../../user/project/repository/code_suggestions/_index.md) - [Eclipse troubleshooting](troubleshooting.md) - [GitLab Language Server documentation](../language_server/_index.md) -- [About the Create:Editor Extensions Group](https://handbook.gitlab.com/handbook/engineering/development/dev/create/editor-extensions/) - [Open issues for this plugin](https://gitlab.com/gitlab-org/editor-extensions/gitlab-eclipse-plugin/-/issues/) - [View source code](https://gitlab.com/gitlab-org/editor-extensions/gitlab-eclipse-plugin) diff --git a/doc/editor_extensions/jetbrains_ide/_index.md b/doc/editor_extensions/jetbrains_ide/_index.md index 19d5ca14bc9..5909fdf2210 100644 --- a/doc/editor_extensions/jetbrains_ide/_index.md +++ b/doc/editor_extensions/jetbrains_ide/_index.md @@ -98,7 +98,6 @@ built-in error reporting tool: - [Code Suggestions](../../user/project/repository/code_suggestions/_index.md) - [JetBrains troubleshooting](jetbrains_troubleshooting.md) - [GitLab Language Server documentation](../language_server/_index.md) -- [About the Create:Editor Extensions Group](https://handbook.gitlab.com/handbook/engineering/development/dev/create/editor-extensions/) - [Open issues for this plugin](https://gitlab.com/gitlab-org/editor-extensions/gitlab-jetbrains-plugin/-/issues/) - [Plugin documentation](https://gitlab.com/gitlab-org/editor-extensions/gitlab-jetbrains-plugin/-/blob/main/README.md) - [View source code](https://gitlab.com/gitlab-org/editor-extensions/gitlab-jetbrains-plugin) diff --git a/doc/editor_extensions/visual_studio/_index.md b/doc/editor_extensions/visual_studio/_index.md index d84bbf5e5b0..eac80b72088 100644 --- a/doc/editor_extensions/visual_studio/_index.md +++ b/doc/editor_extensions/visual_studio/_index.md @@ -27,7 +27,6 @@ To update your extension to the latest version: ## Related topics -- [About the Create:Editor Extensions Group](https://handbook.gitlab.com/handbook/engineering/development/dev/create/editor-extensions/) - [Open issues for this plugin](https://gitlab.com/gitlab-org/editor-extensions/gitlab-visual-studio-extension/-/issues/) - [View source code](https://gitlab.com/gitlab-org/editor-extensions/gitlab-visual-studio-extension) - [GitLab Language Server documentation](../language_server/_index.md) diff --git a/doc/integration/advanced_search/elasticsearch.md b/doc/integration/advanced_search/elasticsearch.md index 25c8f83a82a..fe191a5808b 100644 --- a/doc/integration/advanced_search/elasticsearch.md +++ b/doc/integration/advanced_search/elasticsearch.md @@ -82,8 +82,6 @@ Advanced search works with the following versions of Elasticsearch. | GitLab 14.0 to 14.10 | Elasticsearch 6.8 to 7.x | Advanced search follows the [Elasticsearch end-of-life policy](https://www.elastic.co/support/eol). -When we change Elasticsearch supported versions in GitLab, we announce them in [deprecation notes](https://handbook.gitlab.com/handbook/marketing/blog/release-posts/#update-the-deprecations-doc) in monthly release posts -before we remove them. #### OpenSearch diff --git a/doc/solutions/cloud/aws/gitaly_sre_for_aws.md b/doc/solutions/cloud/aws/gitaly_sre_for_aws.md index 05fc15c4bbb..859f715643c 100644 --- a/doc/solutions/cloud/aws/gitaly_sre_for_aws.md +++ b/doc/solutions/cloud/aws/gitaly_sre_for_aws.md @@ -46,7 +46,7 @@ All recommendations are for production configurations, including performance tes #### Overall recommendations -- Production-grade Gitaly must be implemented on instance compute due to all of the above and below characteristics. +- Production-grade Gitaly must be implemented on instance compute due to all of the previous and following characteristics. - Never use [burstable instance types](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances.html) (such as `t2`, `t3`, `t4g`) for Gitaly. - Always use at least the [AWS Nitro generation of instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html#ec2-nitro-instances) to ensure many of the below concerns are automatically handled. - Use Amazon Linux 2 to ensure that all [AWS oriented hardware and OS optimizations](https://aws.amazon.com/amazon-linux-2/faqs/) are maximized without additional configuration or SRE management. diff --git a/doc/tutorials/reviews/_index.md b/doc/tutorials/reviews/_index.md index 8e4f65a5d5b..ef05f121345 100644 --- a/doc/tutorials/reviews/_index.md +++ b/doc/tutorials/reviews/_index.md @@ -383,6 +383,4 @@ After you provide your feedback, tidy up. ## Related topics - [Conventional comments](https://conventionalcomments.org/) provide helpful structure for comments. -- [Code review guidelines](https://handbook.gitlab.com/handbook/engineering/workflow/code-review/) in the GitLab handbook -- [Merge request coaches](https://handbook.gitlab.com/job-families/expert/merge-request-coach/) in the GitLab handbook - [Efficient code review tips](https://about.gitlab.com/blog/2020/09/08/efficient-code-review-tips/) diff --git a/doc/update/terminology.md b/doc/update/terminology.md index 99c1359b730..36fb5928b85 100644 --- a/doc/update/terminology.md +++ b/doc/update/terminology.md @@ -14,7 +14,7 @@ title: Deprecation terms - Begins after a deprecation announcement outlining an end-of-support or removal date. - Ends after the end-of-support date or removal date has passed. -## End of Support +## End of support - Optional step before removal. - Feature usage strongly discouraged. @@ -23,7 +23,7 @@ title: Deprecation terms - Will be removed in a future major release. - Begins after an end-of-support date has passed. -[Announcing an End of Support period](https://handbook.gitlab.com/handbook/marketing/blog/release-posts/#announcing-an-end-of-support-period) +Announcing an end-of-support period should only be used in special circumstances and is not recommended for general use. Most features should be deprecated and then removed. diff --git a/doc/user/gitlab_com/_index.md b/doc/user/gitlab_com/_index.md index 43203f3148b..0ffef97ba46 100644 --- a/doc/user/gitlab_com/_index.md +++ b/doc/user/gitlab_com/_index.md @@ -49,8 +49,6 @@ this limit. Repository limits apply to both public and private projects. ## Backups -[See our backup strategy](https://handbook.gitlab.com/handbook/engineering/infrastructure/production/#backups). - To back up an entire project on GitLab.com, you can export it: - [Through the UI](../project/settings/import_export.md). @@ -506,9 +504,7 @@ More details are available on the rate limits for GitLab can rate-limit requests at several layers. The rate limits listed here are configured in the application. These limits are the most -restrictive for each IP address. For more information about the rate limits -for GitLab.com, see -[the documentation in the handbook](https://handbook.gitlab.com/handbook/engineering/infrastructure/rate-limiting). +restrictive for each IP address. ### Group and project import by uploading export files diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 0308802ae02..bd836fdd8d5 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -27878,9 +27878,15 @@ msgstr "" msgid "GetStarted|Download the extension to access GitLab features and GitLab Duo AI capabilities to handle everyday tasks." msgstr "" +msgid "GetStarted|There was a problem trying to end the tutorial. Please try again." +msgstr "" + msgid "GetStarted|Use GitLab Duo locally" msgstr "" +msgid "GetStarted|You've ended the tutorial." +msgstr "" + msgid "GiB" msgstr "" @@ -35866,6 +35872,9 @@ msgstr "" msgid "LearnGitLab|Enable require merge approvals" msgstr "" +msgid "LearnGitLab|End tutorial" +msgstr "" + msgid "LearnGitLab|Enroll" msgstr "" diff --git a/qa/qa/support/wait_for_requests.rb b/qa/qa/support/wait_for_requests.rb index a132a5fc52e..a5dd106752b 100644 --- a/qa/qa/support/wait_for_requests.rb +++ b/qa/qa/support/wait_for_requests.rb @@ -24,7 +24,7 @@ module QA end script = requests.join(' || ') - Capybara.page.evaluate_script(script).zero? # rubocop:disable Style/NumericPredicate + Capybara.page.evaluate_script(script).to_i == 0 end def spinner_exists? @@ -32,11 +32,6 @@ module QA end def finished_loading?(wait: DEFAULT_MAX_WAIT_TIME) - # The number of selectors should be able to be reduced after - # migration to the new spinner is complete. - # https://gitlab.com/groups/gitlab-org/-/epics/956 - # retry_on_exception added here due to `StaleElementReferenceError`. See: https://gitlab.com/gitlab-org/gitlab/-/issues/232485 - Capybara.page.has_no_css?('.gl-spinner', wait: wait) rescue Selenium::WebDriver::Error::StaleElementReferenceError => e QA::Runtime::Logger.error(".gl-spinner reference has become stale: #{e}") diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb index ec9e5b5d510..fed96db7403 100644 --- a/qa/spec/page/logging_spec.rb +++ b/qa/spec/page/logging_spec.rb @@ -14,6 +14,8 @@ RSpec.describe QA::Support::Page::Logging do allow(page).to receive(:find).and_return(page) allow(page).to receive(:current_url).and_return('http://current-url') allow(page).to receive(:has_css?).with(any_args).and_return(true) + allow(subject).to receive(:wait_for_requests).and_return(true) + allow(QA::Support::PageErrorChecker).to receive(:check_page_for_error_code).and_return(0) end diff --git a/qa/tasks/ci.rake b/qa/tasks/ci.rake index d9b71a15c2d..7c8fb9abfef 100644 --- a/qa/tasks/ci.rake +++ b/qa/tasks/ci.rake @@ -77,16 +77,23 @@ namespace :ci do pipelines_for_selective_improved = [:test_on_gdk] logger.warn("*** Recreating #{pipelines_for_selective_improved} using spec list based on coverage mappings ***") tests_from_mapping = qa_changes.qa_tests(from_code_path_mapping: true) - properties = { - label: tests_from_mapping.nil? || tests_from_mapping.empty? ? 'non-selective' : 'selective', - value: tests_from_mapping.nil? || tests_from_mapping.empty? ? 0 : tests_from_mapping.count - } - Tooling::Events::TrackPipelineEvents.new( - event_name: "e2e_tests_selected_for_execution_gitlab_pipeline", - properties: properties - ).send_event + logger.info("Following specs were selected for execution: '#{tests_from_mapping}'") - QA::Tools::Ci::PipelineCreator.new(tests_from_mapping, **creator_args).create(pipelines_for_selective_improved) + begin + QA::Tools::Ci::PipelineCreator.new(tests_from_mapping, **creator_args).create(pipelines_for_selective_improved) + properties = { + label: tests_from_mapping.nil? || tests_from_mapping.empty? ? 'non-selective' : 'selective', + value: tests_from_mapping.nil? || tests_from_mapping.empty? ? 0 : tests_from_mapping.count + } + Tooling::Events::TrackPipelineEvents.new( + event_name: "e2e_tests_selected_for_execution_gitlab_pipeline", + properties: properties + ).send_event + rescue StandardError => e + logger.warn("*** Error while creating pipeline with selected specs: #{e.backtrace} ****") + logger.info("*** Running full suite ***") + QA::Tools::Ci::PipelineCreator.new([], **creator_args).create + end end desc "Export test run metrics to influxdb" diff --git a/spec/frontend/work_items/components/create_work_item_spec.js b/spec/frontend/work_items/components/create_work_item_spec.js index 9414e12d682..ca58112b84b 100644 --- a/spec/frontend/work_items/components/create_work_item_spec.js +++ b/spec/frontend/work_items/components/create_work_item_spec.js @@ -2,6 +2,7 @@ import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import { GlAlert, GlButton, GlFormSelect, GlLink, GlSprintf } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; +import { cloneDeep } from 'lodash'; import namespaceWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/project_namespace_work_item_types.query.graphql.json'; import { setHTMLFixture } from 'helpers/fixtures'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; @@ -59,9 +60,9 @@ describe('Create work item component', () => { const mutationErrorHandler = jest.fn().mockResolvedValue(createWorkItemMutationErrorResponse); const errorHandler = jest.fn().mockRejectedValue('Houston, we have a problem'); const workItemQuerySuccessHandler = jest.fn().mockResolvedValue(createWorkItemQueryResponse()); - const namespaceWorkItemTypesHandler = jest - .fn() - .mockResolvedValue(namespaceWorkItemTypesQueryResponse); + const namespaceWorkItemTypes = + namespaceWorkItemTypesQueryResponse.data.workspace.workItemTypes.nodes; + const { webUrl: namespaceWebUrl } = namespaceWorkItemTypesQueryResponse.data.workspace; const findFormTitle = () => wrapper.find('h1'); const findAlert = () => wrapper.findComponent(GlAlert); @@ -86,14 +87,20 @@ describe('Create work item component', () => { const findResolveDiscussionLink = () => wrapper.find('[data-testid="work-item-resolve-discussion"]').findComponent(GlLink); - const namespaceWorkItemTypes = - namespaceWorkItemTypesQueryResponse.data.workspace.workItemTypes.nodes; - const createComponent = ({ props = {}, mutationHandler = createWorkItemSuccessHandler, preselectedWorkItemType = WORK_ITEM_TYPE_NAME_EPIC, + isGroupWorkItem = false, } = {}) => { + const namespaceResponseCopy = cloneDeep(namespaceWorkItemTypesQueryResponse); + namespaceResponseCopy.data.workspace.id = 'gid://gitlab/Group/33'; + const namespaceResponse = isGroupWorkItem + ? namespaceResponseCopy + : namespaceWorkItemTypesQueryResponse; + + const namespaceWorkItemTypesHandler = jest.fn().mockResolvedValue(namespaceResponse); + mockApollo = createMockApollo( [ [workItemByIidQuery, workItemQuerySuccessHandler], @@ -898,4 +905,18 @@ describe('Create work item component', () => { }); }); }); + + it.each` + isGroupWorkItem | uploadsPath + ${true} | ${`${namespaceWebUrl}/-/uploads`} + ${false} | ${`${namespaceWebUrl}/uploads`} + `( + 'passes correct uploads path for markdown editor when isGroupWorkItem is $isGroupWorkItem', + async ({ isGroupWorkItem, uploadsPath }) => { + createComponent({ isGroupWorkItem }); + await waitForPromises(); + + expect(findDescriptionWidget().props('uploadsPath')).toBe(uploadsPath); + }, + ); }); diff --git a/spec/graphql/types/namespaces/link_paths/group_namespace_links_type_spec.rb b/spec/graphql/types/namespaces/link_paths/group_namespace_links_type_spec.rb new file mode 100644 index 00000000000..8138df5a800 --- /dev/null +++ b/spec/graphql/types/namespaces/link_paths/group_namespace_links_type_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Types::Namespaces::LinkPaths::GroupNamespaceLinksType, feature_category: :shared do + it_behaves_like 'expose all link paths fields for the namespace' +end diff --git a/spec/graphql/types/namespaces/link_paths/project_namespace_links_type_spec.rb b/spec/graphql/types/namespaces/link_paths/project_namespace_links_type_spec.rb new file mode 100644 index 00000000000..c733097f053 --- /dev/null +++ b/spec/graphql/types/namespaces/link_paths/project_namespace_links_type_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Types::Namespaces::LinkPaths::ProjectNamespaceLinksType, feature_category: :shared do + it_behaves_like 'expose all link paths fields for the namespace' +end diff --git a/spec/graphql/types/namespaces/link_paths/user_namespace_links_type_spec.rb b/spec/graphql/types/namespaces/link_paths/user_namespace_links_type_spec.rb new file mode 100644 index 00000000000..238cc7b1ec5 --- /dev/null +++ b/spec/graphql/types/namespaces/link_paths/user_namespace_links_type_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Types::Namespaces::LinkPaths::UserNamespaceLinksType, feature_category: :shared do + it_behaves_like 'expose all link paths fields for the namespace' +end diff --git a/spec/graphql/types/namespaces/link_paths_spec.rb b/spec/graphql/types/namespaces/link_paths_spec.rb new file mode 100644 index 00000000000..bea5bc3865e --- /dev/null +++ b/spec/graphql/types/namespaces/link_paths_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Types::Namespaces::LinkPaths, feature_category: :shared do + include GraphqlHelpers + using RSpec::Parameterized::TableSyntax + + where(:namespace_class, :namespace_type_name) do + ::Group | ::Types::Namespaces::LinkPaths::GroupNamespaceLinksType + ::Namespaces::ProjectNamespace | ::Types::Namespaces::LinkPaths::ProjectNamespaceLinksType + ::Namespaces::UserNamespace | ::Types::Namespaces::LinkPaths::UserNamespaceLinksType + end + + with_them do + describe ".resolve_type" do + it 'knows the correct type for objects' do + namespace = namespace_class.new + + expect(described_class.resolve_type(namespace, {})) + .to eq(namespace_type_name) + end + end + + describe '.orphan_types' do + it 'includes the type' do + expect(described_class.orphan_types).to include(namespace_type_name) + end + end + end + + it 'raises an error for an unknown type' do + namespace = build(:project) + + expect { described_class.resolve_type(namespace, {}) } + .to raise_error("Unknown GraphQL type for namespace type #{namespace.class}") + end + + it_behaves_like 'expose all link paths fields for the namespace' +end diff --git a/spec/requests/api/graphql/mutations/ci/runner/assign_to_project_spec.rb b/spec/requests/api/graphql/mutations/ci/runner/assign_to_project_spec.rb new file mode 100644 index 00000000000..57b5fa5fd25 --- /dev/null +++ b/spec/requests/api/graphql/mutations/ci/runner/assign_to_project_spec.rb @@ -0,0 +1,188 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Ci::Runner::AssignToProject, feature_category: :runner do + include GraphqlHelpers + + let_it_be(:group_owner) { create(:user) } + let_it_be(:project_owner) { create(:user) } + let_it_be(:group) { create(:group, owners: group_owner) } + let_it_be(:project) { create(:project, namespace: group, owners: project_owner) } + let_it_be(:project2) { create(:project, namespace: group, owners: project_owner) } + let_it_be(:project_with_org) { create(:project, organization: create(:organization), owners: project_owner) } + let_it_be(:runner) { create(:ci_runner, :project, projects: [project2]) } + let_it_be(:admin) { create(:admin) } + let_it_be(:non_accessible_user) { create(:user) } + + let(:mutation_params) do + { + project_path: project.full_path, + runner_id: runner.to_global_id + } + end + + let(:mutation) do + graphql_mutation( + :runner_assign_to_project, + mutation_params, + 'errors' + ) + end + + let(:mutation_response) { graphql_mutation_response(:runner_assign_to_project) } + + specify { expect(described_class).to require_graphql_authorizations(:assign_runner) } + + context 'with invalid parameters' do + context 'when project_path is not given' do + let(:mutation_params) do + { + runner_id: runner.to_global_id + } + end + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: group_owner) + expect_graphql_errors_to_include("invalid value for projectPath") + end + end + + context 'when project_path is invalid' do + let(:mutation_params) do + { + runner_id: runner.to_global_id, + project_path: 'non/existing/project/path' + } + end + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: group_owner) + expect_graphql_errors_to_include("The resource that you are attempting to access does not exist or you " \ + "don't have permission to perform this action") + end + end + + context 'when runner_id is invalid' do + let(:mutation_params) do + { + runner_id: "gid://gitlab/Ci::Runner/#{non_existing_record_id}", + project_path: project.full_path + } + end + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: group_owner) + expect_graphql_errors_to_include("The resource that you are attempting to access does not exist or you " \ + "don't have permission to perform this action") + end + end + + context 'when runner_id is missing' do + let(:mutation_params) do + { + project_path: project.full_path + } + end + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: group_owner) + expect_graphql_errors_to_include('was provided invalid value for runnerId') + end + end + end + + context 'with runner type constraints' do + context 'when the runner is not a project runner' do + let(:runner) { create(:ci_runner, :group, groups: [group]) } + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: group_owner) + expect_graphql_errors_to_include('Runner is not a project runner') + end + end + end + + context 'with organization constraints' do + context "when project organization_id is not the same as the runner's" do + let(:mutation_params) do + { + project_path: project_with_org.full_path, + runner_id: runner.to_global_id + } + end + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: project_owner) + expect(graphql_mutation_response(:runner_assign_to_project)['errors']) + .to include("runner can only be assigned to projects in the same organization") + expect(runner.reload.projects).not_to include(project) + end + end + end + + context 'with permission checks' do + context 'when user does not have necessary permissions' do + it 'does not allow non-accessible user to assign a project to a runner' do + post_graphql_mutation(mutation, current_user: non_accessible_user) + expect_graphql_errors_to_include("The resource that you are attempting to access does not exist or you " \ + "don't have permission to perform this action") + expect(runner.reload.projects).not_to include(project) + end + end + + context 'when user has necessary permissions' do + context 'when the user is group owner' do + it 'allows accessible user to assign a project to a runner' do + post_graphql_mutation(mutation, current_user: group_owner) + expect(response).to have_gitlab_http_status(:success) + expect(runner.reload.projects).to include(project) + end + + context 'when the runner is locked' do + let_it_be(:runner) { create(:ci_runner, :project, :locked, projects: [project2]) } + + it 'returns an error' do + # this case is not explicitly handled in the mutation definition or service, + # this is handled in the policy itself (app/policies/ci/runner_policy.rb) + post_graphql_mutation(mutation, current_user: group_owner) + expect_graphql_errors_to_include("The resource that you are attempting to access does not exist or you " \ + "don't have permission to perform this action") + expect(runner.reload.projects).not_to include(project) + end + end + + context 'when the runner is already assigned to the project' do + it 'assigns the project to the runner and does not duplicate the assignment' do + post_graphql_mutation(mutation, current_user: group_owner) + expect(response).to have_gitlab_http_status(:success) + expect(runner.reload.projects).to include(project) + + # Check for duplicate assignments + project_count = runner.reload.projects.count + post_graphql_mutation(mutation, current_user: group_owner) + expect(response).to have_gitlab_http_status(:success) + expect(runner.reload.projects).to include(project) + expect(runner.reload.projects.count).to eq(project_count) + end + end + end + + context 'when user is admin', :enable_admin_mode do + it 'allows accessible user to assign a project to a runner' do + post_graphql_mutation(mutation, current_user: admin) + expect(response).to have_gitlab_http_status(:success) + expect(runner.reload.projects).to include(project) + end + end + + context 'when the user is project owner' do + it 'allows accessible user to assign a project to a runner' do + post_graphql_mutation(mutation, current_user: project_owner) + expect(response).to have_gitlab_http_status(:success) + expect(runner.reload.projects).to include(project) + end + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/ci/runner/unassign_from_project_spec.rb b/spec/requests/api/graphql/mutations/ci/runner/unassign_from_project_spec.rb new file mode 100644 index 00000000000..806c631d246 --- /dev/null +++ b/spec/requests/api/graphql/mutations/ci/runner/unassign_from_project_spec.rb @@ -0,0 +1,200 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Ci::Runner::UnassignFromProject, feature_category: :runner do + include GraphqlHelpers + + let_it_be(:group_owner) { create(:user) } + let_it_be(:project_owner) { create(:user) } + let_it_be(:group) { create(:group, owners: group_owner) } + let_it_be(:owner_project) { create(:project, namespace: group, owners: project_owner) } + let_it_be(:project) { create(:project, namespace: group, owners: project_owner) } + let_it_be(:runner) { create(:ci_runner, :project, projects: [owner_project, project]) } + let_it_be(:admin) { create(:admin) } + let_it_be(:non_accessible_user) { create(:user) } + + let(:mutation_params) do + { + project_path: project.full_path, + runner_id: runner.to_global_id + } + end + + let(:mutation) do + graphql_mutation( + :runner_unassign_from_project, + mutation_params, + 'errors' + ) + end + + let(:mutation_response) { graphql_mutation_response(:runner_unassign_from_project) } + + specify { expect(described_class).to require_graphql_authorizations(:admin_project_runners) } + + context 'with invalid parameters' do + context 'when project_path is missing' do + let(:mutation_params) do + { + runner_id: runner.to_global_id + } + end + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: group_owner) + expect_graphql_errors_to_include("invalid value for projectPath") + end + end + + context 'when project_path is invalid' do + let(:mutation_params) do + { + runner_id: runner.to_global_id, + project_path: 'non/existing/project/path' + } + end + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: group_owner) + expect_graphql_errors_to_include("The resource that you are attempting to access does not exist or you " \ + "don't have permission to perform this action") + end + end + + context 'when runner_id is missing' do + let(:mutation_params) do + { + project_path: project.full_path + } + end + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: group_owner) + expect_graphql_errors_to_include('invalid value for runnerId') + end + end + + context 'when runner_id is invalid' do + let(:mutation_params) do + { + runner_id: "gid://gitlab/Ci::Runner/#{non_existing_record_id}", + project_path: project.full_path + } + end + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: group_owner) + expect_graphql_errors_to_include("Runner does not exist or is not assigned to this project") + end + end + end + + context 'with permission checks' do + context 'when user does not have necessary permissions' do + it 'does not allow non-accessible user to unassign a runner from a project' do + post_graphql_mutation(mutation, current_user: non_accessible_user) + expect_graphql_errors_to_include("The resource that you are attempting to access does not exist or you " \ + "don't have permission to perform this action") + expect(runner.reload.projects).to include(project) + end + end + + context 'when user has necessary permissions' do + context 'when the user is group owner' do + it 'allows group owner to unassign a runner from a project' do + post_graphql_mutation(mutation, current_user: group_owner) + expect(response).to have_gitlab_http_status(:success) + expect(runner.reload.projects).not_to include(project) + end + end + + context 'when user is admin', :enable_admin_mode do + it 'allows admin to unassign a runner from a project' do + post_graphql_mutation(mutation, current_user: admin) + expect(response).to have_gitlab_http_status(:success) + expect(runner.reload.projects).not_to include(project) + end + end + + context 'when the user is project owner' do + it 'allows project owner to unassign a runner from a project' do + post_graphql_mutation(mutation, current_user: project_owner) + expect(response).to have_gitlab_http_status(:success) + expect(runner.reload.projects).not_to include(project) + end + end + end + end + + context 'with runner assignment scenarios' do + context 'when the runner is not assigned to the project' do + let_it_be(:project2) { create(:project, namespace: group) } + let(:mutation_params) do + { + project_path: project2.full_path, + runner_id: runner.to_global_id + } + end + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: group_owner) + expect(response).to have_gitlab_http_status(:success) + expect_graphql_errors_to_include("Runner does not exist or is not assigned to this project") + end + end + + context 'when the runner is the last one assigned to the project' do + it 'successfully unassigns the runner' do + expect(project.runners.count).to eq(1) + post_graphql_mutation(mutation, current_user: project_owner) + expect(response).to have_gitlab_http_status(:success) + expect(project.reload.runners.count).to eq(0) + end + end + + context 'when the project has multiple runners' do + let_it_be(:another_runner) { create(:ci_runner, :project, projects: [project]) } + + it 'only unassigns the specified runner' do + expect(project.runners.count).to eq(2) + post_graphql_mutation(mutation, current_user: project_owner) + expect(response).to have_gitlab_http_status(:success) + expect(project.reload.runners.count).to eq(1) + expect(project.runners).to include(another_runner) + expect(project.runners).not_to include(runner) + end + end + + context 'when unassigning a owner project from the runner' do + let(:mutation_params) do + { + project_path: owner_project.full_path, + runner_id: runner.to_global_id + } + end + + it 'returns an error' do + post_graphql_mutation(mutation, current_user: group_owner) + expect(mutation_response['errors']).to include("You cannot unassign a runner from the owner project. " \ + "Delete the runner instead") + end + end + end + + context 'when service returns an error' do + before do + service = instance_double(::Ci::Runners::UnassignRunnerService) + result = ServiceResponse.error(message: 'Custom error message') + + allow(::Ci::Runners::UnassignRunnerService).to receive(:new).and_return(service) + allow(service).to receive(:execute).and_return(result) + end + + it 'returns the error from the service' do + post_graphql_mutation(mutation, current_user: project_owner) + expect(response).to have_gitlab_http_status(:success) + expect(mutation_response['errors']).to include('Custom error message') + end + end +end diff --git a/spec/services/container_registry/protection/concerns/tag_rule_spec.rb b/spec/services/container_registry/protection/concerns/tag_rule_spec.rb new file mode 100644 index 00000000000..95311a15131 --- /dev/null +++ b/spec/services/container_registry/protection/concerns/tag_rule_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ContainerRegistry::Protection::Concerns::TagRule, feature_category: :container_registry do + using RSpec::Parameterized::TableSyntax + + # Create a test class that includes our service concern + let(:test_class) do + Class.new do + include ContainerRegistry::Protection::Concerns::TagRule + + # Make the private method public for testing + public :protected_patterns_for_delete + end + end + + # Create an instance of the test class to use in our tests + let(:service) { test_class.new } + + describe '#protected_patterns_for_delete' do + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project) } + + subject(:tag_name_patterns) { service.protected_patterns_for_delete(project: project, current_user: current_user) } + + context 'when the project has no tag protection rules' do + it { is_expected.to be_nil } + end + + context 'when the project has tag protection rules' do + def create_rule(access_level, tag_name_pattern) + create( + :container_registry_protection_tag_rule, + project: project, + tag_name_pattern: tag_name_pattern, + minimum_access_level_for_delete: access_level + ) + end + + let_it_be(:rule1) { create_rule(:owner, 'owner_pattern') } + let_it_be(:rule2) { create_rule(:admin, 'admin_pattern') } + let_it_be(:rule3) { create_rule(:maintainer, 'maintainer_pattern') } + + context 'when current user is nil' do + let_it_be(:current_user) { nil } + let(:expected_tag_name_pattern) { [rule1, rule2, rule3].map(&:tag_name_pattern) } + + it 'returns all tag rules' do + expect(tag_name_patterns.all?(Gitlab::UntrustedRegexp)).to be(true) + expect(tag_name_patterns.map(&:source)).to match_array(expected_tag_name_pattern) + end + end + + context 'when current user is supplied' do + context 'when current user is an admin', :enable_admin_mode do + let(:current_user) { build_stubbed(:admin) } + + it { is_expected.to be_nil } + end + + where(:user_role, :expected_patterns) do + :developer | %w[admin_pattern maintainer_pattern owner_pattern] + :maintainer | %w[admin_pattern owner_pattern] + :owner | %w[admin_pattern] + end + + with_them do + before do + project.send(:"add_#{user_role}", current_user) + end + + it 'returns the tag name patterns with access levels that are above the user' do + expect(tag_name_patterns.all?(Gitlab::UntrustedRegexp)).to be(true) + expect(tag_name_patterns.map(&:source)).to match_array(expected_patterns) + end + end + end + end + end +end diff --git a/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb b/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb index de781778c9f..edb98ab9ec3 100644 --- a/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb +++ b/spec/services/projects/container_repository/gitlab/cleanup_tags_service_spec.rb @@ -87,28 +87,12 @@ RSpec.describe Projects::ContainerRepository::Gitlab::CleanupTagsService, featur delete_expectations: [%w[Bb], %w[C]] it_behaves_like 'with protected rule having pattern ^\d{1,2}-\d{1,2}-stable$', - delete_expectations: [%w[A], %w[Ba Bb], %w[C D], %w[17-8-stable]] - - context 'with admin minimum_access_level_for_delete' do - it_behaves_like 'with protected rule having pattern ^\d{1,2}-\d{1,2}-stable$', - delete_expectations: [%w[A], %w[Ba Bb], %w[C D]], - minimum_access_level_for_delete: :admin - end - - context 'without user' do - let(:user) { nil } - - it_behaves_like 'with protected rule having pattern ^\d{1,2}-\d{1,2}-stable$', - delete_expectations: [%w[A], %w[Ba Bb], %w[C D]] - end + delete_expectations: [%w[A], %w[Ba Bb], %w[C D]] context 'with the skip_protected_tags param' do - let(:params) do - { 'skip_protected_tags' => true } - end - it_behaves_like 'with protected rule having pattern ^\d{1,2}-\d{1,2}-stable$', - delete_expectations: [%w[A], %w[Ba Bb], %w[C D], %w[17-8-stable]] + delete_expectations: [%w[A], %w[Ba Bb], %w[C D], %w[17-8-stable]], + extra_params: { 'skip_protected_tags' => true } end context 'with a timeout' do @@ -174,28 +158,12 @@ RSpec.describe Projects::ContainerRepository::Gitlab::CleanupTagsService, featur delete_expectations: [%w[Ba Bb C]] it_behaves_like 'with protected rule having pattern ^\d{1,2}-\d{1,2}-stable$', - delete_expectations: [%w[A Ba Bb C D 17-8-stable]] - - context 'with admin minimum_access_level_for_delete' do - it_behaves_like 'with protected rule having pattern ^\d{1,2}-\d{1,2}-stable$', - delete_expectations: [%w[A Ba Bb C D]], - minimum_access_level_for_delete: :admin - end - - context 'without user' do - let(:user) { nil } - - it_behaves_like 'with protected rule having pattern ^\d{1,2}-\d{1,2}-stable$', - delete_expectations: [%w[A Ba Bb C D]] - end + delete_expectations: [%w[A Ba Bb C D]] context 'with the skip_protected_tags param' do - let(:params) do - { 'skip_protected_tags' => true } - end - it_behaves_like 'with protected rule having pattern ^\d{1,2}-\d{1,2}-stable$', - delete_expectations: [%w[A Ba Bb C D 17-8-stable]] + delete_expectations: [%w[A Ba Bb C D 17-8-stable]], + extra_params: { 'skip_protected_tags' => true } end end diff --git a/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb b/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb index eb281a8455b..8ce0f1e5635 100644 --- a/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb +++ b/spec/services/projects/container_repository/gitlab/delete_tags_service_spec.rb @@ -45,18 +45,18 @@ RSpec.describe Projects::ContainerRepository::Gitlab::DeleteTagsService, feature end context 'with tag protection rules' do - let_it_be(:rule1) do - create(:container_registry_protection_tag_rule, project: project, tag_name_pattern: 'A') - end - let(:tag_names) { %w[A Ba Bb C D] } + let(:protected_patterns) { nil } before do + allow(service).to receive(:protected_patterns_for_delete).and_return(protected_patterns) allow(repository.client).to receive(:supports_tag_delete?).and_return(true) stub_delete_reference_requests(tag_names) end context 'when not all tags are protected' do + let(:protected_patterns) { %w[A].map { |pattern| ::Gitlab::UntrustedRegexp.new(pattern) } } + before do expect_delete_tags(%w[Ba Bb C D]) end @@ -65,68 +65,18 @@ RSpec.describe Projects::ContainerRepository::Gitlab::DeleteTagsService, feature end context 'when all tags are protected' do - before do - create(:container_registry_protection_tag_rule, project: project, tag_name_pattern: 'B') - create(:container_registry_protection_tag_rule, project: project, tag_name_pattern: 'C') - create(:container_registry_protection_tag_rule, project: project, tag_name_pattern: 'D') - end + let(:protected_patterns) { %w[A B C D].map { |pattern| ::Gitlab::UntrustedRegexp.new(pattern) } } it { is_expected.to include(status: :error, message: 'cannot delete protected tag(s)') } end - context 'when the user has admin permissions' do - before do - allow(user).to receive(:can_admin_all_resources?).and_return(true) - end - - it 'deletes tags including protected ones' do + context 'when no tags are protected' do + it 'deletes all tags' do expect_delete_tags(tag_names) is_expected.to include(status: :success) end end - - context 'when the user has no admin permissions' do - before do - create(:container_registry_protection_tag_rule, - project: project, - tag_name_pattern: 'B', - minimum_access_level_for_delete: :maintainer) - create(:container_registry_protection_tag_rule, - project: project, - tag_name_pattern: 'C', - minimum_access_level_for_delete: :owner) - - project.add_maintainer(user) - end - - it 'applies tag protection rules based on the user access level' do - expect_delete_tags(%w[A Ba Bb D]) - - is_expected.to include(status: :success) - end - end - - context 'when there is no user, run by a cleanup policy' do - let(:user) { nil } - - before do - create(:container_registry_protection_tag_rule, - project: project, - tag_name_pattern: 'B', - minimum_access_level_for_delete: :maintainer) - create(:container_registry_protection_tag_rule, - project: project, - tag_name_pattern: 'C', - minimum_access_level_for_delete: :owner) - - expect_delete_tags(%w[D]) - end - - it 'uses all the tag protection rules' do - is_expected.to include(status: :success) - end - end end context 'with failures' do diff --git a/spec/support/shared_examples/graphql/types/namespaces/link_paths_shared_examples.rb b/spec/support/shared_examples/graphql/types/namespaces/link_paths_shared_examples.rb new file mode 100644 index 00000000000..9850980963c --- /dev/null +++ b/spec/support/shared_examples/graphql/types/namespaces/link_paths_shared_examples.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'expose all link paths fields for the namespace' do + include GraphqlHelpers + + specify do + expected_fields = %i[ + issuesList + labelsManage + newCommentTemplate + newProject + register + reportAbuse + signIn + ] + + if Gitlab.ee? + expected_fields.push(*%i[ + labelsFetch + epicsList + groupIssues + ]) + end + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/support/shared_examples/product_usage_data_collection_shared_examples.rb b/spec/support/shared_examples/product_usage_data_collection_shared_examples.rb index 703279371c9..0908c663826 100644 --- a/spec/support/shared_examples/product_usage_data_collection_shared_examples.rb +++ b/spec/support/shared_examples/product_usage_data_collection_shared_examples.rb @@ -17,6 +17,8 @@ RSpec.shared_examples 'page with product usage data collection banner' do allow(user).to receive(:dismissed_callout?).and_return(false) visit page_path + wait_for_requests + expect(page).to have_selector '[data-testid="product-usage-data-collection-banner"]' page.within('[data-testid="product-usage-data-collection-banner"]') do diff --git a/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb b/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb index 94b7fe37195..bbc4a50c7fa 100644 --- a/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb +++ b/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb @@ -195,19 +195,15 @@ RSpec.shared_examples 'when running a container_expiration_policy' do end RSpec.shared_examples 'with protected rule having pattern ^\d{1,2}-\d{1,2}-stable$' do - |delete_expectations:, service_response_extra: {}, supports_caching: false, - minimum_access_level_for_delete: :maintainer| - let_it_be(:rule) do - create( - :container_registry_protection_tag_rule, - tag_name_pattern: '^\d{1,2}-\d{1,2}-stable$', - project: project, - minimum_access_level_for_delete: minimum_access_level_for_delete - ) + |delete_expectations:, service_response_extra: {}, supports_caching: false, extra_params: {}| + + before do + patterns = [::Gitlab::UntrustedRegexp.new('^\d{1,2}-\d{1,2}-stable$')] + allow(service).to receive(:protected_patterns_for_delete).and_return(patterns) end let(:params) do - { 'name_regex_delete' => '.*' } + { 'name_regex_delete' => '.*' }.merge(extra_params) end it_behaves_like 'removing the expected tags',