Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-06-04 00:11:42 +00:00
parent 4e0a9fbb9b
commit cea1ec3ed5
65 changed files with 841 additions and 324 deletions

View File

@ -23,10 +23,6 @@ discover_duo_pro_hand_raise_lead_data:
file: ee/app/helpers/gitlab_subscriptions/hand_raise_leads_helper.rb
buy_addon_data:
file: ee/app/helpers/subscriptions_helper.rb
group_icon:
file: app/helpers/avatars_helper.rb
topic_icon:
file: app/helpers/avatars_helper.rb
can_view_namespace_catalog?:
file: app/helpers/ci/catalog/resources_helper.rb
js_ci_catalog_data:

View File

@ -570,6 +570,18 @@ Gitlab/AvoidCurrentOrganization:
- 'spec/**/*'
- 'ee/spec/**/*'
Gitlab/DisallowCurrentOrganizationIdSafeNavigation:
Description: 'Use `Current.organization.id` instead of `Current.organization&.id`. `Current.organization` is expected to be assigned.'
Enabled: true
Include:
- 'app/**/*.rb'
- 'ee/app/**/*.rb'
- 'lib/**/*.rb'
- 'ee/lib/**/*.rb'
Exclude:
# Exclude the cop's own spec file to prevent self-reporting.
- 'spec/rubocop/cop/gitlab/disallow_current_organization_id_safe_navigation_spec.rb'
# See https://gitlab.com/groups/gitlab-org/-/epics/7374
Gitlab/AvoidGitlabInstanceChecks:
Enabled: true

View File

