diff --git a/doc/user/admin_area/settings/visibility_and_access_controls.md b/doc/user/admin_area/settings/visibility_and_access_controls.md index a0acc70829d673400661ebb6c4a944e83eda5c55..8a9db68b34f58195a57b9c42a6025d8a0a6ce38d 100644 --- a/doc/user/admin_area/settings/visibility_and_access_controls.md +++ b/doc/user/admin_area/settings/visibility_and_access_controls.md @@ -52,6 +52,7 @@ By default both administrators and anyone with the **Owner** role can delete a p > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/255449) in GitLab 14.2 for groups created after August 12, 2021. > - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/352960) from default delayed project deletion in GitLab 15.1. +> - [Enabled for projects in personal namespaces](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89466) in GitLab 15.1. Instance-level protection against accidental deletion of groups and projects. diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index b1514203f4b6ed4f7cd5669a59bb9b52d840bace..46a6c1a049edcbfae52920e876be3c4961f7dc32 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -455,7 +455,9 @@ in GitLab 12.6, and then to [immediate deletion](https://gitlab.com/gitlab-org/g ### Delayed project deletion **(PREMIUM)** -Projects in a group (not a personal namespace) can be deleted after a delay period. Multiple settings can affect whether +> [Enabled for projects in personal namespaces](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89466) in GitLab 15.1. + +Projects can be deleted after a delay period. Multiple settings can affect whether delayed project deletion is enabled for a particular project: - Self-managed instance [settings](../../admin_area/settings/visibility_and_access_controls.md#deletion-protection). diff --git a/ee/app/models/ee/project.rb b/ee/app/models/ee/project.rb index 4a358329f501d2537ae4fead64f42dd046562e14..64b836f5f7e8dabe95e27258d2cd783c45d12c41 100644 --- a/ee/app/models/ee/project.rb +++ b/ee/app/models/ee/project.rb @@ -137,7 +137,7 @@ def lock_for_confirmation!(id) scope :verification_failed_wikis, -> { joins(:repository_state).merge(ProjectRepositoryState.verification_failed_wikis) } scope :for_plan_name, -> (name) { joins(namespace: { gitlab_subscription: :hosted_plan }).where(plans: { name: name }) } scope :with_feature_available, -> (name) do - projects_with_feature_available_in_plan = ::Project.for_group(::Group.with_feature_available_in_plan(name)) + projects_with_feature_available_in_plan = ::Project.in_namespace(::Namespace.with_feature_available_in_plan(name)) public_projects_in_public_groups = ::Project.public_only.for_group(::Group.public_only) from_union([projects_with_feature_available_in_plan, public_projects_in_public_groups]) end @@ -949,13 +949,16 @@ def requirements_ci_variables end end - # Return the group's setting for delayed deletion, false for user namespace - # projects - # + # Return the group's setting for delayed deletion def group_deletion_mode_configured? - return false unless group&.namespace_settings - - group.namespace_settings.delayed_project_removal? + if group&.namespace_settings + group.namespace_settings.delayed_project_removal? + elsif namespace&.namespace_settings + # for projects under user namespace + namespace.namespace_settings.delayed_project_removal? + else + false + end end def latest_ingested_security_pipeline diff --git a/ee/spec/controllers/projects_controller_spec.rb b/ee/spec/controllers/projects_controller_spec.rb index 0a685533523ed5638a8374460a45fb8e496aaa88..d288aac225b9478a40935bb589f1d8ab066631ef 100644 --- a/ee/spec/controllers/projects_controller_spec.rb +++ b/ee/spec/controllers/projects_controller_spec.rb @@ -791,7 +791,23 @@ context 'for projects in user namespace' do let(:project) { create(:project, namespace: user.namespace) } - it_behaves_like 'deletes project right away' + context 'when feature is enabled at instance level' do + before do + stub_application_setting(deletion_adjourned_period: 7) + stub_application_setting(delayed_project_removal: true) + end + + it_behaves_like 'marks project for deletion' + end + + context 'when feature is not enabled at instance level' do + before do + stub_application_setting(deletion_adjourned_period: 0) + stub_application_setting(delayed_project_removal: false) + end + + it_behaves_like 'deletes project right away' + end end end diff --git a/ee/spec/factories/namespaces/user_namespaces.rb b/ee/spec/factories/namespaces/user_namespaces.rb new file mode 100644 index 0000000000000000000000000000000000000000..a0e4a9234073941f9828e5cb420167a7e97ef6af --- /dev/null +++ b/ee/spec/factories/namespaces/user_namespaces.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +FactoryBot.modify do + factory :user_namespace do + trait :user_namespace_with_plan do + transient do + plan { :free_plan } + trial_ends_on { nil } + end + + after(:create) do |user_namespace, evaluator| + if evaluator.plan + create(:namespace_settings, namespace: user_namespace) + + create(:gitlab_subscription, + namespace: user_namespace, + hosted_plan: create(evaluator.plan), + trial: evaluator.trial_ends_on.present?, + trial_ends_on: evaluator.trial_ends_on) + end + end + end + end +end diff --git a/ee/spec/models/project_spec.rb b/ee/spec/models/project_spec.rb index 7f77d011574523164dcb271aff865dee2711f0b3..dc3f4ff52959b3c1be263b41bf3ea1748c1fba86 100644 --- a/ee/spec/models/project_spec.rb +++ b/ee/spec/models/project_spec.rb @@ -456,9 +456,13 @@ premium_project = create(:project, :archived, creator: user, namespace: premium_group) no_plan_project = create(:project, :archived, creator: user, namespace: no_plan_group) no_plan_public_project = create(:project, :archived, creator: user, visibility: ::Gitlab::VisibilityLevel::PUBLIC, namespace: no_plan_group) + free_user_namespace = create(:user_namespace, :user_namespace_with_plan, plan: :free_plan) + ultimate_user_namespace = create(:user_namespace, :user_namespace_with_plan, plan: :ultimate_plan) + free_personal_project = create(:project, :archived, creator: user, namespace: free_user_namespace) + ultimate_personal_project = create(:project, :archived, creator: user, namespace: ultimate_user_namespace) - expect(described_class.with_feature_available(:adjourned_deletion_for_projects_and_groups)).to contain_exactly(premium_project, ultimate_project, no_plan_public_project) - expect(described_class.with_feature_available(:adjourned_deletion_for_projects_and_groups)).not_to include(no_plan_project) + expect(described_class.with_feature_available(:adjourned_deletion_for_projects_and_groups)).to contain_exactly(premium_project, ultimate_project, no_plan_public_project, ultimate_personal_project) + expect(described_class.with_feature_available(:adjourned_deletion_for_projects_and_groups)).not_to include(no_plan_project, free_personal_project) end end end