diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 30bd8c1e716bae584e9a5ee3bc0fae6aac360c5d..330747e8baf6411deab9768ffd357aaa2e4bfaec 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -176,11 +176,9 @@ def destroy ::Gitlab::Utils.to_boolean(params.permit(:permanently_remove)[:permanently_remove]) # Admin frontend uses this endpoint to force-delete groups - if Feature.enabled?(:disallow_immediate_deletion, current_user) && !current_user.can_admin_all_resources? - return access_denied! - end + return destroy_immediately if Gitlab::CurrentSettings.allow_immediate_namespaces_deletion_for_user?(current_user) - return destroy_immediately + return access_denied! end result = ::Groups::MarkForDeletionService.new(group, current_user).execute diff --git a/app/controllers/organizations/groups_controller.rb b/app/controllers/organizations/groups_controller.rb index ffd3ad0a08906cbc9123eb6497e2e7cdb271f209..5fbabeb03e6315a887d46647eda27404723b0be6 100644 --- a/app/controllers/organizations/groups_controller.rb +++ b/app/controllers/organizations/groups_controller.rb @@ -31,9 +31,11 @@ def destroy if group.self_deletion_scheduled? && ::Gitlab::Utils.to_boolean(params.permit(:permanently_remove)[:permanently_remove]) - return access_denied! if Feature.enabled?(:disallow_immediate_deletion, current_user) + if Gitlab::CurrentSettings.allow_immediate_namespaces_deletion_for_user?(current_user) + return destroy_immediately + end - return destroy_immediately + return access_denied! end result = ::Groups::MarkForDeletionService.new(group, current_user).execute diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 15778d7f793c53efd00155884f64617fb4d37dc8..d945fd380cc27aba2d58b1f7d3ace8683518153b 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -195,9 +195,9 @@ def destroy if @project.self_deletion_scheduled? && ::Gitlab::Utils.to_boolean(params.permit(:permanently_delete)[:permanently_delete]) - return access_denied! if Feature.enabled?(:disallow_immediate_deletion, current_user) + return destroy_immediately if Gitlab::CurrentSettings.allow_immediate_namespaces_deletion_for_user?(current_user) - return destroy_immediately + return access_denied! end result = ::Projects::MarkForDeletionService.new(@project, current_user).execute diff --git a/app/helpers/namespaces/deletable_helper.rb b/app/helpers/namespaces/deletable_helper.rb index 9530fe95e9508a13b96a49307aa2829ca255ed01..8dfcd71793bd1e60d78ba50f47125784fc444cea 100644 --- a/app/helpers/namespaces/deletable_helper.rb +++ b/app/helpers/namespaces/deletable_helper.rb @@ -123,7 +123,7 @@ def group_confirm_modal_data( has_security_policy_project: false) { remove_form_id: remove_form_id, - button_text: button_text.nil? ? _('Delete group') : button_text, + button_text: button_text.nil? ? _('Delete') : button_text, button_testid: 'remove-group-button', disabled: (group.linked_to_subscription? || has_security_policy_project).to_s, confirm_danger_message: confirm_remove_group_message(group, permanently_remove), @@ -143,16 +143,16 @@ def confirm_remove_group_message(group, permanently_remove) ) end - def project_delete_delayed_button_data(project, button_text = nil) - _project_delete_button_shared_data(project, button_text).merge({ + def project_delete_delayed_button_data(project) + _project_delete_button_shared_data(project).merge({ restore_help_path: help_page_path('user/project/working_with_projects.md', anchor: 'restore-a-project'), delayed_deletion_date: permanent_deletion_date_formatted, form_path: project_path(project) }) end - def project_delete_immediately_button_data(project, button_text = nil) - _project_delete_button_shared_data(project, button_text).merge({ + def project_delete_immediately_button_data(project) + _project_delete_button_shared_data(project, s_('ProjectSettings|Delete immediately')).merge({ form_path: project_path(project, permanently_delete: true) }) end @@ -248,7 +248,7 @@ def _project_delete_button_shared_data(project, button_text = nil) merge_requests_count: number_with_delimiter(merge_requests_count), forks_count: number_with_delimiter(forks_count), stars_count: number_with_delimiter(project.star_count), - button_text: button_text.presence || _('Delete project') + button_text: button_text.presence || _('Delete') } end end diff --git a/app/views/groups/settings/_delayed_deletion.html.haml b/app/views/groups/settings/_delayed_deletion.html.haml index 4acb7b454d304e0adb9574b864c870a8a903b721..f9b6c901013a0002f55cc628cb6b30f1973e26df 100644 --- a/app/views/groups/settings/_delayed_deletion.html.haml +++ b/app/views/groups/settings/_delayed_deletion.html.haml @@ -2,8 +2,7 @@ = render Pajamas::CardComponent.new(body_options: { class: 'gl-bg-feedback-danger' }) do |c| - c.with_header do - .gl-flex.gl-grow - %h4.gl-text-base.gl-leading-24.gl-m-0.gl-text-feedback-danger= _('Delete group') + %h4.gl-text-base.gl-leading-24.gl-m-0= _('Delete group') - c.with_body do = form_tag(group, method: :delete, id: remove_form_id) do diff --git a/app/views/groups/settings/_immediately_remove.html.haml b/app/views/groups/settings/_immediately_remove.html.haml index 91ef408aff1bce2208125e078fd21bc2756f253f..7adb69486d6a40a95f1cd534a59fa05d7a9918ce 100644 --- a/app/views/groups/settings/_immediately_remove.html.haml +++ b/app/views/groups/settings/_immediately_remove.html.haml @@ -1,17 +1,23 @@ -- return if Feature.enabled?(:disallow_immediate_deletion, current_user) -- return unless group.self_deletion_scheduled? +- return unless can?(current_user, :remove_group, group) +- return unless group.scheduled_for_deletion_in_hierarchy_chain? - remove_form_id = local_assigns.fetch(:remove_form_id, nil) +- allow_immediate_namespaces_deletion = Gitlab::CurrentSettings.allow_immediate_namespaces_deletion_for_user?(current_user) +- immediate_deletion_available = group.self_deletion_scheduled? && allow_immediate_namespaces_deletion = render Pajamas::CardComponent.new(body_options: { class: 'gl-bg-feedback-danger' }) do |c| - c.with_header do - .gl-flex.gl-grow - %h4.gl-text-base.gl-leading-24.gl-m-0.gl-text-feedback-danger= _('Delete group immediately') + %h4.gl-text-base.gl-leading-24.gl-m-0 + = s_('GroupSettings|Delete group') - c.with_body do - = form_tag(group, method: :delete, id: remove_form_id) do - %p= delete_immediately_namespace_scheduled_for_deletion_message(group) + - if immediate_deletion_available + = form_tag(group, method: :delete, id: remove_form_id) do + %p= delete_immediately_namespace_scheduled_for_deletion_message(group) + = hidden_field_tag(:permanently_remove, true) + = render 'groups/settings/remove_button', group: group, remove_form_id: remove_form_id, button_text: s_('GroupSettings|Delete immediately') - = hidden_field_tag(:permanently_remove, true) - - = render 'groups/settings/remove_button', group: group, remove_form_id: remove_form_id, button_text: _('Delete group immediately') + - else + -# Immediate deletion not available: either because of ancestor, or instance setting + %p.gl-m-0 + = self_or_ancestors_deletion_in_progress_or_scheduled_message(group) diff --git a/app/views/projects/_delete.html.haml b/app/views/projects/_delete.html.haml index a5765a77493fa6112f62644dd41f47111d632190..1e5acf338710a980fbd57dae930976ce57ceded5 100644 --- a/app/views/projects/_delete.html.haml +++ b/app/views/projects/_delete.html.haml @@ -1,17 +1,18 @@ - return unless can?(current_user, :remove_project, @project) -- return if Feature.enabled?(:disallow_immediate_deletion, current_user) && @project.self_deletion_scheduled? -- return if @project.ancestor_scheduled_for_deletion? -- title = @project.self_deletion_scheduled? ? _('Delete project immediately') : _('Delete project') +- allow_immediate_namespaces_deletion = Gitlab::CurrentSettings.allow_immediate_namespaces_deletion_for_user?(current_user) = render Pajamas::CardComponent.new(body_options: { class: 'gl-bg-feedback-danger' }) do |c| - c.with_header do - .gl-flex.gl-grow - %h4.gl-text-base.gl-leading-24.gl-m-0.gl-text-feedback-danger= title - + %h4.gl-text-base.gl-leading-24.gl-m-0 + = s_('ProjectSettings|Delete project') - c.with_body do - - if @project.self_deletion_scheduled? + - if @project.self_deletion_scheduled? && allow_immediate_namespaces_deletion %p= delete_immediately_namespace_scheduled_for_deletion_message(@project) - #js-project-delete-button{ data: project_delete_immediately_button_data(@project, _('Delete project immediately')) } + #js-project-delete-button{ data: project_delete_immediately_button_data(@project) } + - elsif @project.scheduled_for_deletion_in_hierarchy_chain? + -# Immediate deletion not available: either because of ancestor, or instance setting + %p.gl-m-0 + = self_or_ancestors_deletion_in_progress_or_scheduled_message(@project) - else = render 'delete_delayed' diff --git a/config/feature_flags/wip/allow_immediate_namespaces_deletion.yml b/config/feature_flags/gitlab_com_derisk/allow_immediate_namespaces_deletion.yml similarity index 74% rename from config/feature_flags/wip/allow_immediate_namespaces_deletion.yml rename to config/feature_flags/gitlab_com_derisk/allow_immediate_namespaces_deletion.yml index 2ff9d65b00307226617e5df4fd19ec239e941afb..385199896287249bc9ba8271c7ac084f16f02efc 100644 --- a/config/feature_flags/wip/allow_immediate_namespaces_deletion.yml +++ b/config/feature_flags/gitlab_com_derisk/allow_immediate_namespaces_deletion.yml @@ -3,8 +3,8 @@ name: allow_immediate_namespaces_deletion description: feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/569453 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/205554 -rollout_issue_url: +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/576726 milestone: '18.5' group: group::organizations -type: wip +type: gitlab_com_derisk default_enabled: false diff --git a/config/feature_flags/gitlab_com_derisk/disallow_immediate_deletion.yml b/config/feature_flags/gitlab_com_derisk/disallow_immediate_deletion.yml deleted file mode 100644 index 9a11c78ca5c13621acfc9d50bf96e09113cffdf9..0000000000000000000000000000000000000000 --- a/config/feature_flags/gitlab_com_derisk/disallow_immediate_deletion.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: disallow_immediate_deletion -description: -feature_issue_url: -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/201957 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/562308 -milestone: '18.4' -group: group::organizations -type: gitlab_com_derisk -default_enabled: false diff --git a/doc/administration/settings/visibility_and_access_controls.md b/doc/administration/settings/visibility_and_access_controls.md index 9d888d673e6daa6a951c2755a916600a597a9a57..dbe808b06371362f9278bfc995daaff80caea466 100644 --- a/doc/administration/settings/visibility_and_access_controls.md +++ b/doc/administration/settings/visibility_and_access_controls.md @@ -102,6 +102,13 @@ To disable the restriction: {{< /history >}} +{{< alert type="flag" >}} + +The availability of this feature is controlled by a feature flag. +For more information, see the history. + +{{< /alert >}} + These protections help guard against accidental deletion of groups and projects on your instance. ### Retention period diff --git a/doc/user/group/_index.md b/doc/user/group/_index.md index 48c01c56ebcdceb2c9ea64b0c5bd7177711a793c..cddbb5ebb52afcba543396761c73c4b3828558bd 100644 --- a/doc/user/group/_index.md +++ b/doc/user/group/_index.md @@ -124,6 +124,33 @@ This page shows groups that you are a member of through: - Membership of a subgroup's parent group. - Direct or inherited membership of a project in the group or subgroup. +### View inactive groups + +{{< history >}} + +- **Inactive** tab [introduced](https://gitlab.com/groups/gitlab-org/-/epics/13781) in GitLab 18.2 [with a flag](../../administration/feature_flags/_index.md) named `your_work_groups_vue`. Disabled by default. +- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/542790) in GitLab 18.3. Feature flag `your_work_groups_vue` removed. + +{{< /history >}} + +A group is inactive when it is either pending deletion or it has been archived. + +To view all inactive groups: + +1. On the left sidebar, select **Search or go to** and find your group. +1. Select **View all my groups**. +1. Select the **Inactive** tab. + +Each inactive group in the list displays a badge to indicate that the group is either +archived or pending deletion. + +If the group is pending deletion, the list also shows: + +- The time the group is scheduled for final deletion. +- A **Restore** action. When you restore a group: + - The **Pending deletion** label is removed. The group is no longer scheduled for deletion. + - The group is removed from the **Inactive** tab. + ## View a group {{< history >}} @@ -249,13 +276,21 @@ To leave a group: {{< /history >}} +By default, when you delete a group for the first time, it enters a pending deletion state. +Delete a group again to remove it immediately. + +Prerequisites: + +- You must have the Owner role for a group. +- If the groups contains any project, owners must be [allowed to delete projects](../../administration/settings/visibility_and_access_controls.md#restrict-project-deletion-to-administrators). + To delete a group and its contents: 1. On the left sidebar, select **Search or go to** and find your group. 1. Select **Settings** > **General**. 1. Expand the **Advanced** section. -1. In the **Delete group** section, select **Delete group**. -1. On the confirmation dialog, type the group name and select **Confirm**. +1. In the **Delete group** section, select **Delete**. +1. On the confirmation dialog, enter the group name and select **Confirm**. You can also delete a group from the groups dashboard: @@ -263,8 +298,7 @@ You can also delete a group from the groups dashboard: 1. Select **View all my groups**. 1. Select ({{< icon name="ellipsis_v" >}}) for the group you want to delete. 1. Select **Delete**. -1. In the **Delete group** section, select **Delete group**. -1. On the confirmation dialog, type the group name and select **Confirm**. +1. On the confirmation dialog, enter the group name and select **Confirm**. This action adds a background job to mark a group for deletion. On GitLab.com, the group is deleted after 30 days. On GitLab Self-Managed, you can modify the retention period through the [instance settings](../../administration/settings/visibility_and_access_controls.md#deletion-protection). @@ -272,43 +306,26 @@ you can modify the retention period through the [instance settings](../../admini If the user who scheduled the group deletion loses access to the group (for example, by leaving the group, having their role downgraded, or being banned from the group) before the deletion occurs, the deletion job instead restores the group, and the group is no longer scheduled for deletion. - {{< alert type="warning" >}} - - If the user who scheduled the group deletion regains Owner role or administrator access before the job runs, then the job removes the group permanently. - - {{< /alert >}} - -### View groups pending deletion - -{{< history >}} - -- **Inactive** tab [introduced](https://gitlab.com/groups/gitlab-org/-/epics/13781) in GitLab 18.2 [with a flag](../../administration/feature_flags/_index.md) named `your_work_groups_vue`. Disabled by default. -- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/542790) in GitLab 18.3. Feature flag `your_work_groups_vue` removed. - -{{< /history >}} - -To view a list of the groups that are pending deletion: +{{< alert type="warning" >}} -1. On the left sidebar, select **Search or go to** and find your group. -1. Select **View all my groups**. -1. Select the **Inactive** tab. +If the user who scheduled the group deletion regains Owner role or administrator access before the job runs, then the job removes the group permanently. -Groups that are marked for deletion are labeled **Pending deletion**. +{{< /alert >}} ## Delete a group immediately {{< history >}} - Enabled delayed deletion by default [on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/393622) and [on GitLab Self-Managed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119606) in GitLab 16.0. -- [Moved](https://gitlab.com/groups/gitlab-org/-/epics/17208) from GitLab Premium to GitLab Free in 18.0. -- Support for disallowing immediate deletion for groups or projects scheduled for deletion [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/201957) in GitLab 18.4 [with a flag](../../administration/feature_flags/_index.md) named `disallow_immediate_deletion`. Disabled by default. +- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/561680) in GitLab 18.4 [with a flag](../../administration/feature_flags/_index.md) named `disallow_immediate_deletion`. Disabled by default. +- [Replaced](https://gitlab.com/gitlab-org/gitlab/-/issues/569453) in GitLab 18.5 by an instance setting to allow immediate deletion of groups and projects scheduled for deletion. [Controlled by a flag](../../administration/feature_flags/_index.md) named `allow_immediate_namespaces_deletion`. Feature flag is disabled by default. {{< /history >}} -{{< alert type="flag" >}} +{{< alert type="warning" >}} -The availability of this feature is controlled by a feature flag. -For more information, see the history. +On GitLab.com and GitLab Dedicated, after a group is deleted, its data is retained for 30 days, and immediate deletion is not available. +If you must delete a group immediately on GitLab.com, you can open a [support ticket](https://about.gitlab.com/support/). {{< /alert >}} @@ -324,7 +341,7 @@ To immediately delete a group marked for deletion: 1. On the left sidebar, select **Search or go to** and find your group. 1. Select **Settings** > **General**. 1. Expand **Advanced**. -1. In the **Permanently delete group** section, select **Delete group**. +1. In the **Delete group** section, select **Delete immediately**. 1. Confirm the action when asked to. This action deletes the group, its subgroups, projects, and all related resources, including issues and merge requests. diff --git a/doc/user/project/working_with_projects.md b/doc/user/project/working_with_projects.md index a80d79f5c7f1b0847270a908e88147d4f1302e74..f7ca8dd0b1a14726198a8ad393bbb2e091494a0f 100644 --- a/doc/user/project/working_with_projects.md +++ b/doc/user/project/working_with_projects.md @@ -137,10 +137,7 @@ You can also view your starred and personal projects from your personal profile: {{< /history >}} -A project is inactive when: - -- It is pending deletion. -- It has been archived. +A project is inactive when it is either pending deletion or it has been archived. To view all inactive projects: @@ -149,13 +146,15 @@ To view all inactive projects: - **Explore**, to filter all projects you can access. 1. Select the **Inactive** tab. -Each project in the list shows: +Each inactive project in the list displays a badge to indicate that the project is either +archived or pending deletion. + +If the project is pending deletion, the list also shows: -- A badge indicating that the project is archived or marked for deletion. - If the project is marked for deletion, the list also shows: - - The time the project was marked for deletion. - - The time the project is scheduled for final deletion. - - A **Restore** action to stop the project being eventually deleted. +- The time the project is scheduled for final deletion. +- A **Restore** action. When you restore a project: + - The **Pending deletion** label is removed. The project is no longer scheduled for deletion. + - The project is removed from the **Inactive** tab. ### View only projects you own @@ -330,26 +329,13 @@ To upload an avatar in your project settings: {{< history >}} - Default behavior [changed](https://gitlab.com/gitlab-org/gitlab/-/issues/389557) to delayed project deletion for Premium and Ultimate tiers on [GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/393622) and [GitLab Self-Managed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119606) in 16.0. -- Option to delete projects immediately as a group setting removed [on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/393622) and [on GitLab Self-Managed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119606) in GitLab 16.0. - Default behavior changed to delayed project deletion for [GitLab Free](https://gitlab.com/groups/gitlab-org/-/epics/17208) and [personal projects](https://gitlab.com/gitlab-org/gitlab/-/issues/536244) in 18.0. -- Option to delete projects immediately [moved](https://gitlab.com/groups/gitlab-org/-/epics/17208) from GitLab Premium to GitLab Free in 18.0. -- Support for disallowing immediate deletion for groups or projects scheduled for deletion [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/201957) in GitLab 18.4 [with a flag](../../administration/feature_flags/_index.md) named `disallow_immediate_deletion`. Disabled by default. {{< /history >}} -{{< alert type="flag" >}} - -The availability of this feature is controlled by a feature flag. -For more information, see the history. - -{{< /alert >}} - -You can schedule a project for deletion. By default, when you delete a project for the first time, it enters a pending deletion state. Delete a project again to remove it immediately. -On GitLab.com, after a project is deleted, its data is retained for 30 days. - Prerequisites: - You must have the Owner role for a project. @@ -360,16 +346,59 @@ To delete a project: 1. On the left sidebar, select **Search or go to** and find your project. 1. Select **Settings** > **General**. 1. Expand **Advanced**. -1. In the **Delete project** section, select **Delete project**. +1. In the **Delete project** section, select **Delete**. 1. On the confirmation dialog, enter the project name and select **Yes, delete project**. -1. Optional. To delete the project immediately, repeat these steps. -You can also [delete projects using the Rails console](troubleshooting.md#delete-a-project-using-console). +This action adds a background job to mark a project for deletion. On GitLab.com, the project is deleted after 30 days. On GitLab Self-Managed, +you can modify the retention period through the [instance settings](../../administration/settings/visibility_and_access_controls.md#deletion-protection). If the user who scheduled the project deletion loses access to the project before the deletion occurs (for example, by leaving the project, having their role downgraded, or being banned from the project), -the deletion job restores the project. However, if the user regains access before the deletion job runs, -the job removes the project permanently. +the deletion job instead restores the project, and the project is no longer scheduled for deletion. + +{{< alert type="warning" >}} + +If the user who scheduled the project deletion regains Owner role or administrator access before the job runs, then the job removes the project permanently. + +{{< /alert >}} + +You can also [delete projects using the Rails console](troubleshooting.md#delete-a-project-using-console). + +## Delete a project immediately + +{{< history >}} + +- Option to delete projects immediately as a group setting removed [on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/393622) and [on GitLab Self-Managed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119606) in GitLab 16.0. +- Option to delete projects immediately [moved](https://gitlab.com/groups/gitlab-org/-/epics/17208) from GitLab Premium to GitLab Free in 18.0. +- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/561680) in GitLab 18.4 [with a flag](../../administration/feature_flags/_index.md) named `disallow_immediate_deletion`. Disabled by default. +- [Replaced](https://gitlab.com/gitlab-org/gitlab/-/issues/569453) in GitLab 18.5 by an instance setting to allow immediate deletion of groups and projects scheduled for deletion. [Controlled by a flag](../../administration/feature_flags/_index.md) named `allow_immediate_namespaces_deletion`. Feature flag is disabled by default. + +{{< /history >}} + +{{< alert type="warning" >}} + +On GitLab.com and GitLab Dedicated, after a project is deleted, its data is retained for 30 days, and immediate deletion is not available. +If you must delete a project immediately on GitLab.com, you can open a [support ticket](https://about.gitlab.com/support/). + +{{< /alert >}} + +If you do not want to wait for the configured retention period to delete a project, +you can delete the project immediately. + +Prerequisites: + +- You must have the Owner role for a project. +- You have [scheduled the project for deletion](#delete-a-project). + +To immediately delete a project scheduled for deletion: + +1. On the left sidebar, select **Search or go to** and find your project. +1. Select **Settings** > **General**. +1. Expand **Advanced**. +1. In the **Delete project** section, select **Delete immediately**. +1. On the confirmation dialog, enter the project name and select **Confirm**. + +This action deletes the project and all related resources, including issues and merge requests. ### Restore a project diff --git a/ee/app/views/groups/settings/_delayed_deletion.html.haml b/ee/app/views/groups/settings/_delayed_deletion.html.haml index ae7933b8cce245096d900d707a0a530e4cc331e0..0d37da1846f505d1d4307240131004b4319b400d 100644 --- a/ee/app/views/groups/settings/_delayed_deletion.html.haml +++ b/ee/app/views/groups/settings/_delayed_deletion.html.haml @@ -5,7 +5,7 @@ = render Pajamas::CardComponent.new(body_options: { class: 'gl-bg-feedback-danger' }) do |c| - c.with_header do .gl-flex.gl-grow - %h4.gl-text-base.gl-leading-24.gl-m-0.gl-text-feedback-danger= _('Delete group') + %h4.gl-text-base.gl-leading-24.gl-m-0= _('Delete group') - c.with_body do = form_tag(group, method: :delete, id: remove_form_id) do diff --git a/ee/app/views/projects/_delete_delayed.html.haml b/ee/app/views/projects/_delete_delayed.html.haml index 5002ee7a0639603cd7003e35dd848308bdea508a..8648d1ddaf78f9c5163beff9b11e07926a439ede 100644 --- a/ee/app/views/projects/_delete_delayed.html.haml +++ b/ee/app/views/projects/_delete_delayed.html.haml @@ -10,4 +10,4 @@ %li = link_to(configuration.project&.name || configuration.namespace&.name, security_policies_path(configuration.project || configuration.namespace)) = render Pajamas::ButtonComponent.new(disabled: true) do - = _('Delete project') + = _('Delete') diff --git a/ee/spec/features/projects_spec.rb b/ee/spec/features/projects_spec.rb index 7d89802d98a117ae3ef65551afa6d008b4168f9f..3b663fc9867b35593bbb93d9f9dfe6acac12a2ed 100644 --- a/ee/spec/features/projects_spec.rb +++ b/ee/spec/features/projects_spec.rb @@ -29,7 +29,7 @@ def deletion_date expect(page).to have_content("This action will place this project, including all its resources, in a pending deletion state for #{deletion_adjourned_period} days, and delete it permanently on #{deletion_date}.") - click_button "Delete project" + click_button "Delete" expect(page).to have_content("This project can be restored until #{deletion_date}.") diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a91cbc02d073486f04210390af96e44b5f7856f9..de85a117598f011fd318f2c80dbb16cafc210dbd 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -22435,9 +22435,6 @@ msgstr "" msgid "Delete group" msgstr "" -msgid "Delete group immediately" -msgstr "" - msgid "Delete group immediately?" msgstr "" @@ -22465,9 +22462,6 @@ msgstr "" msgid "Delete project" msgstr "" -msgid "Delete project immediately" -msgstr "" - msgid "Delete release" msgstr "" @@ -32495,6 +32489,12 @@ msgstr "" msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group" msgstr "" +msgid "GroupSettings|Delete group" +msgstr "" + +msgid "GroupSettings|Delete immediately" +msgstr "" + msgid "GroupSettings|Disable personal access tokens" msgstr "" @@ -51635,6 +51635,12 @@ msgstr "" msgid "ProjectSettings|Data sources" msgstr "" +msgid "ProjectSettings|Delete immediately" +msgstr "" + +msgid "ProjectSettings|Delete project" +msgstr "" + msgid "ProjectSettings|Determine what happens to the commit history when you merge a merge request." msgstr "" diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 0f9d9a1504c87a69a04c4ba04fd4867376ad632c..598a845ed59b3bce8aca18e3208d205840ee03c5 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -633,7 +633,11 @@ context 'when permanently_remove param is set' do let(:params) { { permanently_remove: true } } - describe 'forbidden by the :disallow_immediate_deletion feature flag' do + describe 'when the :allow_immediate_namespaces_deletion application setting is false' do + before do + stub_application_setting(allow_immediate_namespaces_deletion: false) + end + it 'returns error' do Sidekiq::Testing.fake! do expect { subject }.not_to change { GroupDestroyWorker.jobs.size } @@ -656,32 +660,26 @@ end end - context 'when the :disallow_immediate_deletion feature flag is disabled' do - before do - stub_feature_flags(disallow_immediate_deletion: false) - end - - context 'for a html request' do - it 'deletes the group immediately and redirects to root path' do - expect(GroupDestroyWorker).to receive(:perform_async) + context 'for a html request' do + it 'deletes the group immediately and redirects to root path' do + expect(GroupDestroyWorker).to receive(:perform_async) - subject + subject - expect(response).to redirect_to(root_path) - expect(flash[:toast]).to include "Group '#{group.name}' is being deleted." - end + expect(response).to redirect_to(root_path) + expect(flash[:toast]).to include "Group '#{group.name}' is being deleted." end + end - context 'for a json request' do - let(:format) { :json } + context 'for a json request' do + let(:format) { :json } - it 'deletes the group immediately and returns json with message' do - expect(GroupDestroyWorker).to receive(:perform_async) + it 'deletes the group immediately and returns json with message' do + expect(GroupDestroyWorker).to receive(:perform_async) - subject + subject - expect(json_response['message']).to eq("Group '#{group.name}' is being deleted.") - end + expect(json_response['message']).to eq("Group '#{group.name}' is being deleted.") end end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 531f595dc304b622699d934458751fafc9e4f53a..36f3659e74353999d0eac30ddb748624de25aefb 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -1279,7 +1279,11 @@ def update_project_feature context 'when project is already marked for deletion' do let_it_be(:project) { create(:project, group: group, marked_for_deletion_at: Date.current) } - describe 'forbidden by the :disallow_immediate_deletion feature flag' do + describe 'when the :allow_immediate_namespaces_deletion application setting is false' do + before do + stub_application_setting(allow_immediate_namespaces_deletion: false) + end + subject(:request) { delete :destroy, params: { namespace_id: project.namespace, id: project, permanently_delete: true } } it 'returns error' do @@ -1291,21 +1295,15 @@ def update_project_feature end end - context 'when the :disallow_immediate_deletion feature flag is disabled' do - before do - stub_feature_flags(disallow_immediate_deletion: false) - end + context 'when permanently_delete param is set' do + it 'deletes project right away' do + expect(ProjectDestroyWorker).to receive(:perform_async) - context 'when permanently_delete param is set' do - it 'deletes project right away' do - expect(ProjectDestroyWorker).to receive(:perform_async) + delete :destroy, params: { namespace_id: project.namespace, id: project, permanently_delete: true } - delete :destroy, params: { namespace_id: project.namespace, id: project, permanently_delete: true } - - expect(project.reload.pending_delete).to eq(true) - expect(response).to have_gitlab_http_status(:found) - expect(response).to redirect_to(dashboard_projects_path) - end + expect(project.reload.pending_delete).to eq(true) + expect(response).to have_gitlab_http_status(:found) + expect(response).to redirect_to(dashboard_projects_path) end end diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb index 69c8a002475a884e8bce2bfe18cc8fc2a5562a88..8e2269fd6d7078c6f24d7bdc9512531bd5fbb5ca 100644 --- a/spec/features/groups/group_settings_spec.rb +++ b/spec/features/groups/group_settings_spec.rb @@ -7,8 +7,10 @@ include Features::WebIdeSpecHelpers include SafeFormatHelper include ActionView::Helpers::TagHelper + include Namespaces::DeletableHelper let_it_be(:user) { create(:user, organization: current_organization) } + let_it_be(:admin) { create(:user, :admin, organization: current_organization) } let_it_be_with_reload(:group) { create(:group, path: 'foo', owners: [user]) } before do @@ -392,29 +394,98 @@ end end - describe 'delayed project deletion' do + describe 'group deletion', :js, :freeze_time do def remove_with_confirm(button_text, confirm_with, confirm_button_text = 'Confirm') click_button button_text fill_in 'confirm_name_input', with: confirm_with click_button confirm_button_text end - context 'when group is marked for deletion', :js do + before do + stub_application_setting(deletion_adjourned_period: 7) + end + + context 'when group is not marked for deletion' do + before do + visit edit_group_path(group) + end + + it 'allows delayed deletion' do + remove_with_confirm('Delete', group.path) + + expect(page).to have_content "This group and its subgroups and projects are pending deletion, and will be deleted on #{permanent_deletion_date_formatted}." + end + end + + context 'when group is marked for deletion' do before do create(:group_deletion_schedule, group: group) end - context 'when the :disallow_immediate_deletion feature flag is disabled' do + context 'when "Allow immediate deletion" setting is enabled' do before do - stub_feature_flags(disallow_immediate_deletion: false) + stub_application_setting(usage_ping_enabled: true) + visit edit_group_path(group) + end + + it 'allows immediate deletion', :sidekiq_inline do + expect { remove_with_confirm('Delete immediately', group.path) }.to change { Group.count }.by(-1) + + expect(page).to have_content "Group '#{group.name}' is being deleted" + end + end + + context 'when there are subgroups and projects' do + let_it_be(:subgroup) { create(:group, parent: group) } + let_it_be(:project) { create(:project, namespace: group) } + it 'does not allow immediate deletion of subgroup' do + visit edit_group_path(subgroup) + + expect(page).not_to have_button('Delete immediately') + expect(page).to have_content "This group will be deleted on #{permanent_deletion_date_formatted(group)} because its parent group is scheduled for deletion." + end + + it 'does not allow immediate deletion of project' do + visit edit_project_path(project) + + expect(page).not_to have_button('Delete immediately') + expect(page).to have_content "This project will be deleted on #{permanent_deletion_date_formatted(group)} because its parent group is scheduled for deletion." + end + end + + context 'when "Allow immediate deletion" setting is disabled' do + before do + stub_application_setting(allow_immediate_namespaces_deletion: false) + end + + context 'when allow_immediate_namespaces_deletion feature flag is disabled' do + before do + stub_feature_flags(allow_immediate_namespaces_deletion: false) + visit edit_group_path(group) + end + + it 'allows immediate deletion', :sidekiq_inline do + expect { remove_with_confirm('Delete immediately', group.path) }.to change { Group.count }.by(-1) + + expect(page).to have_content "Group '#{group.name}' is being deleted" + end + end + + it 'allows immediate deletion for admins', :enable_admin_mode, :sidekiq_inline do + sign_in(admin) visit edit_group_path(group) + + expect { remove_with_confirm('Delete immediately', group.path) }.to change { Group.count }.by(-1) + + expect(page).to have_content "Group '#{group.name}' is being deleted" end - it 'deletes the project immediately', :sidekiq_inline do - expect { remove_with_confirm('Delete group immediately', group.path) }.to change { Group.count }.by(-1) + it 'does not allow immediate deletion' do + visit edit_group_path(group) - expect(page).to have_content "is being deleted" + expect(page).not_to have_button('Delete immediately') + expect(page).to have_content "This group and its subgroups and projects are pending deletion, and will be deleted on #{permanent_deletion_date_formatted(group)}." end end end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 95454706ac35d0167615a9cf11edb5fb91e86b9b..0c02b458b601f6aea0c1b82e743ce241ed440a89 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -354,13 +354,13 @@ end it 'focuses confirmation field on remove group' do - click_button('Delete group') + click_button('Delete') expect(page).to have_selector '#confirm_name_input:focus' end it 'marks the group for deletion' do - expect { remove_with_confirm('Delete group', group.path) }.to change { + expect { remove_with_confirm('Delete', group.path) }.to change { group.reload.self_deletion_scheduled? }.from(false).to(true) expect(page).to have_content "pending deletion" diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 428197e3c7493d9fc088ea0ce9f7d2bff46f94b8..eb71c587e837acb74d4574a8a1375ea0a50e780c 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -6,6 +6,7 @@ include ProjectForksHelper include MobileHelpers include ListboxHelpers + include Namespaces::DeletableHelper describe 'template' do let(:user) { create(:user) } @@ -280,36 +281,94 @@ end end - describe 'removal', :with_current_organization, :js do + describe 'project deletion', :with_current_organization, :js, :freeze_time do let_it_be(:user) { create(:user, organization: current_organization) } + let_it_be(:admin) { create(:user, :admin, organization: current_organization) } let_it_be(:group) { create(:group, :public, owners: user) } let_it_be_with_reload(:project_to_delete) { create(:project, group: group) } before do stub_application_setting(deletion_adjourned_period: 7) - sign_in user - visit edit_project_path(project_to_delete) end - it 'deletes project delayed and is restorable', :freeze_time do - deletion_adjourned_period = ::Gitlab::CurrentSettings.deletion_adjourned_period - deletion_date = (Time.now.utc + deletion_adjourned_period.days).strftime('%F') + def remove_with_confirm(button_text, confirm_with) + click_button button_text + fill_in 'confirm_name_input', with: confirm_with + click_button 'Yes, delete project' + wait_for_requests + end - expect(page).to have_content("This action will place this project, including all its resources, in a pending deletion state for #{deletion_adjourned_period} days, and delete it permanently on #{deletion_date}.") + context 'when project is not marked for deletion' do + before do + sign_in user + visit edit_project_path(project_to_delete) + end - click_button "Delete project" + it 'deletes project delayed and is restorable' do + expect(page).to have_content("This action will place this project, including all its resources, in a pending deletion state for 7 days, and delete it permanently on #{permanent_deletion_date_formatted}.") - expect(page).to have_content("This project can be restored until #{deletion_date}.") + remove_with_confirm("Delete", project_to_delete.path_with_namespace) - fill_in 'confirm_name_input', with: project_to_delete.path_with_namespace - click_button 'Yes, delete project' - wait_for_requests + expect(page).to have_content("This project is pending deletion, and will be deleted on #{permanent_deletion_date_formatted}. Repository and other project resources are read-only.") + + visit inactive_dashboard_projects_path + + expect(page).to have_content(project_to_delete.name_with_namespace) + end + end + + context 'when project is marked for deletion' do + let_it_be_with_reload(:project_aimed_for_deletion) { create(:project, :aimed_for_deletion, group: group) } + + context 'when "Allow immediate deletion" setting is enabled' do + before do + sign_in user + visit edit_project_path(project_aimed_for_deletion) + end - expect(page).to have_content("This project is pending deletion, and will be deleted on #{deletion_date}. Repository and other project resources are read-only.") + it 'allows immediate deletion', :sidekiq_inline do + remove_with_confirm('Delete immediately', project_aimed_for_deletion.path_with_namespace) - visit inactive_dashboard_projects_path + expect(page).to have_content "Project '#{project_aimed_for_deletion.full_name}' is being deleted" + end + end + + context 'when "Allow immediate deletion" setting is disabled' do + before do + stub_application_setting(allow_immediate_namespaces_deletion: false) + end + + context 'when allow_immediate_namespaces_deletion feature flag is disabled' do + before do + stub_feature_flags(allow_immediate_namespaces_deletion: false) + sign_in user + visit edit_project_path(project_aimed_for_deletion) + end + + it 'allows immediate deletion', :sidekiq_inline do + remove_with_confirm('Delete immediately', project_aimed_for_deletion.path_with_namespace) + + expect(page).to have_content "Project '#{project_aimed_for_deletion.full_name}' is being deleted" + end + end + + it 'allows immediate deletion for admins', :enable_admin_mode, :sidekiq_inline do + sign_in admin + visit edit_project_path(project_aimed_for_deletion) + + remove_with_confirm('Delete immediately', project_aimed_for_deletion.path_with_namespace) + + expect(page).to have_content "Project '#{project_aimed_for_deletion.full_name}' is being deleted" + end + + it 'does not allow immediate deletion' do + sign_in user + visit edit_project_path(project_aimed_for_deletion) - expect(page).to have_content(project_to_delete.name_with_namespace) + expect(page).not_to have_button('Delete immediately') + expect(page).to have_content("This project is pending deletion, and will be deleted on #{permanent_deletion_date_formatted(project_aimed_for_deletion)}. Repository and other project resources are read-only.") + end + end end end diff --git a/spec/helpers/namespaces/deletable_helper_spec.rb b/spec/helpers/namespaces/deletable_helper_spec.rb index f67f69574a77e9ad3fa27818536dc0ebb50577b6..cbba3eec20a3f6c149e1420e1468097d3b2f88c6 100644 --- a/spec/helpers/namespaces/deletable_helper_spec.rb +++ b/spec/helpers/namespaces/deletable_helper_spec.rb @@ -233,7 +233,7 @@ button_text: button_text, has_security_policy_project: has_security_policy_project, permanently_remove: permanently_remove) expect(expected).to eq({ - button_text: button_text.nil? ? "Delete group" : button_text, + button_text: button_text.nil? ? "Delete" : button_text, confirm_danger_message: confirm_remove_group_message(group, permanently_remove), remove_form_id: form_value_id, phrase: group.full_path, @@ -333,7 +333,7 @@ let(:project) { build(:project) } let(:base_button_data) do { - button_text: 'Delete project', + button_text: 'Delete', restore_help_path: help_page_path('user/project/working_with_projects.md', anchor: 'restore-a-project'), delayed_deletion_date: '2025-02-09', form_path: project_path(project), @@ -347,26 +347,14 @@ } end - let(:button_text) { nil } - - subject(:data) { helper.project_delete_delayed_button_data(project, button_text) } + subject(:data) { helper.project_delete_delayed_button_data(project) } before do stub_application_setting(deletion_adjourned_period: 7) end - describe 'with default button text' do - it 'returns expected hash' do - expect(data).to match(base_button_data) - end - end - - describe 'with custom button text' do - let(:button_text) { 'Delete project immediately' } - - it 'returns expected hash' do - expect(data).to match(base_button_data.merge(button_text: 'Delete project immediately')) - end + it 'returns expected hash' do + expect(data).to match(base_button_data) end end @@ -374,7 +362,7 @@ let(:project) { build(:project) } let(:base_button_data) do { - button_text: 'Delete project', + button_text: 'Delete immediately', form_path: project_path(project, permanently_delete: true), confirm_phrase: project.path_with_namespace, name_with_namespace: project.name_with_namespace, @@ -386,22 +374,10 @@ } end - let(:button_text) { nil } + subject(:data) { helper.project_delete_immediately_button_data(project) } - subject(:data) { helper.project_delete_immediately_button_data(project, button_text) } - - describe 'with default button text' do - it 'returns expected hash' do - expect(data).to match(base_button_data) - end - end - - describe 'with custom button text' do - let(:button_text) { 'Delete project immediately' } - - it 'returns expected hash' do - expect(data).to match(base_button_data.merge(button_text: 'Delete project immediately')) - end + it 'returns expected hash' do + expect(data).to match(base_button_data) end end diff --git a/spec/requests/organizations/groups_controller_spec.rb b/spec/requests/organizations/groups_controller_spec.rb index 9d7ba5a2829d5db36b2a468fdc6e252f66072ae1..7cba1f158a7374779969c71f545d5a861520d2e8 100644 --- a/spec/requests/organizations/groups_controller_spec.rb +++ b/spec/requests/organizations/groups_controller_spec.rb @@ -308,7 +308,11 @@ delete groups_organization_path(organization, id: group.to_param), params: params, as: :json end - describe 'forbidden by the :disallow_immediate_deletion feature flag' do + describe 'when the :allow_immediate_namespaces_deletion application setting is false' do + before do + stub_application_setting(allow_immediate_namespaces_deletion: false) + end + it 'returns error' do Sidekiq::Testing.fake! do expect { gitlab_request }.not_to change { GroupDestroyWorker.jobs.size } @@ -318,19 +322,13 @@ end end - context 'when the :disallow_immediate_deletion feature flag is disabled' do - before do - stub_feature_flags(disallow_immediate_deletion: false) - end - - it 'deletes the group immediately' do - expect(GroupDestroyWorker).to receive(:perform_async) + it 'deletes the group immediately' do + expect(GroupDestroyWorker).to receive(:perform_async) - gitlab_request + gitlab_request - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['message']).to include "Group '#{group.name}' is being deleted." - end + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['message']).to include "Group '#{group.name}' is being deleted." end end