@ -60,6 +60,10 @@
"MergeRequest",
"WorkItemWidgetCurrentUserTodos"
],
"CustomRoleInterface": [
"AdminMemberRole",
"MemberRole"
],
"DependencyInterface": [
"Dependency",
"DependencyAggregation"
@ -144,6 +148,10 @@
"PendingProjectMember",
"ProjectMember"
],
"MemberRoleInterface": [
"MemberRole",
"StandardRole"
],
"NamespaceUnion": [
"CiDeletedNamespace",
"Namespace"

View File

@ -0,0 +1,4 @@
fragment ContainerRepositoryTagProtection on ContainerProtectionAccessLevel {
minimumAccessLevelForPush
minimumAccessLevelForDelete
}

View File

@ -1,4 +1,5 @@
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
#import "ee_else_ce/packages_and_registries/container_registry/explorer/graphql/fragments/container_repository_tag_protection.fragment.graphql"
query getContainerRepositoryTags(
$id: ContainerRepositoryID!
@ -40,9 +41,7 @@ query getContainerRepositoryTags(
destroyContainerRepositoryTag
}
protection {
minimumAccessLevelForPush
minimumAccessLevelForDelete
immutable
...ContainerRepositoryTagProtection
}
referrers {
artifactType

View File

@ -0,0 +1,3 @@
fragment ContainerProtectionTagRuleFragment on ContainerProtectionTagRule {
id
}

View File

@ -1,16 +1,17 @@
#import "ee_else_ce/packages_and_registries/settings/project/graphql/fragments/container_protection_tag_rule.fragment.graphql"
query getProjectContainerProtectionTagRules($projectPath: ID!, $first: Int) {
project(fullPath: $projectPath) {
id
containerProtectionTagRules(first: $first) {
nodes {
id
tagNamePattern
immutable
minimumAccessLevelForPush
minimumAccessLevelForDelete
userPermissions {
destroyContainerRegistryProtectionTagRule
}
...ContainerProtectionTagRuleFragment
}
}
}

View File

@ -224,19 +224,19 @@ class ProjectsFinder < UnionFinder
def by_marked_for_deletion_on(items)
return items unless params[:marked_for_deletion_on].present?
items.by_marked_for_deletion_on(params[:marked_for_deletion_on])
items.marked_for_deletion_on(params[:marked_for_deletion_on])
end
def by_aimed_for_deletion(items)
if ::Gitlab::Utils.to_boolean(params[:aimed_for_deletion])
items.aimed_for_deletion(Date.current)
items.self_or_ancestors_aimed_for_deletion
else
items
end
end
def by_not_aimed_for_deletion(items)
params[:not_aimed_for_deletion].present? ? items.not_aimed_for_deletion : items
params[:not_aimed_for_deletion].present? ? items.self_and_ancestors_not_aimed_for_deletion : items
end
def by_last_activity_after(items)
@ -280,14 +280,14 @@ class ProjectsFinder < UnionFinder
def by_archived(projects)
if params[:non_archived]
projects.non_archived
projects.self_and_ancestors_non_archived
elsif params.key?(:archived) && !params[:archived].nil?
if params[:archived] == 'only'
projects.archived
projects.self_or_ancestors_archived
elsif Gitlab::Utils.to_boolean(params[:archived])
projects
else
projects.non_archived
projects.self_and_ancestors_non_archived
end
else
projects
@ -310,15 +310,7 @@ class ProjectsFinder < UnionFinder
def by_active(items)
return items if params[:active].nil?
params[:active] ? active(items) : inactive(items)
end
def active(items)
items.non_archived.not_aimed_for_deletion
end
def inactive(items)
items.archived.or(items.aimed_for_deletion(Date.current))
params[:active] ? items.self_and_ancestors_active : items.self_or_ancestors_inactive
end
def finder_params

View File

@ -29,7 +29,7 @@ module Mutations
argument :minimum_access_level_for_delete,
Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum,
required: false,
required: true,
description: copy_field_description(
Types::ContainerRegistry::Protection::TagRuleType,
:minimum_access_level_for_delete
@ -37,7 +37,7 @@ module Mutations
argument :minimum_access_level_for_push,
Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum,
required: false,
required: true,
description: copy_field_description(
Types::ContainerRegistry::Protection::TagRuleType,
:minimum_access_level_for_push
@ -65,3 +65,5 @@ module Mutations
end
end
end
Mutations::ContainerRegistry::Protection::TagRule::Create.prepend_mod

View File

@ -8,29 +8,22 @@ module Types
field :minimum_access_level_for_delete,
Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum,
null: true,
null: false,
experiment: { milestone: '17.8' },
description:
'Minimum GitLab access level required to delete container image tags from the container repository. ' \
'Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. ' \
'If the value is `nil`, no access level can delete tags. '
'Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. '
field :minimum_access_level_for_push,
Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum,
null: true,
null: false,
experiment: { milestone: '17.8' },
description:
'Minimum GitLab access level required to push container image tags to the container repository. ' \
'Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. ' \
'If the value is `nil`, no access level can push tags. '
field :immutable,
GraphQL::Types::Boolean,
null: false,
method: :immutable?,
experiment: { milestone: '17.11' },
description: 'Returns true when tag rule is for tag immutability. Otherwise, false.'
'Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. '
end
end
end
end
Types::ContainerRegistry::Protection::AccessLevelInterface.prepend_mod

View File

@ -876,12 +876,8 @@ module Types
end
def container_protection_tag_rules
rules = object.container_registry_protection_tag_rules
return rules.mutable unless Feature.enabled?(:container_registry_immutable_tags, object)
# mutable tag rules come first before immutable
rules.mutable + rules.immutable
# Immutable tag rules are added in EE extension
object.container_registry_protection_tag_rules.mutable
end
{

View File

@ -3,14 +3,6 @@
module AvatarsHelper
DEFAULT_AVATAR_PATH = 'no_avatar.png'
def group_icon(group, options = {})
source_icon(group, options)
end
def topic_icon(topic, options = {})
source_icon(topic, options)
end
# Takes both user and email and returns the avatar_icon by
# user (preferred) or email.
def avatar_icon_for(user = nil, email = nil, size = nil, scale = 2, only_path: true)

View File

@ -658,13 +658,30 @@ class Project < ApplicationRecord
scope :not_hidden, -> { where(hidden: false) }
scope :not_in_groups, ->(groups) { where.not(group: groups) }
scope :by_not_in_root_id, ->(root_id) { joins(:project_namespace).where('namespaces.traversal_ids[1] NOT IN (?)', root_id) }
scope :aimed_for_deletion, -> { where.not(marked_for_deletion_at: nil).without_deleted }
scope :self_or_ancestors_aimed_for_deletion, -> do
left_joins(:group)
.where.not(marked_for_deletion_at: nil)
.or(where(Group.self_or_ancestors_deletion_schedule_subquery.exists))
.without_deleted
end
scope :not_aimed_for_deletion, -> { where(marked_for_deletion_at: nil).without_deleted }
scope :aimed_for_deletion, ->(date) { where('marked_for_deletion_at <= ?', date).without_deleted }
scope :with_deleting_user, -> { includes(:deleting_user) }
scope :by_marked_for_deletion_on, ->(marked_for_deletion_on) do
scope :self_and_ancestors_not_aimed_for_deletion, -> do
left_joins(:group)
.where(marked_for_deletion_at: nil)
.where.not(Group.self_or_ancestors_deletion_schedule_subquery.exists)
.without_deleted
end
scope :marked_for_deletion_before, ->(date) { where('marked_for_deletion_at <= ?', date).without_deleted }
scope :marked_for_deletion_on, ->(marked_for_deletion_on) do
where(marked_for_deletion_at: marked_for_deletion_on)
end
scope :with_deleting_user, -> { includes(:deleting_user) }
scope :with_storage_feature, ->(feature) do
where(arel_table[:storage_version].gteq(HASHED_STORAGE_FEATURES[feature]))
end
@ -798,8 +815,24 @@ class Project < ApplicationRecord
scope :starred_by, ->(user) { joins(:users_star_projects).where('users_star_projects.user_id': user.id) }
scope :visible_to_user, ->(user) { where(id: user.authorized_projects.select(:id).reorder(nil)) }
scope :visible_to_user_and_access_level, ->(user, access_level) { where(id: user.authorized_projects.where('project_authorizations.access_level >= ?', access_level).select(:id).reorder(nil)) }
scope :archived, -> { where(archived: true) }
scope :self_or_ancestors_archived, -> do
left_joins(:group)
.where(archived: true)
.or(where(Group.self_or_ancestors_archived_setting_subquery.exists))
end
scope :non_archived, -> { where(archived: false) }
scope :self_and_ancestors_non_archived, -> do
left_joins(:group)
.where(archived: false)
.where.not(Group.self_or_ancestors_archived_setting_subquery.exists)
end
scope :self_and_ancestors_active, -> { self_and_ancestors_non_archived.self_and_ancestors_not_aimed_for_deletion }
scope :self_or_ancestors_inactive, -> { self_or_ancestors_archived.or(self_or_ancestors_aimed_for_deletion) }
scope :with_push, -> { joins(:events).merge(Event.pushed_action) }
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :with_jira_dvcs_server, -> { joins(:feature_usage).merge(ProjectFeatureUsage.with_jira_dvcs_integration_enabled(cloud: false)) }

View File

@ -17,7 +17,7 @@ class AdjournedProjectsDeletionCronWorker
def perform
deletion_cutoff = Gitlab::CurrentSettings.deletion_adjourned_period.days.ago.to_date
Project.with_route.with_deleting_user.aimed_for_deletion(deletion_cutoff).find_each(batch_size: 100).with_index do |project, index| # rubocop: disable CodeReuse/ActiveRecord -- existing class moved from EE to CE
Project.with_route.with_deleting_user.marked_for_deletion_before(deletion_cutoff).find_each(batch_size: 100).with_index do |project, index| # rubocop: disable CodeReuse/ActiveRecord -- existing class moved from EE to CE
delay = index * INTERVAL
with_context(project: project, user: project.deleting_user) do

View File

@ -21,11 +21,11 @@ additional_properties:
property:
description: "a UUID that identifies a scan"
value:
description: "exit status of the analyer where 0 indicates success and 1 indicates error"
description: "exit status of the analyzer where 0 indicates success and 1 indicates error"
version:
description: "version of the analyzer"
exit_code:
description: "exit code of the analyer"
description: "exit code of the analyzer"
override_count:
description: "number of configured overrides"
passthrough_count:
@ -36,3 +36,5 @@ additional_properties:
description: "scan time duration in seconds"
file_count:
description: "project size in terms of number of files"
language_feature_usage:
description: "counts of programming language features used in the project"

View File

@ -639,6 +639,8 @@
- 1
- - namespaces_cascade_duo_features_enabled
- 1
- - namespaces_cascade_web_based_commit_signing_enabled
- 1
- - namespaces_free_user_cap_group_over_limit_notification
- 1
- - namespaces_process_sync_events

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddCreatedByIdToCustomStatuses < Gitlab::Database::Migration[2.3]
milestone '18.1'
def change
add_column :work_item_custom_statuses, :created_by_id, :bigint
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddCreatedByIdIndexToCustomStatuses < Gitlab::Database::Migration[2.3]
disable_ddl_transaction!
milestone '18.1'
INDEX_NAME = 'index_work_item_custom_statuses_on_created_by_id'
def up
add_concurrent_index :work_item_custom_statuses, :created_by_id, name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :work_item_custom_statuses, name: INDEX_NAME
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddCreatedByForeignKeyToCustomStatuses < Gitlab::Database::Migration[2.3]
disable_ddl_transaction!
milestone '18.1'
def up
add_concurrent_foreign_key :work_item_custom_statuses, :users,
column: :created_by_id, on_delete: :nullify
end
def down
remove_foreign_key_if_exists :work_item_custom_statuses, column: :created_by_id
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddCreatedByIdToCustomLifecycles < Gitlab::Database::Migration[2.3]
milestone '18.1'
def change
add_column :work_item_custom_lifecycles, :created_by_id, :bigint
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddCreatedByIdIndexToCustomLifecycles < Gitlab::Database::Migration[2.3]
disable_ddl_transaction!
milestone '18.1'
INDEX_NAME = 'index_work_item_custom_lifecycles_on_created_by_id'
def up
add_concurrent_index :work_item_custom_lifecycles, :created_by_id, name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :work_item_custom_lifecycles, name: INDEX_NAME
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddCreatedByForeignKeyToCustomLifecycles < Gitlab::Database::Migration[2.3]
disable_ddl_transaction!
milestone '18.1'
def up
add_concurrent_foreign_key :work_item_custom_lifecycles, :users,
column: :created_by_id, on_delete: :nullify
end
def down
remove_foreign_key_if_exists :work_item_custom_lifecycles, column: :created_by_id
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddUpdatedByIdToCustomStatuses < Gitlab::Database::Migration[2.3]
milestone '18.1'
def change
add_column :work_item_custom_statuses, :updated_by_id, :bigint
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddUpdatedByIdIndexToCustomStatuses < Gitlab::Database::Migration[2.3]
disable_ddl_transaction!
milestone '18.1'
INDEX_NAME = 'index_work_item_custom_statuses_on_updated_by_id'
def up
add_concurrent_index :work_item_custom_statuses, :updated_by_id, name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :work_item_custom_statuses, name: INDEX_NAME
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddUpdatedByForeignKeyToCustomStatuses < Gitlab::Database::Migration[2.3]
disable_ddl_transaction!
milestone '18.1'
def up
add_concurrent_foreign_key :work_item_custom_statuses, :users,
column: :updated_by_id, on_delete: :nullify
end
def down
remove_foreign_key_if_exists :work_item_custom_statuses, column: :updated_by_id
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddUpdatedByIdToCustomLifecycles < Gitlab::Database::Migration[2.3]
milestone '18.1'
def change
add_column :work_item_custom_lifecycles, :updated_by_id, :bigint
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddUpdatedByIdIndexToCustomLifecycles < Gitlab::Database::Migration[2.3]
disable_ddl_transaction!
milestone '18.1'
INDEX_NAME = 'index_work_item_custom_lifecycles_on_updated_by_id'
def up
add_concurrent_index :work_item_custom_lifecycles, :updated_by_id, name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :work_item_custom_lifecycles, name: INDEX_NAME
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddUpdatedByForeignKeyToCustomLifecycles < Gitlab::Database::Migration[2.3]
disable_ddl_transaction!
milestone '18.1'
def up
add_concurrent_foreign_key :work_item_custom_lifecycles, :users,
column: :updated_by_id, on_delete: :nullify
end
def down
remove_foreign_key_if_exists :work_item_custom_lifecycles, column: :updated_by_id
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
class RemoveIdxCiJobVariablesOnPartitionIdJobId < Gitlab::Database::Migration[2.3]
milestone '18.1'
disable_ddl_transaction!
TABLE_NAME = :ci_job_variables
INDEX_NAME = :index_ci_job_variables_on_partition_id_job_id
COLUMNS = [:partition_id, :job_id]
def up
remove_concurrent_index_by_name TABLE_NAME, name: INDEX_NAME
end
def down
add_concurrent_index TABLE_NAME, COLUMNS, name: INDEX_NAME
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class RemoveProjectFingerprintFromSecurityFindings < Gitlab::Database::Migration[2.3]
include Gitlab::Database::PartitioningMigrationHelpers
disable_ddl_transaction!
milestone '18.1'
def up
remove_check_constraint :security_findings, 'check_b9508c6df8'
remove_column :security_findings, :project_fingerprint
end
def down
add_column :security_findings, :project_fingerprint, :text, if_not_exists: true
add_check_constraint :security_findings, 'char_length(project_fingerprint) <= 40', 'check_b9508c6df8'
add_concurrent_partitioned_index :security_findings, :project_fingerprint,
name: 'security_findings_project_fingerprint_idx'
end
end

View File

@ -0,0 +1 @@
8dc9aa1a310798758d7b115d50d20142af56c35af11ada707708a8d521389021

View File

@ -0,0 +1 @@
363c5d217044ac211c3bdadf3d8d2e582f4c1c8c705ab5b6d2d141c18431e861

View File

@ -0,0 +1 @@
76f7874268107ae4a9d8bdbbfcdf522a869726b67a87a95ca184fa0409519c83

View File

@ -0,0 +1 @@
ccb4e487017f8742eaffb87048a6ea7c6b2bbb59a31b276abef1c4b921725f73

View File

@ -0,0 +1 @@
cc592eedda6586088a1049c9ca27618a73b50281335ccac5ea4c425c0c5f9444

View File

@ -0,0 +1 @@
e3a4587ea36f0c4b266a8e908b73e867e5df0afefa202417a439cf099a112e63

View File

@ -0,0 +1 @@
a3f50469cd58afe7fb6012ff7b27eef52e45c966f7cffb0516d499b70b0de666

View File

@ -0,0 +1 @@
f0c69d5d3bfdcc30df065db0c658ee86bbe4e28dc4eb50233308d947faaf0b3f

View File

@ -0,0 +1 @@
4a373f4a79e4bf14b5509ce63a9fb79267246b2dfc03c83d20868142f6eaca9d

View File

@ -0,0 +1 @@
5674253dd6f59541e4a396635aceffd7963bf4fef38bbebb58851ecd8b10b1ee

View File

@ -0,0 +1 @@
e0ae0be50f1fd197267e524d0bed195be8692dec84c3ddb24ad5bf754fa26a02

View File

@ -0,0 +1 @@
d8de1cea7fdafa9d31cab776eff7ea66015d253abed20056cff1fcfd4334561b

View File

@ -0,0 +1 @@
a05765899ad07672f1c007ac86087e799aeb507328675566585cfd684878b4ba

View File

@ -0,0 +1 @@
1826f2a6ec247577a18af2a0c6d14713ef817760ca0607c0a8f49badce883504

View File

@ -4965,15 +4965,13 @@ CREATE TABLE security_findings (
scan_id bigint NOT NULL,
scanner_id bigint NOT NULL,
severity smallint NOT NULL,
project_fingerprint text,
deduplicated boolean DEFAULT false NOT NULL,
uuid uuid,
overridden_uuid uuid,
partition_number integer DEFAULT 1 NOT NULL,
finding_data jsonb DEFAULT '{}'::jsonb NOT NULL,
project_id bigint,
CONSTRAINT check_6c2851a8c9 CHECK ((uuid IS NOT NULL)),
CONSTRAINT check_b9508c6df8 CHECK ((char_length(project_fingerprint) <= 40))
CONSTRAINT check_6c2851a8c9 CHECK ((uuid IS NOT NULL))
)
PARTITION BY LIST (partition_number);
@ -25788,6 +25786,8 @@ CREATE TABLE work_item_custom_lifecycles (
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
name text NOT NULL,
created_by_id bigint,
updated_by_id bigint,
CONSTRAINT check_1feff2de99 CHECK ((char_length(name) <= 255))
);
@ -25809,6 +25809,8 @@ CREATE TABLE work_item_custom_statuses (
name text NOT NULL,
description text,
color text NOT NULL,
created_by_id bigint,
updated_by_id bigint,
CONSTRAINT check_4789467800 CHECK ((char_length(color) <= 7)),
CONSTRAINT check_720a7c4d24 CHECK ((char_length(name) <= 255)),
CONSTRAINT check_8ea8b3c991 CHECK ((char_length(description) <= 255)),
@ -34438,8 +34440,6 @@ CREATE INDEX index_ci_job_variables_on_job_id ON ci_job_variables USING btree (j
CREATE UNIQUE INDEX index_ci_job_variables_on_key_and_job_id ON ci_job_variables USING btree (key, job_id);
CREATE INDEX index_ci_job_variables_on_partition_id_job_id ON ci_job_variables USING btree (partition_id, job_id);
CREATE INDEX index_ci_job_variables_on_project_id ON ci_job_variables USING btree (project_id);
CREATE INDEX index_ci_minutes_additional_packs_on_namespace_id_purchase_xid ON ci_minutes_additional_packs USING btree (namespace_id, purchase_xid);
@ -38204,10 +38204,18 @@ CREATE INDEX index_work_item_current_statuses_on_namespace_id ON work_item_curre
CREATE UNIQUE INDEX index_work_item_current_statuses_on_work_item_id ON work_item_current_statuses USING btree (work_item_id);
CREATE INDEX index_work_item_custom_lifecycles_on_created_by_id ON work_item_custom_lifecycles USING btree (created_by_id);
CREATE UNIQUE INDEX index_work_item_custom_lifecycles_on_namespace_id_and_name ON work_item_custom_lifecycles USING btree (namespace_id, name);
CREATE INDEX index_work_item_custom_lifecycles_on_updated_by_id ON work_item_custom_lifecycles USING btree (updated_by_id);
CREATE INDEX index_work_item_custom_statuses_on_created_by_id ON work_item_custom_statuses USING btree (created_by_id);
CREATE UNIQUE INDEX index_work_item_custom_statuses_on_namespace_id_and_lower_name ON work_item_custom_statuses USING btree (namespace_id, TRIM(BOTH FROM lower(name)));
CREATE INDEX index_work_item_custom_statuses_on_updated_by_id ON work_item_custom_statuses USING btree (updated_by_id);
CREATE INDEX index_work_item_hierarchy_restrictions_on_child_type_id ON work_item_hierarchy_restrictions USING btree (child_type_id);
CREATE UNIQUE INDEX index_work_item_hierarchy_restrictions_on_parent_and_child ON work_item_hierarchy_restrictions USING btree (parent_type_id, child_type_id);
@ -38708,8 +38716,6 @@ CREATE INDEX scan_finding_approval_project_rule_index_created_at_project_id ON a
CREATE INDEX scan_finding_approval_project_rule_index_project_id ON approval_project_rules USING btree (project_id) WHERE (report_type = 4);
CREATE INDEX security_findings_project_fingerprint_idx ON ONLY security_findings USING btree (project_fingerprint);
CREATE INDEX security_findings_scan_id_deduplicated_idx ON ONLY security_findings USING btree (scan_id, deduplicated);
CREATE INDEX security_findings_scan_id_id_idx ON ONLY security_findings USING btree (scan_id, id);
@ -42339,6 +42345,9 @@ ALTER TABLE ONLY deployment_approvals
ALTER TABLE ONLY bulk_import_trackers
ADD CONSTRAINT fk_2d0e051bc3 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY work_item_custom_lifecycles
ADD CONSTRAINT fk_2d0f7ebf48 FOREIGN KEY (updated_by_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY audit_events_instance_external_audit_event_destinations
ADD CONSTRAINT fk_2d3ebd0fbc FOREIGN KEY (stream_destination_id) REFERENCES audit_events_instance_external_streaming_destinations(id) ON DELETE SET NULL;
@ -42708,6 +42717,9 @@ ALTER TABLE ONLY deploy_keys_projects
ALTER TABLE ONLY merge_requests_approval_rules_groups
ADD CONSTRAINT fk_59068f09e5 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY work_item_custom_statuses
ADD CONSTRAINT fk_590e87b7c7 FOREIGN KEY (updated_by_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY oauth_access_grants
ADD CONSTRAINT fk_59cdb2323c FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
@ -42777,6 +42789,9 @@ ALTER TABLE ONLY user_achievements
ALTER TABLE ONLY merge_requests
ADD CONSTRAINT fk_6149611a04 FOREIGN KEY (assignee_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY work_item_custom_lifecycles
ADD CONSTRAINT fk_614a3cdb95 FOREIGN KEY (created_by_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY member_approvals
ADD CONSTRAINT fk_619f381144 FOREIGN KEY (member_role_id) REFERENCES member_roles(id) ON DELETE SET NULL;
@ -44139,6 +44154,9 @@ ALTER TABLE ONLY merge_requests_approval_rules
ALTER TABLE ONLY clusters_managed_resources
ADD CONSTRAINT fk_fad3c3b2e2 FOREIGN KEY (environment_id) REFERENCES environments(id) ON DELETE CASCADE;
ALTER TABLE ONLY work_item_custom_statuses
ADD CONSTRAINT fk_fb28a15e7b FOREIGN KEY (created_by_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY agent_group_authorizations
ADD CONSTRAINT fk_fb70782616 FOREIGN KEY (agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE;

View File

@ -4261,8 +4261,8 @@ Input type: `createContainerProtectionTagRuleInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationcreatecontainerprotectiontagruleclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationcreatecontainerprotectiontagruleminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | Minimum GitLab access level required to delete container image tags from the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no access level can delete tags. Introduced in GitLab 17.8: **Status**: Experiment. |
| <a id="mutationcreatecontainerprotectiontagruleminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | Minimum GitLab access level required to push container image tags to the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no access level can push tags. Introduced in GitLab 17.8: **Status**: Experiment. |
| <a id="mutationcreatecontainerprotectiontagruleminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | Minimum GitLab access level required to delete container image tags from the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. Introduced in GitLab 17.8: **Status**: Experiment. If the value is `nil`, no access level can delete tags. |
| <a id="mutationcreatecontainerprotectiontagruleminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | Minimum GitLab access level required to push container image tags to the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. Introduced in GitLab 17.8: **Status**: Experiment. If the value is `nil`, no access level can push tags. |
| <a id="mutationcreatecontainerprotectiontagruleprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project containing the container image tags. |
| <a id="mutationcreatecontainerprotectiontagruletagnamepattern"></a>`tagNamePattern` | [`String!`](#string) | The pattern that matches container image tags to protect. For example, `v1.*`. Wildcard character `*` allowed. Introduced in GitLab 17.8: **Status**: Experiment. |
@ -21197,9 +21197,9 @@ Represents an admin member role.
| <a id="adminmemberroleeditpath"></a>`editPath` {{< icon name="warning-solid" >}} | [`String!`](#string) | **Introduced** in GitLab 16.11. **Status**: Experiment. Web UI path to edit the custom role. |
| <a id="adminmemberroleenabledpermissions"></a>`enabledPermissions` {{< icon name="warning-solid" >}} | [`CustomizableAdminPermissionConnection!`](#customizableadminpermissionconnection) | **Introduced** in GitLab 17.7. **Status**: Experiment. Array of all permissions enabled for the custom role. |
| <a id="adminmemberroleid"></a>`id` | [`ID!`](#id) | Role ID. |
| <a id="adminmemberrolememberscount"></a>`membersCount` {{< icon name="warning-solid" >}} | [`Int`](#int) | **Introduced** in GitLab 17.3. **Status**: Experiment. Number of times the role has been directly assigned to a group or project member. |
| <a id="adminmemberroleldapadminrolelinks"></a>`ldapAdminRoleLinks` {{< icon name="warning-solid" >}} | [`LdapAdminRoleLinkConnection`](#ldapadminrolelinkconnection) | **Introduced** in GitLab 18.1. **Status**: Experiment. LDAP admin role sync configurations that will assign the admin member role. |
| <a id="adminmemberrolename"></a>`name` | [`String`](#string) | Role name. |
| <a id="adminmemberroleuserscount"></a>`usersCount` {{< icon name="warning-solid" >}} | [`Int`](#int) | **Introduced** in GitLab 17.5. **Status**: Experiment. Number of users who have been directly assigned the role in at least one group or project. |
| <a id="adminmemberroleuserscount"></a>`usersCount` {{< icon name="warning-solid" >}} | [`Int`](#int) | **Introduced** in GitLab 17.5. **Status**: Experiment. Number of users who have been directly assigned the admin member role. |
### `AgentConfiguration`
@ -24455,8 +24455,8 @@ Represents the most restrictive permissions for a container image tag.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="containerprotectionaccesslevelimmutable"></a>`immutable` {{< icon name="warning-solid" >}} | [`Boolean!`](#boolean) | **Introduced** in GitLab 17.11. **Status**: Experiment. Returns true when tag rule is for tag immutability. Otherwise, false. |
| <a id="containerprotectionaccesslevelminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` {{< icon name="warning-solid" >}} | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to delete container image tags from the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no access level can delete tags. |
| <a id="containerprotectionaccesslevelminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` {{< icon name="warning-solid" >}} | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to push container image tags to the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no access level can push tags. |
| <a id="containerprotectionaccesslevelminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` {{< icon name="warning-solid" >}} | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to delete container image tags from the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. |
| <a id="containerprotectionaccesslevelminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` {{< icon name="warning-solid" >}} | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to push container image tags to the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. |
### `ContainerProtectionRepositoryRule`
@ -24481,8 +24481,8 @@ A container repository tag protection rule designed to prevent users with a cert
| ---- | ---- | ----------- |
| <a id="containerprotectiontagruleid"></a>`id` {{< icon name="warning-solid" >}} | [`ContainerRegistryProtectionTagRuleID!`](#containerregistryprotectiontagruleid) | **Introduced** in GitLab 17.8. **Status**: Experiment. ID of the container repository tag protection rule. |
| <a id="containerprotectiontagruleimmutable"></a>`immutable` {{< icon name="warning-solid" >}} | [`Boolean!`](#boolean) | **Introduced** in GitLab 17.11. **Status**: Experiment. Returns true when tag rule is for tag immutability. Otherwise, false. |
| <a id="containerprotectiontagruleminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` {{< icon name="warning-solid" >}} | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to delete container image tags from the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no access level can delete tags. |
| <a id="containerprotectiontagruleminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` {{< icon name="warning-solid" >}} | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to push container image tags to the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no access level can push tags. |
| <a id="containerprotectiontagruleminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` {{< icon name="warning-solid" >}} | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to delete container image tags from the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. |
| <a id="containerprotectiontagruleminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` {{< icon name="warning-solid" >}} | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to push container image tags to the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. |
| <a id="containerprotectiontagruletagnamepattern"></a>`tagNamePattern` {{< icon name="warning-solid" >}} | [`String!`](#string) | **Introduced** in GitLab 17.8. **Status**: Experiment. The pattern that matches container image tags to protect. For example, `v1.*`. Wildcard character `*` allowed. |
| <a id="containerprotectiontagruleuserpermissions"></a>`userPermissions` | [`ContainerRegistryProtectionTagRulePermissions!`](#containerregistryprotectiontagrulepermissions) | Permissions for the current user on the resource. |
@ -48627,8 +48627,8 @@ Implementations:
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="accesslevelinterfaceimmutable"></a>`immutable` {{< icon name="warning-solid" >}} | [`Boolean!`](#boolean) | **Introduced** in GitLab 17.11. **Status**: Experiment. Returns true when tag rule is for tag immutability. Otherwise, false. |
| <a id="accesslevelinterfaceminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` {{< icon name="warning-solid" >}} | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to delete container image tags from the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no access level can delete tags. |
| <a id="accesslevelinterfaceminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` {{< icon name="warning-solid" >}} | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to push container image tags to the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. If the value is `nil`, no access level can push tags. |
| <a id="accesslevelinterfaceminimumaccesslevelfordelete"></a>`minimumAccessLevelForDelete` {{< icon name="warning-solid" >}} | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to delete container image tags from the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. |
| <a id="accesslevelinterfaceminimumaccesslevelforpush"></a>`minimumAccessLevelForPush` {{< icon name="warning-solid" >}} | [`ContainerProtectionTagRuleAccessLevel`](#containerprotectiontagruleaccesslevel) | **Introduced** in GitLab 17.8. **Status**: Experiment. Minimum GitLab access level required to push container image tags to the container repository. Valid values include `MAINTAINER`, `OWNER`, or `ADMIN`. |
#### `AlertManagementIntegration`
@ -48870,6 +48870,20 @@ four standard [pagination arguments](#pagination-arguments):
| ---- | ---- | ----------- |
| <a id="currentusertodoscurrentusertodosstate"></a>`state` | [`TodoStateEnum`](#todostateenum) | State of the to-do items. |
#### `CustomRoleInterface`
Implementations:
- [`AdminMemberRole`](#adminmemberrole)
- [`MemberRole`](#memberrole)
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="customroleinterfacecreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of when the member role was created. |
| <a id="customroleinterfaceeditpath"></a>`editPath` {{< icon name="warning-solid" >}} | [`String!`](#string) | **Introduced** in GitLab 16.11. **Status**: Experiment. Web UI path to edit the custom role. |
#### `DependencyInterface`
Implementations:
@ -49110,6 +49124,20 @@ Returns [`UserMergeRequestInteraction`](#usermergerequestinteraction).
| ---- | ---- | ----------- |
| <a id="memberinterfacemergerequestinteractionid"></a>`id` | [`MergeRequestID!`](#mergerequestid) | Global ID of the merge request. |
#### `MemberRoleInterface`
Implementations:
- [`MemberRole`](#memberrole)
- [`StandardRole`](#standardrole)
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="memberroleinterfacememberscount"></a>`membersCount` {{< icon name="warning-solid" >}} | [`Int`](#int) | **Introduced** in GitLab 17.3. **Status**: Experiment. Number of times the role has been directly assigned to a group or project member. |
| <a id="memberroleinterfaceuserscount"></a>`usersCount` {{< icon name="warning-solid" >}} | [`Int`](#int) | **Introduced** in GitLab 17.5. **Status**: Experiment. Number of users who have been directly assigned the role in at least one group or project. |
#### `NamespacesLinkPaths`
Implementations:
@ -49323,9 +49351,7 @@ Implementations:
| <a id="roleinterfacedescription"></a>`description` | [`String`](#string) | Role description. |
| <a id="roleinterfacedetailspath"></a>`detailsPath` {{< icon name="warning-solid" >}} | [`String`](#string) | **Introduced** in GitLab 17.4. **Status**: Experiment. URL path to the role details webpage. |
| <a id="roleinterfaceid"></a>`id` | [`ID!`](#id) | Role ID. |
| <a id="roleinterfacememberscount"></a>`membersCount` {{< icon name="warning-solid" >}} | [`Int`](#int) | **Introduced** in GitLab 17.3. **Status**: Experiment. Number of times the role has been directly assigned to a group or project member. |
| <a id="roleinterfacename"></a>`name` | [`String`](#string) | Role name. |
| <a id="roleinterfaceuserscount"></a>`usersCount` {{< icon name="warning-solid" >}} | [`Int`](#int) | **Introduced** in GitLab 17.5. **Status**: Experiment. Number of users who have been directly assigned the role in at least one group or project. |
#### `Service`

View File

@ -58,7 +58,7 @@ somewhere within the worker:
deletion_cutoff = Gitlab::CurrentSettings
.deletion_adjourned_period.days.ago.to_date
projects = Project.with_route.with_namespace
.aimed_for_deletion(deletion_cutoff)
.marked_for_deletion_before(deletion_cutoff)
projects.find_each(batch_size: 100).with_index do |project, index|
delay = index * INTERVAL

View File

@ -195,6 +195,11 @@ You can use GitLab compliance controls or external controls for framework requir
GitLab compliance controls can be used in GitLab compliance frameworks. Controls are checks against the configuration or
behavior of projects that are assigned to a compliance framework.
Combine GitLab compliance controls to help you meet
[compliance standards](compliance_frameworks/compliance_standards.md).
<!-- Updates to control names must be reflected also in compliance_frameworks/compliance_standards.md -->
| Control name | Control ID | Description |
|:---------------------------------------------------------|:-----------------------------------------------------------|:------------|
| API security running | `scanner_api_security_running` | Ensures that [API security scanning](../application_security/api_security/_index.md) is configured and running in the project pipelines. |

View File

@ -0,0 +1,28 @@
---
stage: Software Supply Chain Security
group: Compliance
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
title: Compliance standards
---
You can use [GitLab compliance controls](../compliance_frameworks.md#gitlab-compliance-controls) to help meet the
requirements of many compliance standards.
## ISO 27001 compliance requirements
ISO 27001 is an internationally recognized standard that provides a framework for implementing and managing an
Information Security Management System (ISMS).
The following table lists the requirements supported by GitLab for ISO 27001 and the controls for the requirements.
| ISO 27001 requirement | Description | Supported controls |
|:----------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------|
| 5.3 Segregation of duties | Conflicting duties and conflicting areas of responsibility shall be segregated. | <ul><li>At least two approvals</li><li>Author approved merge request is forbidden</li><li>Committers approved merge request is forbidden</li><li>Merge requests approval rules prevent editing</li></ul> |
| 5.17 Authentication information | Allocation and management of authentication information should be controlled by a management process, including advising personnel on the appropriate handling of authentication information. | <ul><li>Secret detection running</li></ul> |
| 5.18 Access rights | Access rights to information and other associated assets should be provisioned, reviewed, modified, and removed in accordance with the organization's topic-specific policy on and rules for access control. | <ul><li>At least two approvals</li><li>Author approved merge request is forbidden</li><li>Committers approved merge request is forbidden</li><li>Merge requests approval rules prevent editing</li></ul> |
| 5.32 Intellectual property rights | The organization should implement appropriate procedures to protect intellectual property rights. | <ul><li>License compliance running</li></ul> |
| 8.4 Access to source code | Read and write access to source code, development tools and software libraries shall be appropriately managed. | <ul><li>Default branch protected</li></ul> |
| 8.8 Management of technical vulnerabilities | Information about technical vulnerabilities of information systems in use shall be obtained, the organization's exposure to such vulnerabilities shall be evaluated and appropriate measures shall be taken. | <ul><li>Dependency scanning running</li><li>Container scanning running</li><li>SAST running</li><li>DAST running</li><li>API security running</li><li>Fuzz testing running</li></ul> |
| 8.28 Secure coding | Secure coding principles shall be applied to software development. | <ul><li>Dependency scanning running</li><li>Container scanning running</li><li>SAST running</li><li>DAST running</li><li>API security running</li><li>Secret detection running</li><li>Fuzz testing running</li></ul> |
| 8.29 Security testing in development and acceptance | Security testing processes shall be defined and implemented in the development lifecycle. | <ul><li>Dependency scanning running</li><li>Container scanning running</li><li>SAST running</li><li>DAST running</li><li>API security running</li><li>Secret detection running</li><li>Fuzz testing running</li></ul> |
| 8.32 Change management | Changes to information processing facilities and information systems shall be subject to change management procedures. | <ul><li>Default branch protected</li></ul> |

View File

@ -16,7 +16,9 @@ module Backup
# Ignore the DROP errors; recent database dumps will use --if-exists with pg_dump
/does not exist$/,
# User may not have permissions to drop extensions or schemas
/must be owner of/
/must be owner of/,
# PG16 introduced generally ignorable error `must be able to SET ROLE "gitlab-psql"`
/must be able to SET ROLE "gitlab-psql"/i
].freeze
IGNORED_ERRORS_REGEXP = Regexp.union(IGNORED_ERRORS).freeze

View File

@ -37689,6 +37689,9 @@ msgstr ""
msgid "MemberRole|Admin role is assigned to one or more users. Remove role from all users, then delete role."
msgstr ""
msgid "MemberRole|Admin role is used by one or more LDAP synchronizations. Remove LDAP syncs, then delete role."
msgstr ""
msgid "MemberRole|Are you sure you want to delete this role?"
msgstr ""

View File

@ -0,0 +1,70 @@
# frozen_string_literal: true
module RuboCop
module Cop
module Gitlab
# Discourages the use of `Current.organization&.id`.
#
# `Current.organization` is expected to be assigned in contexts where its ID is accessed.
# If `Current.organization` is not assigned, attempting to access `id` directly
# (i.e., `Current.organization.id`) will correctly raise a
# `Current::OrganizationNotAssignedError`. Using the safe navigation operator (`&.id`)
# prevents this error from being raised, potentially hiding issues where
# `Current.organization` was not properly set up.
#
# This cop enforces the direct use of `Current.organization.id` to ensure
# that `Current::OrganizationNotAssignedError` is raised if `Current.organization` is nil.
#
# @example
#
# # bad
# id = Current.organization&.id
# id = ::Current.organization&.id
#
# # good
# # If Current.organization is expected to be present (which it is),
# # this will raise Current::OrganizationNotAssignedError if it's unexpectedly nil,
# # making the underlying issue visible.
# id = Current.organization.id
#
class DisallowCurrentOrganizationIdSafeNavigation < RuboCop::Cop::Base
extend AutoCorrector
MSG = 'Use `Current.organization.id` instead of `Current.organization&.id`. ' \
'`Current.organization` is expected to be assigned.'
# @!method current_organization_safe_id?(node)
def_node_matcher :current_organization_safe_id?, <<~PATTERN
(csend
(send
(const {nil? | cbase} :Current) :organization) :id)
PATTERN
def on_csend(node)
return unless current_organization_safe_id?(node)
add_offense(node) do |corrector|
operator_range = node.loc.operator
if operator_range.nil? && node.receiver && node.loc.selector
# Fallback: If node.loc.operator is nil, try to determine the range
# by looking at the space between the receiver and the method selector.
receiver_end_pos = node.receiver.source_range.end_pos
selector_begin_pos = node.loc.selector.begin_pos
if receiver_end_pos < selector_begin_pos
# This range covers the characters between the end of the receiver
# and the start of the selector, which should be the operator.
operator_range = Parser::Source::Range.new(node.source_range.source_buffer,
receiver_end_pos,
selector_begin_pos)
end
end
corrector.replace(operator_range, '.') if operator_range
end
end
end
end
end
end

View File

@ -198,7 +198,10 @@ RSpec.describe ProjectsFinder, feature_category: :groups_and_projects do
let_it_be(:aimed_for_deletion_project) { create(:project, :public, marked_for_deletion_at: 2.days.ago, pending_delete: false) }
let_it_be(:pending_deletion_project) { create(:project, :public, marked_for_deletion_at: 1.month.ago, pending_delete: true) }
it { is_expected.to contain_exactly(aimed_for_deletion_project) }
let_it_be(:group_aimed_for_deletion) { create(:group_with_deletion_schedule) }
let_it_be(:group_aimed_for_deletion_project) { create(:project, :public, group: group_aimed_for_deletion) }
it { is_expected.to contain_exactly(aimed_for_deletion_project, group_aimed_for_deletion_project) }
end
describe 'filter by not aimed for deletion' do
@ -206,6 +209,9 @@ RSpec.describe ProjectsFinder, feature_category: :groups_and_projects do
let_it_be(:aimed_for_deletion_project) { create(:project, :public, marked_for_deletion_at: 2.days.ago, pending_delete: false) }
let_it_be(:pending_deletion_project) { create(:project, :public, marked_for_deletion_at: 1.month.ago, pending_delete: true) }
let_it_be(:group_aimed_for_deletion) { create(:group_with_deletion_schedule) }
let_it_be(:group_aimed_for_deletion_project) { create(:project, :public, group: group_aimed_for_deletion) }
it { is_expected.to contain_exactly(public_project, internal_project) }
end
@ -370,11 +376,16 @@ RSpec.describe ProjectsFinder, feature_category: :groups_and_projects do
let_it_be(:archived_project) { create(:project, :archived, :public) }
let_it_be(:for_deletion_project) { create(:project, :public, marked_for_deletion_at: Date.current) }
let_it_be(:archived_group_project) { create(:project, :public, group: create(:group, :archived)) }
let_it_be(:for_deletion_group_project) do
create(:project, :public, group: create(:group_with_deletion_schedule))
end
where :test_params, :expected_projects do
{} | [ref(:active_projects), ref(:archived_project), ref(:for_deletion_project)]
{ active: nil } | [ref(:active_projects), ref(:archived_project), ref(:for_deletion_project)]
{} | [ref(:active_projects), ref(:archived_project), ref(:for_deletion_project), ref(:archived_group_project), ref(:for_deletion_group_project)]
{ active: nil } | [ref(:active_projects), ref(:archived_project), ref(:for_deletion_project), ref(:archived_group_project), ref(:for_deletion_group_project)]
{ active: true } | [ref(:active_projects)]
{ active: false } | [ref(:archived_project), ref(:for_deletion_project)]
{ active: false } | [ref(:archived_project), ref(:for_deletion_project), ref(:archived_group_project), ref(:for_deletion_group_project)]
end
with_them do
@ -385,7 +396,8 @@ RSpec.describe ProjectsFinder, feature_category: :groups_and_projects do
end
describe 'filter by archived' do
let!(:archived_project) { create(:project, :public, :archived, name: 'E', path: 'E') }
let_it_be(:archived_project) { create(:project, :public, :archived, name: 'E', path: 'E') }
let_it_be(:archived_group_project) { create(:project, :public, group: create(:group, :archived)) }
context 'non_archived=true' do
let(:params) { { non_archived: true } }
@ -396,13 +408,13 @@ RSpec.describe ProjectsFinder, feature_category: :groups_and_projects do
context 'non_archived=false' do
let(:params) { { non_archived: false } }
it { is_expected.to match_array([public_project, internal_project, archived_project]) }
it { is_expected.to match_array([public_project, internal_project, archived_project, archived_group_project]) }
end
describe 'filter by archived only' do
let(:params) { { archived: 'only' } }
it { is_expected.to eq([archived_project]) }
it { is_expected.to eq([archived_project, archived_group_project]) }
end
describe 'filter by archived for backward compatibility' do
@ -414,7 +426,7 @@ RSpec.describe ProjectsFinder, feature_category: :groups_and_projects do
describe 'filter by archived is present and is nil' do
let(:params) { { archived: nil } }
it { is_expected.to match_array([public_project, internal_project, archived_project]) }
it { is_expected.to match_array([public_project, internal_project, archived_project, archived_group_project]) }
end
end

View File

@ -2480,8 +2480,6 @@ Groups::MilestonesController
returns not found
GroupsHelper
group_icon
returns an url for the avatar
group_icon_url
returns an url for the avatar
gives default avatar_icon when no avatar is present

View File

@ -32,18 +32,6 @@ RSpec.describe 'Container registry (JavaScript fixtures)', feature_category: :co
}
end
shared_examples 'container registry protection tag rules' do |fixture_suffix|
before do
create(:container_registry_protection_tag_rule, :immutable, project: project)
end
it "graphql/#{project_container_protection_tag_rules_query_path}.#{fixture_suffix}.json" do
post_graphql(query, current_user: user, variables: variables)
expect_graphql_errors_to_be_empty
end
end
before do
stub_gitlab_api_client_to_support_gitlab_api(supported: true)
end
@ -85,18 +73,6 @@ RSpec.describe 'Container registry (JavaScript fixtures)', feature_category: :co
end
end
context 'with immutable tag protection rules' do
it_behaves_like 'container registry protection tag rules', 'immutable_rules'
end
context 'with immutable tag protection rules as maintainer' do
before_all do
project.add_maintainer(user)
end
it_behaves_like 'container registry protection tag rules', 'immutable_rules_maintainer'
end
context 'with maximum number of tag protection rules' do
before do
5.times do |i|

View File

@ -7,21 +7,15 @@ RSpec.describe GitlabSchema.types['ContainerProtectionAccessLevel'], feature_cat
specify { expect(described_class.description).to be_present }
describe 'minimum_access_level_for_push' do
describe 'minimum_access_level_for_push', unless: Gitlab.ee? do
subject { described_class.fields['minimumAccessLevelForPush'] }
it { is_expected.to have_nullable_graphql_type(Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum) }
it { is_expected.to have_non_null_graphql_type(Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum) }
end
describe 'minimum_access_level_for_delete' do
describe 'minimum_access_level_for_delete', unless: Gitlab.ee? do
subject { described_class.fields['minimumAccessLevelForDelete'] }
it { is_expected.to have_nullable_graphql_type(Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum) }
end
describe 'immutable' do
subject { described_class.fields['immutable'] }
it { is_expected.to have_non_null_graphql_type(GraphQL::Types::Boolean) }
it { is_expected.to have_non_null_graphql_type(Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum) }
end
end

View File

@ -25,21 +25,15 @@ RSpec.describe GitlabSchema.types['ContainerProtectionTagRule'], feature_categor
it { is_expected.to have_non_null_graphql_type(GraphQL::Types::String) }
end
describe 'minimum_access_level_for_push' do
describe 'minimum_access_level_for_push', unless: Gitlab.ee? do
subject { described_class.fields['minimumAccessLevelForPush'] }
it { is_expected.to have_nullable_graphql_type(Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum) }
it { is_expected.to have_non_null_graphql_type(Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum) }
end
describe 'minimum_access_level_for_delete' do
describe 'minimum_access_level_for_delete', unless: Gitlab.ee? do
subject { described_class.fields['minimumAccessLevelForDelete'] }
it { is_expected.to have_nullable_graphql_type(Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum) }
end
describe 'immutable' do
subject { described_class.fields['immutable'] }
it { is_expected.to have_non_null_graphql_type(GraphQL::Types::Boolean) }
it { is_expected.to have_non_null_graphql_type(Types::ContainerRegistry::Protection::TagRuleAccessLevelEnum) }
end
end

View File

@ -1578,29 +1578,10 @@ RSpec.describe GitlabSchema.types['Project'], feature_category: :groups_and_proj
end
end
describe 'container_protection_tag_rules' do
describe 'container_protection_tag_rules', unless: Gitlab.ee? do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
before_all do
create(:container_registry_protection_tag_rule, :immutable,
project: project,
tag_name_pattern: 'immutable-1'
)
create(:container_registry_protection_tag_rule,
project: project,
minimum_access_level_for_push: Gitlab::Access::MAINTAINER,
minimum_access_level_for_delete: Gitlab::Access::OWNER,
tag_name_pattern: 'mutable'
)
create(:container_registry_protection_tag_rule, :immutable,
project: project,
tag_name_pattern: 'immutable-2'
)
end
let(:query) do
%(
query {
@ -1618,52 +1599,37 @@ RSpec.describe GitlabSchema.types['Project'], feature_category: :groups_and_proj
)
end
subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
before_all do
create(:container_registry_protection_tag_rule, :immutable,
project: project,
tag_name_pattern: 'immutable-1'
)
create(:container_registry_protection_tag_rule,
project: project,
minimum_access_level_for_push: Gitlab::Access::MAINTAINER,
minimum_access_level_for_delete: Gitlab::Access::OWNER,
tag_name_pattern: 'mutable'
)
end
subject do
GitlabSchema.execute(query, context: { current_user: user })
.as_json.dig('data', 'project', 'containerProtectionTagRules', 'nodes')
end
before do
project.add_maintainer(user)
end
it 'returns tag rules with mutable ones first' do
result_nodes = subject.dig('data', 'project', 'containerProtectionTagRules', 'nodes')
expect(result_nodes.size).to eq(3)
expect(result_nodes[0]).to include(
'tagNamePattern' => 'mutable',
'minimumAccessLevelForPush' => 'MAINTAINER',
'minimumAccessLevelForDelete' => 'OWNER'
)
expect(result_nodes[1]).to include(
'tagNamePattern' => 'immutable-1',
'minimumAccessLevelForPush' => nil,
'minimumAccessLevelForDelete' => nil
)
expect(result_nodes[2]).to include(
'tagNamePattern' => 'immutable-2',
'minimumAccessLevelForPush' => nil,
'minimumAccessLevelForDelete' => nil
)
end
context 'when the feature container_registry_immutable_tags is disabled' do
before do
stub_feature_flags(container_registry_immutable_tags: false)
end
it 'only returns mutable tag rules' do
result_nodes = subject.dig('data', 'project', 'containerProtectionTagRules', 'nodes')
expect(result_nodes.size).to eq(1)
expect(result_nodes[0]).to include(
it do
is_expected.to have_attributes(size: 1).and contain_exactly(
a_hash_including(
'tagNamePattern' => 'mutable',
'minimumAccessLevelForPush' => 'MAINTAINER',
'minimumAccessLevelForDelete' => 'OWNER'
)
end
)
end
end
end

View File

@ -7,54 +7,6 @@ RSpec.describe AvatarsHelper, feature_category: :source_code_management do
let_it_be(:user) { create(:user) }
describe '#group_icon, #topic_icon' do
shared_examples 'resource with a default avatar' do |source_type|
it 'returns a default avatar div' do
expect(public_send("#{source_type}_icon", *helper_args))
.to match(%r{<span class="identicon bg\d+">F</span>})
end
end
shared_examples 'resource with a custom avatar' do |source_type|
it 'returns a custom avatar image' do
expect(public_send("#{source_type}_icon", *helper_args))
.to eq "<img src=\"#{resource.avatar.url}\" />"
end
end
shared_examples 'Gitaly exception handling' do
before do
allow(resource).to receive(:avatar_url).and_raise(error_class)
end
it_behaves_like 'resource with a default avatar', 'project'
end
context 'when providing a group' do
it_behaves_like 'resource with a default avatar', 'group' do
let(:resource) { create(:group, name: 'foo') }
let(:helper_args) { [resource] }
end
it_behaves_like 'resource with a custom avatar', 'group' do
let(:resource) { create(:group, avatar: File.open(uploaded_image_temp_path)) }
let(:helper_args) { [resource] }
end
end
context 'when providing a topic' do
it_behaves_like 'resource with a default avatar', 'topic' do
let(:resource) { create(:topic, name: 'foo') }
let(:helper_args) { [resource] }
end
it_behaves_like 'resource with a custom avatar', 'topic' do
let(:resource) { create(:topic, avatar: File.open(uploaded_image_temp_path)) }
let(:helper_args) { [resource] }
end
end
end
describe '#avatar_icon_for' do
let!(:user) { create(:user, avatar: File.open(uploaded_image_temp_path), email: 'bar@example.com') }
let(:email) { 'foo@example.com' }

View File

@ -669,6 +669,216 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
end
end
describe 'scopes' do
shared_examples 'includes projects in hierarchy marked for deletion' do
let_it_be(:group) { create(:group) }
let_it_be(:active_project) { create(:project, group: group) }
let_it_be(:for_deletion_project) { create(:project, group: group, marked_for_deletion_at: Date.current) }
context 'when parent group is active' do
it 'returns only projects marked for deletion' do
expect(subject).to include(for_deletion_project)
expect(subject).not_to include(active_project)
end
end
context 'when parent group is marked for deletion' do
let_it_be(:group_deletion_schedule) { create(:group_deletion_schedule, group: group) }
it 'returns all projects in the group' do
expect(subject).to include(for_deletion_project, active_project)
end
end
end
shared_examples 'excludes projects in hierarchy marked for deletion' do
let_it_be(:group) { create(:group) }
let_it_be(:active_project) { create(:project, group: group) }
let_it_be(:for_deletion_project) { create(:project, group: group, marked_for_deletion_at: Date.current) }
context 'when parent group is active' do
it 'returns only active projects' do
expect(subject).to include(active_project)
expect(subject).not_to include(for_deletion_project)
end
end
context 'when parent group is marked for deletion' do
let_it_be(:group_deletion_schedule) { create(:group_deletion_schedule, group: group) }
it 'excludes all projects in the group' do
expect(subject).not_to include(for_deletion_project, active_project)
end
end
end
shared_examples 'includes projects in archived hierarchy' do
let_it_be(:group) { create(:group) }
let_it_be(:active_project) { create(:project, group: group) }
let_it_be(:archived_project) { create(:project, group: group, archived: true) }
context 'when parent group is active' do
it 'returns only archived projects' do
expect(subject).to include(archived_project)
expect(subject).not_to include(active_project)
end
end
context 'when parent group is archived' do
before do
group.archive
end
it 'returns all projects in the group' do
expect(subject).to include(archived_project, active_project)
end
end
end
shared_examples 'excludes projects in archived hierarchy' do
let_it_be(:group) { create(:group) }
let_it_be(:active_project) { create(:project, group: group) }
let_it_be(:archived_project) { create(:project, group: group, archived: true) }
context 'when parent group is active' do
it 'returns only active projects' do
expect(subject).to include(active_project)
expect(subject).not_to include(archived_project)
end
end
context 'when parent group is archived' do
before do
group.archive
end
it 'excludes all projects in the group' do
expect(subject).not_to include(archived_project, active_project)
end
end
end
describe '.aimed_for_deletion' do
let_it_be(:active_project) { create(:project) }
let_it_be(:for_deletion_project) { create(:project, marked_for_deletion_at: Date.current) }
it 'returns projects marked for deletion' do
result = described_class.aimed_for_deletion
expect(result).to include(for_deletion_project)
expect(result).not_to include(active_project)
end
end
describe '.self_or_ancestors_aimed_for_deletion' do
subject { described_class.self_or_ancestors_aimed_for_deletion }
it_behaves_like 'includes projects in hierarchy marked for deletion'
end
describe '.not_aimed_for_deletion' do
let_it_be(:active_project) { create(:project) }
let_it_be(:for_deletion_project) { create(:project, marked_for_deletion_at: Date.current) }
it 'returns projects not marked for deletion' do
result = described_class.not_aimed_for_deletion
expect(result).to include(active_project)
expect(result).not_to include(for_deletion_project)
end
end
describe '.self_and_ancestors_not_aimed_for_deletion' do
subject { described_class.self_and_ancestors_not_aimed_for_deletion }
it_behaves_like 'excludes projects in hierarchy marked for deletion'
end
describe '.marked_for_deletion_on' do
let_it_be(:active_project) { create(:project) }
let_it_be(:for_deletion_project) { create(:project, marked_for_deletion_at: Date.parse('2024-01-01')) }
context 'when date is provided' do
it 'returns projects marked for deletion on that date' do
result = described_class.marked_for_deletion_on(Date.parse('2024-01-01'))
expect(result).to contain_exactly(for_deletion_project)
end
end
context 'when date is nil' do
it 'returns projects not marked for deletion' do
result = described_class.marked_for_deletion_on(nil)
expect(result).to contain_exactly(active_project)
end
end
end
describe '.marked_for_deletion_before' do
let_it_be(:cutoff_date) { 10.days.ago }
let_it_be(:active_project) { create(:project) }
let_it_be(:marked_after) { create(:project, marked_for_deletion_at: cutoff_date + 2.days) }
let_it_be(:marked_before) { create(:project, marked_for_deletion_at: cutoff_date - 2.days) }
let_it_be(:marked_on_date) { create(:project, marked_for_deletion_at: cutoff_date) }
it 'returns projects marked for deletion on or before the specified date' do
result = described_class.marked_for_deletion_before(cutoff_date)
expect(result).to include(marked_before, marked_on_date)
expect(result).not_to include(marked_after, active_project)
end
end
describe '.archived' do
let_it_be(:active_project) { create(:project, archived: false) }
let_it_be(:archived_project) { create(:project, archived: true) }
it 'returns archived projects' do
result = described_class.archived
expect(result).to include(archived_project)
expect(result).not_to include(active_project)
end
end
describe '.self_or_ancestors_archived' do
subject { described_class.self_or_ancestors_archived }
it_behaves_like 'includes projects in archived hierarchy'
end
describe '.non_archived' do
let_it_be(:active_project) { create(:project, archived: false) }
let_it_be(:archived_project) { create(:project, archived: true) }
it 'returns non-archived projects' do
result = described_class.non_archived
expect(result).to include(active_project)
expect(result).not_to include(archived_project)
end
end
describe '.self_and_ancestors_non_archived' do
subject { described_class.self_and_ancestors_non_archived }
it_behaves_like 'excludes projects in archived hierarchy'
end
describe '.self_and_ancestors_active' do
subject { described_class.self_and_ancestors_active }
it_behaves_like 'excludes projects in archived hierarchy'
it_behaves_like 'excludes projects in hierarchy marked for deletion'
end
describe '.self_or_ancestors_inactive' do
subject { described_class.self_or_ancestors_inactive }
it_behaves_like 'includes projects in archived hierarchy'
it_behaves_like 'includes projects in hierarchy marked for deletion'
end
end
describe 'modules' do
subject { described_class }
@ -2454,32 +2664,6 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
end
end
describe '.not_aimed_for_deletion' do
let_it_be(:project) { create(:project) }
let_it_be(:delayed_deletion_project) { create(:project, marked_for_deletion_at: Date.current) }
it do
expect(described_class.not_aimed_for_deletion).to contain_exactly(project)
end
end
describe '.by_marked_for_deletion_on' do
let_it_be(:project) { create(:project) }
let_it_be(:marked_for_deletion_project) { create(:project, marked_for_deletion_at: Date.parse('2024-01-01')) }
context 'when marked_for_deletion_on is present' do
it 'return projects marked for deletion' do
expect(described_class.by_marked_for_deletion_on(Date.parse('2024-01-01'))).to contain_exactly(marked_for_deletion_project)
end
end
context 'when marked_for_deletion_on is not present' do
it 'return projects not marked for deletion' do
expect(described_class.by_marked_for_deletion_on(nil)).to contain_exactly(project)
end
end
end
describe '.sorted_by_similarity_desc' do
let_it_be(:project_a) { create(:project, path: 'similar-1', name: 'similar-1', description: 'A similar project') }
let_it_be_with_reload(:project_b) { create(:project, path: 'similar-2', name: 'similar-2', description: 'A related project') }

View File

@ -720,45 +720,6 @@ RSpec.describe 'container repository details', feature_category: :container_regi
}
)
end
context 'when there is an immutable rule' do
before_all do
create(
:container_registry_protection_tag_rule,
:immutable,
project: project,
tag_name_pattern: 'la'
)
end
it 'returns the maximum access fields from the matching protection rules' do
subject
expect(tag_permissions_response).to eq(
{
'minimumAccessLevelForPush' => nil,
'minimumAccessLevelForDelete' => nil
}
)
end
context 'when the feature container_registry_immutable_tags is disabled' do
before do
stub_feature_flags(container_registry_immutable_tags: false)
end
it 'ignores the immutable rule' do
subject
expect(tag_permissions_response).to eq(
{
'minimumAccessLevelForPush' => 'OWNER',
'minimumAccessLevelForDelete' => 'OWNER'
}
)
end
end
end
end
context 'for tags destroyContainerRepositoryTag field' do

View File

@ -83,16 +83,16 @@ RSpec.describe 'Creating the container registry tag protection rule', :aggregate
it_behaves_like 'returning a GraphQL error', [/minimumAccessLevelForPush/, /minimumAccessLevelForDelete/]
end
context 'with blank input for the field `minimumAccessLevelForPush`' do
context 'with blank input for the field `minimumAccessLevelForPush`', unless: Gitlab.ee? do
let(:input) { super().merge(minimum_access_level_for_push: nil) }
it_behaves_like 'returning a mutation error', 'Access levels should either both be present or both be nil'
it_behaves_like 'returning a GraphQL error', /minimumAccessLevelForPush/
end
context 'with blank input for the field `minimumAccessLevelForDelete`' do
context 'with blank input for the field `minimumAccessLevelForDelete`', unless: Gitlab.ee? do
let(:input) { super().merge(minimum_access_level_for_delete: nil) }
it_behaves_like 'returning a mutation error', 'Access levels should either both be present or both be nil'
it_behaves_like 'returning a GraphQL error', /minimumAccessLevelForDelete/
end
context 'with blank input field `tagNamePattern`' do

View File

@ -38,20 +38,19 @@ RSpec.describe Mutations::ContainerRegistry::Protection::TagRule::Delete, :aggre
it 'responds with deleted container registry tag protection rule' do
expect { post_graphql_mutation_request }
.to change { ::ContainerRegistry::Protection::TagRule.count }.from(1).to(0)
.to change { ::ContainerRegistry::Protection::TagRule.count }.by(-1)
expect(mutation_response).to include(
'errors' => be_blank,
'containerProtectionTagRule' => {
'containerProtectionTagRule' => hash_including(
'id' => container_protection_rule.to_global_id.to_s,
'tagNamePattern' => container_protection_rule.tag_name_pattern,
'minimumAccessLevelForDelete' => container_protection_rule.minimum_access_level_for_delete.upcase,
'minimumAccessLevelForPush' => container_protection_rule.minimum_access_level_for_push.upcase,
'immutable' => container_protection_rule.immutable?,
'userPermissions' => {
'destroyContainerRegistryProtectionTagRule' => true
}
}
)
)
end

View File

@ -13,10 +13,6 @@ RSpec.describe 'Updating the container registry tag protection rule', :aggregate
let_it_be(:current_user) { create(:user, maintainer_of: project) }
let(:container_protection_tag_rule_attributes) do
build_stubbed(:container_protection_tag_rule, project: project)
end
let(:mutation) do
graphql_mutation(:update_container_protection_tag_rule, input,
<<~QUERY
@ -68,8 +64,8 @@ RSpec.describe 'Updating the container registry tag protection rule', :aggregate
post_graphql_mutation_request.tap do
expect(container_protection_tag_rule.reload).to have_attributes(
tag_name_pattern: input[:tag_name_pattern],
minimum_access_level_for_push: input[:minimum_access_level_for_push]&.downcase,
minimum_access_level_for_delete: input[:minimum_access_level_for_delete]&.downcase
minimum_access_level_for_push: input[:minimum_access_level_for_push].downcase,
minimum_access_level_for_delete: input[:minimum_access_level_for_delete].downcase
)
end
end
@ -121,12 +117,6 @@ RSpec.describe 'Updating the container registry tag protection rule', :aggregate
it_behaves_like 'returning a GraphQL error', /tagNamePattern can't be blank/
end
context 'with blank input fields `minimumAccessLevelForPush` and `minimumAccessLevelForDelete`' do
let(:input) { super().merge(minimum_access_level_for_push: nil, minimum_access_level_for_delete: nil) }
it_behaves_like 'a successful response'
end
context 'with only `minimumAccessLevelForDelete` blank' do
let(:input) { super().merge(minimum_access_level_for_delete: nil) }

View File

@ -0,0 +1,91 @@
# frozen_string_literal: true
require 'rubocop_spec_helper'
require 'rubocop/rspec/support'
require_relative '../../../../rubocop/cop/gitlab/disallow_current_organization_id_safe_navigation'
RSpec.describe RuboCop::Cop::Gitlab::DisallowCurrentOrganizationIdSafeNavigation, feature_category: :organization do
include RuboCop::RSpec::ExpectOffense
let(:hardcoded_expected_message) do
'Use `Current.organization.id` instead of `Current.organization&.id`. ' \
'`Current.organization` is expected to be assigned.'
end
context 'when `Current.organization&.id` is used' do
it 'registers an offense and autocorrects `Current.organization&.id`' do
expect_offense(<<~RUBY)
id = Current.organization&.id
^^^^^^^^^^^^^^^^^^^^^^^^ #{hardcoded_expected_message}
RUBY
expect_correction(<<~RUBY)
id = Current.organization.id
RUBY
end
# This is the test case that was failing due to the spec's conditional logic.
# Simplify it as follows:
it 'registers an offense and autocorrects `::Current.organization&.id` (top-level constant)' do
expect_offense(<<~RUBY)
id = ::Current.organization&.id
^^^^^^^^^^^^^^^^^^^^^^^^^^ #{hardcoded_expected_message}
RUBY
expect_correction(<<~RUBY)
id = ::Current.organization.id
RUBY
end
it 'registers an offense and autocorrects when used in a condition' do
expect_offense(<<~RUBY)
if Current.organization&.id == 5
^^^^^^^^^^^^^^^^^^^^^^^^ #{hardcoded_expected_message}
end
RUBY
expect_correction(<<~RUBY)
if Current.organization.id == 5
end
RUBY
end
end
context 'when related but non-offending patterns are used' do
it 'does not register an offense for `Current.organization.id` (no safe navigation)' do
expect_no_offenses(<<~RUBY)
id = Current.organization.id
RUBY
end
it 'does not register an offense for `other_object.organization&.id`' do
expect_no_offenses(<<~RUBY)
id = other_object.organization&.id
RUBY
end
it 'does not register an offense for `Current.other_method&.id`' do
expect_no_offenses(<<~RUBY)
id = Current.other_method&.id
RUBY
end
it 'does not register an offense for `Current.organization&.other_attribute`' do
expect_no_offenses(<<~RUBY)
id = Current.organization&.other_attribute
RUBY
end
it 'does not register an offense for just `Current.organization`' do
expect_no_offenses(<<~RUBY)
org = Current.organization
RUBY
end
it 'does not register an offense for a different safe navigation chain' do
expect_no_offenses(<<~RUBY)
name = Current.user&.name
RUBY
end
end
end