From a7a6cdfa37ce2d4e88a3fbeb99ba121cf11dd6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 11 Sep 2025 11:04:40 +0200 Subject: [PATCH 1/8] Support disabled deletion section in group/project settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changelog: changed Signed-off-by: Rémy Coutable --- app/controllers/groups_controller.rb | 6 +- .../organizations/groups_controller.rb | 6 +- app/controllers/projects_controller.rb | 4 +- app/helpers/namespaces/deletable_helper.rb | 9 +++ .../settings/_immediately_remove.html.haml | 36 ++++++--- app/views/projects/_delete.html.haml | 32 ++++++-- doc/user/group/_index.md | 73 ++++++++++++------- doc/user/project/working_with_projects.md | 66 ++++++++++++----- locale/gitlab.pot | 36 +++++++-- spec/controllers/groups_controller_spec.rb | 38 +++++----- spec/controllers/projects_controller_spec.rb | 26 +++---- spec/features/groups/group_settings_spec.rb | 16 ++-- .../organizations/groups_controller_spec.rb | 22 +++--- 13 files changed, 241 insertions(+), 129 deletions(-) diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 30bd8c1e716bae..330747e8baf641 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 ffd3ad0a08906c..5fbabeb03e6315 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 15778d7f793c53..d945fd380cc27a 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 9530fe95e9508a..a62cccf77b01e3 100644 --- a/app/helpers/namespaces/deletable_helper.rb +++ b/app/helpers/namespaces/deletable_helper.rb @@ -115,6 +115,15 @@ def delete_immediately_namespace_scheduled_for_deletion_message(namespace) ) end + def delete_immediately_unavailable_due_to_ancestor_message(namespace) + messages = { + group: _('To delete immediately this group, you must delete immediately its parent group.'), + project: _('To delete immediately this project, you must delete immediately its parent group.') + } + + message_for_namespace(namespace, messages) + end + def group_confirm_modal_data( group:, remove_form_id: nil, diff --git a/app/views/groups/settings/_immediately_remove.html.haml b/app/views/groups/settings/_immediately_remove.html.haml index 91ef408aff1bce..f9f96e9fa88dfe 100644 --- a/app/views/groups/settings/_immediately_remove.html.haml +++ b/app/views/groups/settings/_immediately_remove.html.haml @@ -1,17 +1,35 @@ -- 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_disabled = group.self_deletion_scheduled? && allow_immediate_namespaces_deletion +- deletion_available = !group.scheduled_for_deletion_in_hierarchy_chain? || immediate_deletion_disabled +- component_class = deletion_available ? 'gl-bg-feedback-danger' : '' +- h4_class = deletion_available ? 'gl-text-feedback-danger' : '' +- title = group.scheduled_for_deletion_in_hierarchy_chain? ? s_('GroupSettings|Delete group immediately') : s_('GroupSettings|Delete group') -= render Pajamas::CardComponent.new(body_options: { class: 'gl-bg-feedback-danger' }) do |c| += render Pajamas::CardComponent.new(body_options: { class: component_class }) 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{ class: h4_class } + = title + - unless deletion_available + - tooltip = allow_immediate_namespaces_deletion && group.ancestor_scheduled_for_deletion? ? delete_immediately_unavailable_due_to_ancestor_message(group) : s_('GroupSettings|Immediate deletion is disabled') + %span.has-tooltip{ title: tooltip } + = sprite_icon('cancel', css_class: "gl-icon gl-cursor-pointer", size: 16) - c.with_body do - = form_tag(group, method: :delete, id: remove_form_id) do - %p= delete_immediately_namespace_scheduled_for_deletion_message(group) + - if group.self_deletion_scheduled? && allow_immediate_namespaces_deletion + = 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: title - = 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 + - help_page_link = link_to('', help_page_path('user/group/_index.md', anchor: 'delete-a-group-immediately'), target: '_blank', rel: 'noopener noreferrer') + %div + = self_or_ancestors_deletion_in_progress_or_scheduled_message(group) + - if group.self_deletion_scheduled? + = safe_format(s_('GroupSettings|%{link_start}How do I immediately delete a group?%{link_end}'), tag_pair(help_page_link, :link_start, :link_end)) diff --git a/app/views/projects/_delete.html.haml b/app/views/projects/_delete.html.haml index a5765a77493fa6..c95244843f0f80 100644 --- a/app/views/projects/_delete.html.haml +++ b/app/views/projects/_delete.html.haml @@ -1,17 +1,35 @@ - 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) +- immediate_deletion_disabled = @project.self_deletion_scheduled? && allow_immediate_namespaces_deletion +- deletion_available = !@project.scheduled_for_deletion_in_hierarchy_chain? || immediate_deletion_disabled +- component_class = deletion_available ? 'gl-bg-feedback-danger' : '' +- h4_class = deletion_available ? 'gl-text-feedback-danger' : '' +- title = @project.scheduled_for_deletion_in_hierarchy_chain? ? s_('ProjectSettings|Delete project immediately') : s_('ProjectSettings|Delete project') -= render Pajamas::CardComponent.new(body_options: { class: 'gl-bg-feedback-danger' }) do |c| += render Pajamas::CardComponent.new(body_options: { class: component_class }) 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{ class: h4_class } + = title + - unless deletion_available + - tooltip = allow_immediate_namespaces_deletion && @project.ancestor_scheduled_for_deletion? ? delete_immediately_unavailable_due_to_ancestor_message(@project) : s_('ProjectSettings|Immediate deletion is disabled') + %span.has-tooltip{ title: tooltip } + = sprite_icon('cancel', css_class: "gl-icon gl-cursor-pointer", size: 16) - 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, title) } + + - elsif @project.scheduled_for_deletion_in_hierarchy_chain? + -# Immediate deletion not available: either because of ancestor, or instance setting + - help_page_link = link_to('', help_page_path('user/project/working_with_projects.md', anchor: 'delete-a-project-immediately'), target: '_blank', rel: 'noopener noreferrer') + %p + = self_or_ancestors_deletion_in_progress_or_scheduled_message(@project) + - if @project.self_deletion_scheduled? + = safe_format(s_('ProjectSettings|%{link_start}How do I immediately delete a project?%{link_end}'), tag_pair(help_page_link, :link_start, :link_end)) + - else + -# Neither the project or its ancestor is scheduled for deletion = render 'delete_delayed' diff --git a/doc/user/group/_index.md b/doc/user/group/_index.md index 48c01c56ebcdce..e8662b296fe2b7 100644 --- a/doc/user/group/_index.md +++ b/doc/user/group/_index.md @@ -124,6 +124,34 @@ 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 pending deletion. +- 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 group in the list shows: + +- A badge indicating that the group is archived or marked for deletion. + If the group is marked for deletion, the list also shows: + - The time the group was marked for deletion. + - The time the group is scheduled for final deletion. + - A **Restore** action to stop the group being eventually deleted. + ## View a group {{< history >}} @@ -249,6 +277,14 @@ 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. @@ -272,28 +308,11 @@ 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 +{{< alert type="warning" >}} -{{< history >}} +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. -- **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: - -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. - -Groups that are marked for deletion are labeled **Pending deletion**. +{{< /alert >}} ## Delete a group immediately @@ -301,14 +320,18 @@ Groups that are marked for deletion are labeled **Pending deletion**. - 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. +- [Instance setting](../../administration/settings/visibility_and_access_controls.md#immediate-deletion) + to allow immediate deletion for groups or projects scheduled for deletion + [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/205556) in GitLab 18.5 + [with a flag](../../administration/feature_flags/_index.md) named `allow_immediate_namespaces_deletion`. + Enabled by default. Disabled on GitLab.com and Dedicated. {{< /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, after a group is deleted, its data is retained for 30 days, and immediate deletion is not available. +If you really need to delete a group immediately on GitLab.com, you can open a [support ticket](https://about.gitlab.com/support/). {{< /alert >}} @@ -324,7 +347,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 immediately** section, select **Delete group 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 a80d79f5c7f1b0..b65a653e3dd7ba 100644 --- a/doc/user/project/working_with_projects.md +++ b/doc/user/project/working_with_projects.md @@ -330,26 +330,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. @@ -362,14 +349,59 @@ To delete a project: 1. Expand **Advanced**. 1. In the **Delete project** section, select **Delete project**. 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 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). 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. +- [Instance setting](../../administration/settings/visibility_and_access_controls.md#immediate-deletion) + to allow immediate deletion for groups or projects scheduled for deletion + [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/205556) in GitLab 18.5 + [with a flag](../../administration/feature_flags/_index.md) named `allow_immediate_namespaces_deletion`. + Enabled by default. Disabled on GitLab.com and Dedicated. + +{{< /history >}} + +{{< alert type="warning" >}} + +On GitLab.com, after a project is deleted, its data is retained for 30 days, and immediate deletion is not available. +If you really need to delete a project immediately on GitLab.com, you can open a [support ticket](https://about.gitlab.com/support/). + +{{< /alert >}} + +If you don't want to wait, you can delete a project immediately. + +Prerequisites: + +- You must have the Owner role for a project. +- You have [marked the project for deletion](#delete-a-project). + +To immediately delete a project marked 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 immediately** section, select **Delete project 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. ### Restore a project diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a91cbc02d07348..a07abb76f96a1f 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 "" @@ -32414,6 +32408,9 @@ msgstr "" msgid "GroupSelect|Select a group" msgstr "" +msgid "GroupSettings|%{link_start}How do I immediately delete a group?%{link_end}" +msgstr "" + msgid "GroupSettings|Add additional webhook triggers for group access token expiration" msgstr "" @@ -32495,6 +32492,12 @@ msgstr "" msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group" msgstr "" +msgid "GroupSettings|Delete group" +msgstr "" + +msgid "GroupSettings|Delete group immediately" +msgstr "" + msgid "GroupSettings|Disable personal access tokens" msgstr "" @@ -32588,6 +32591,9 @@ msgstr "" msgid "GroupSettings|If the parent group's visibility is lower than the group's current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility." msgstr "" +msgid "GroupSettings|Immediate deletion is disabled" +msgstr "" + msgid "GroupSettings|Include diff previews" msgstr "" @@ -51521,6 +51527,9 @@ msgstr "" msgid "ProjectService|Trigger event when an issue is created, updated, or closed." msgstr "" +msgid "ProjectSettings|%{link_start}How do I immediately delete a project?%{link_end}" +msgstr "" + msgid "ProjectSettings|%{link_start}What are description templates?%{link_end}" msgstr "" @@ -51635,6 +51644,12 @@ msgstr "" msgid "ProjectSettings|Data sources" msgstr "" +msgid "ProjectSettings|Delete project" +msgstr "" + +msgid "ProjectSettings|Delete project immediately" +msgstr "" + msgid "ProjectSettings|Determine what happens to the commit history when you merge a merge request." msgstr "" @@ -51749,6 +51764,9 @@ msgstr "" msgid "ProjectSettings|If merge trains are enabled, merging is only possible if the branch can be rebased without conflicts." msgstr "" +msgid "ProjectSettings|Immediate deletion is disabled" +msgstr "" + msgid "ProjectSettings|Include diff previews" msgstr "" @@ -68246,6 +68264,12 @@ msgstr "" msgid "To continue, you need to select the link in the confirmation email we sent to verify your email address. If you didn't get our email, select %{strongStart}Resend confirmation email.%{strongEnd}" msgstr "" +msgid "To delete immediately this group, you must delete immediately its parent group." +msgstr "" + +msgid "To delete immediately this project, you must delete immediately its parent group." +msgstr "" + msgid "To disable the setting, set this value to 0." msgstr "" diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 0f9d9a1504c87a..598a845ed59b3b 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 531f595dc304b6..36f3659e743539 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 69c8a002475a88..7c82395f11e5e0 100644 --- a/spec/features/groups/group_settings_spec.rb +++ b/spec/features/groups/group_settings_spec.rb @@ -402,20 +402,14 @@ def remove_with_confirm(button_text, confirm_with, confirm_button_text = 'Confir context 'when group is marked for deletion', :js do before do create(:group_deletion_schedule, group: group) - end - - context 'when the :disallow_immediate_deletion feature flag is disabled' do - before do - stub_feature_flags(disallow_immediate_deletion: false) - visit edit_group_path(group) - end + visit edit_group_path(group) + 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 'deletes the project immediately', :sidekiq_inline do + expect { remove_with_confirm('Delete group immediately', group.path) }.to change { Group.count }.by(-1) - expect(page).to have_content "is being deleted" - end + expect(page).to have_content "is being deleted" end end end diff --git a/spec/requests/organizations/groups_controller_spec.rb b/spec/requests/organizations/groups_controller_spec.rb index 9d7ba5a2829d5d..7cba1f158a7374 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 -- GitLab From 3fa3d2815855e9c611d05b7c2a125bdab135e472 Mon Sep 17 00:00:00 2001 From: Peter Hegman Date: Thu, 2 Oct 2025 18:15:51 -0700 Subject: [PATCH 2/8] Adjust the logic in the HAML templates Attempt to simplify --- .../settings/_immediately_remove.html.haml | 21 +++------ app/views/projects/_delete.html.haml | 43 +++++++++---------- locale/gitlab.pot | 9 ---- 3 files changed, 27 insertions(+), 46 deletions(-) diff --git a/app/views/groups/settings/_immediately_remove.html.haml b/app/views/groups/settings/_immediately_remove.html.haml index f9f96e9fa88dfe..53adba7e94fcd5 100644 --- a/app/views/groups/settings/_immediately_remove.html.haml +++ b/app/views/groups/settings/_immediately_remove.html.haml @@ -3,33 +3,26 @@ - 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_disabled = group.self_deletion_scheduled? && allow_immediate_namespaces_deletion -- deletion_available = !group.scheduled_for_deletion_in_hierarchy_chain? || immediate_deletion_disabled -- component_class = deletion_available ? 'gl-bg-feedback-danger' : '' -- h4_class = deletion_available ? 'gl-text-feedback-danger' : '' -- title = group.scheduled_for_deletion_in_hierarchy_chain? ? s_('GroupSettings|Delete group immediately') : s_('GroupSettings|Delete group') +- immediate_deletion_available = group.self_deletion_scheduled? && allow_immediate_namespaces_deletion +- component_class = immediate_deletion_available ? 'gl-bg-feedback-danger' : '' = render Pajamas::CardComponent.new(body_options: { class: component_class }) do |c| - c.with_header do .gl-flex.gl-grow - %h4.gl-text-base.gl-leading-24.gl-m-0{ class: h4_class } - = title - - unless deletion_available - - tooltip = allow_immediate_namespaces_deletion && group.ancestor_scheduled_for_deletion? ? delete_immediately_unavailable_due_to_ancestor_message(group) : s_('GroupSettings|Immediate deletion is disabled') - %span.has-tooltip{ title: tooltip } - = sprite_icon('cancel', css_class: "gl-icon gl-cursor-pointer", size: 16) + %h4.gl-text-base.gl-leading-24.gl-m-0 + = s_('GroupSettings|Delete group immediately') - c.with_body do - - if group.self_deletion_scheduled? && allow_immediate_namespaces_deletion + - 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: title + = render 'groups/settings/remove_button', group: group, remove_form_id: remove_form_id, button_text: s_('GroupSettings|Delete group immediately') - else -# Immediate deletion not available: either because of ancestor, or instance setting - help_page_link = link_to('', help_page_path('user/group/_index.md', anchor: 'delete-a-group-immediately'), target: '_blank', rel: 'noopener noreferrer') - %div + %p.gl-m-0 = self_or_ancestors_deletion_in_progress_or_scheduled_message(group) - if group.self_deletion_scheduled? = safe_format(s_('GroupSettings|%{link_start}How do I immediately delete a group?%{link_end}'), tag_pair(help_page_link, :link_start, :link_end)) diff --git a/app/views/projects/_delete.html.haml b/app/views/projects/_delete.html.haml index c95244843f0f80..0d42744ddb8c29 100644 --- a/app/views/projects/_delete.html.haml +++ b/app/views/projects/_delete.html.haml @@ -1,35 +1,32 @@ - return unless can?(current_user, :remove_project, @project) - allow_immediate_namespaces_deletion = Gitlab::CurrentSettings.allow_immediate_namespaces_deletion_for_user?(current_user) -- immediate_deletion_disabled = @project.self_deletion_scheduled? && allow_immediate_namespaces_deletion -- deletion_available = !@project.scheduled_for_deletion_in_hierarchy_chain? || immediate_deletion_disabled -- component_class = deletion_available ? 'gl-bg-feedback-danger' : '' -- h4_class = deletion_available ? 'gl-text-feedback-danger' : '' -- title = @project.scheduled_for_deletion_in_hierarchy_chain? ? s_('ProjectSettings|Delete project immediately') : s_('ProjectSettings|Delete project') -= render Pajamas::CardComponent.new(body_options: { class: component_class }) do |c| - - c.with_header do - .gl-flex.gl-grow - %h4.gl-text-base.gl-leading-24.gl-m-0{ class: h4_class } - = title - - unless deletion_available - - tooltip = allow_immediate_namespaces_deletion && @project.ancestor_scheduled_for_deletion? ? delete_immediately_unavailable_due_to_ancestor_message(@project) : s_('ProjectSettings|Immediate deletion is disabled') - %span.has-tooltip{ title: tooltip } - = sprite_icon('cancel', css_class: "gl-icon gl-cursor-pointer", size: 16) - - - c.with_body do - - if @project.self_deletion_scheduled? && allow_immediate_namespaces_deletion +- if @project.self_deletion_scheduled? && allow_immediate_namespaces_deletion + = render Pajamas::CardComponent.new(body_options: { class: 'gl-bg-feedback-danger' }) do |c| + - c.with_header do + %h4.gl-text-base.gl-leading-24.gl-m-0 + = s_('ProjectSettings|Delete project immediately') + - c.with_body do %p= delete_immediately_namespace_scheduled_for_deletion_message(@project) #js-project-delete-button{ data: project_delete_immediately_button_data(@project, title) } - - - elsif @project.scheduled_for_deletion_in_hierarchy_chain? +- elsif @project.scheduled_for_deletion_in_hierarchy_chain? + = render Pajamas::CardComponent.new do |c| + - c.with_header do + %h4.gl-text-base.gl-leading-24.gl-m-0 + = s_('ProjectSettings|Delete project immediately') + - c.with_body do -# Immediate deletion not available: either because of ancestor, or instance setting - help_page_link = link_to('', help_page_path('user/project/working_with_projects.md', anchor: 'delete-a-project-immediately'), target: '_blank', rel: 'noopener noreferrer') - %p + %p.gl-m-0 = self_or_ancestors_deletion_in_progress_or_scheduled_message(@project) - if @project.self_deletion_scheduled? = safe_format(s_('ProjectSettings|%{link_start}How do I immediately delete a project?%{link_end}'), tag_pair(help_page_link, :link_start, :link_end)) - - - else - -# Neither the project or its ancestor is scheduled for deletion +- else + -# Neither the project or its ancestor is scheduled for deletion + = render Pajamas::CardComponent.new(body_options: { class: 'gl-bg-feedback-danger' }) do |c| + - c.with_header do + %h4.gl-text-base.gl-leading-24.gl-m-0 + = s_('ProjectSettings|Delete project') + - c.with_body do = render 'delete_delayed' diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a07abb76f96a1f..1c145f98890b93 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -32492,9 +32492,6 @@ msgstr "" msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group" msgstr "" -msgid "GroupSettings|Delete group" -msgstr "" - msgid "GroupSettings|Delete group immediately" msgstr "" @@ -32591,9 +32588,6 @@ msgstr "" msgid "GroupSettings|If the parent group's visibility is lower than the group's current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility." msgstr "" -msgid "GroupSettings|Immediate deletion is disabled" -msgstr "" - msgid "GroupSettings|Include diff previews" msgstr "" @@ -51764,9 +51758,6 @@ msgstr "" msgid "ProjectSettings|If merge trains are enabled, merging is only possible if the branch can be rebased without conflicts." msgstr "" -msgid "ProjectSettings|Immediate deletion is disabled" -msgstr "" - msgid "ProjectSettings|Include diff previews" msgstr "" -- GitLab From 4047dff01daa453eb376c7cfb5df0048fd97f536 Mon Sep 17 00:00:00 2001 From: Peter Hegman Date: Fri, 3 Oct 2025 11:30:45 -0700 Subject: [PATCH 3/8] Adjust HAML code some more and docs Simplify HAML templates further and try to clear up docs --- app/helpers/namespaces/deletable_helper.rb | 13 ++------- .../settings/_delayed_deletion.html.haml | 2 +- .../settings/_immediately_remove.html.haml | 7 ++--- app/views/projects/_delete.html.haml | 29 ++++++------------- .../visibility_and_access_controls.md | 7 +++++ doc/user/group/_index.md | 15 ++++------ doc/user/project/working_with_projects.md | 15 ++++------ .../settings/_delayed_deletion.html.haml | 2 +- .../views/projects/_delete_delayed.html.haml | 2 +- locale/gitlab.pot | 15 ++++------ .../namespaces/deletable_helper_spec.rb | 2 +- 11 files changed, 42 insertions(+), 67 deletions(-) diff --git a/app/helpers/namespaces/deletable_helper.rb b/app/helpers/namespaces/deletable_helper.rb index a62cccf77b01e3..da611f243b6e7f 100644 --- a/app/helpers/namespaces/deletable_helper.rb +++ b/app/helpers/namespaces/deletable_helper.rb @@ -115,15 +115,6 @@ def delete_immediately_namespace_scheduled_for_deletion_message(namespace) ) end - def delete_immediately_unavailable_due_to_ancestor_message(namespace) - messages = { - group: _('To delete immediately this group, you must delete immediately its parent group.'), - project: _('To delete immediately this project, you must delete immediately its parent group.') - } - - message_for_namespace(namespace, messages) - end - def group_confirm_modal_data( group:, remove_form_id: nil, @@ -132,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), @@ -257,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 4acb7b454d304e..e80a13afc9d5e6 100644 --- a/app/views/groups/settings/_delayed_deletion.html.haml +++ b/app/views/groups/settings/_delayed_deletion.html.haml @@ -3,7 +3,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 53adba7e94fcd5..c19a6088109886 100644 --- a/app/views/groups/settings/_immediately_remove.html.haml +++ b/app/views/groups/settings/_immediately_remove.html.haml @@ -4,20 +4,19 @@ - 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 -- component_class = immediate_deletion_available ? 'gl-bg-feedback-danger' : '' -= render Pajamas::CardComponent.new(body_options: { class: component_class }) do |c| += 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 - = s_('GroupSettings|Delete group immediately') + = s_('GroupSettings|Delete group') - c.with_body do - 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 group immediately') + = render 'groups/settings/remove_button', group: group, remove_form_id: remove_form_id, button_text: s_('GroupSettings|Delete immediately') - else -# Immediate deletion not available: either because of ancestor, or instance setting diff --git a/app/views/projects/_delete.html.haml b/app/views/projects/_delete.html.haml index 0d42744ddb8c29..b54ef367d1b809 100644 --- a/app/views/projects/_delete.html.haml +++ b/app/views/projects/_delete.html.haml @@ -2,31 +2,20 @@ - allow_immediate_namespaces_deletion = Gitlab::CurrentSettings.allow_immediate_namespaces_deletion_for_user?(current_user) -- if @project.self_deletion_scheduled? && allow_immediate_namespaces_deletion - = render Pajamas::CardComponent.new(body_options: { class: 'gl-bg-feedback-danger' }) do |c| - - c.with_header do - %h4.gl-text-base.gl-leading-24.gl-m-0 - = s_('ProjectSettings|Delete project immediately') - - c.with_body do += render Pajamas::CardComponent.new(body_options: { class: 'gl-bg-feedback-danger' }) do |c| + - c.with_header do + %h4.gl-text-base.gl-leading-24.gl-m-0 + = s_('ProjectSettings|Delete project') + - c.with_body do + - 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, title) } -- elsif @project.scheduled_for_deletion_in_hierarchy_chain? - = render Pajamas::CardComponent.new do |c| - - c.with_header do - %h4.gl-text-base.gl-leading-24.gl-m-0 - = s_('ProjectSettings|Delete project immediately') - - c.with_body do + #js-project-delete-button{ data: project_delete_immediately_button_data(@project, s_('ProjectSettings|Delete immediately')) } + - elsif @project.scheduled_for_deletion_in_hierarchy_chain? -# Immediate deletion not available: either because of ancestor, or instance setting - help_page_link = link_to('', help_page_path('user/project/working_with_projects.md', anchor: 'delete-a-project-immediately'), target: '_blank', rel: 'noopener noreferrer') %p.gl-m-0 = self_or_ancestors_deletion_in_progress_or_scheduled_message(@project) - if @project.self_deletion_scheduled? = safe_format(s_('ProjectSettings|%{link_start}How do I immediately delete a project?%{link_end}'), tag_pair(help_page_link, :link_start, :link_end)) -- else - -# Neither the project or its ancestor is scheduled for deletion - = render Pajamas::CardComponent.new(body_options: { class: 'gl-bg-feedback-danger' }) do |c| - - c.with_header do - %h4.gl-text-base.gl-leading-24.gl-m-0 - = s_('ProjectSettings|Delete project') - - c.with_body do + - else = render 'delete_delayed' diff --git a/doc/administration/settings/visibility_and_access_controls.md b/doc/administration/settings/visibility_and_access_controls.md index 9d888d673e6daa..dbe808b0637136 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 e8662b296fe2b7..06f77338804e98 100644 --- a/doc/user/group/_index.md +++ b/doc/user/group/_index.md @@ -290,7 +290,7 @@ 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. In the **Delete group** section, select **Delete**. 1. On the confirmation dialog, type the group name and select **Confirm**. You can also delete a group from the groups dashboard: @@ -299,7 +299,6 @@ 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**. 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, @@ -319,18 +318,14 @@ If the user who scheduled the group deletion regains Owner role or administrator {{< 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. -- [Instance setting](../../administration/settings/visibility_and_access_controls.md#immediate-deletion) - to allow immediate deletion for groups or projects scheduled for deletion - [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/205556) in GitLab 18.5 - [with a flag](../../administration/feature_flags/_index.md) named `allow_immediate_namespaces_deletion`. - Enabled by default. Disabled on GitLab.com and Dedicated. +- [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, after a group is deleted, its data is retained for 30 days, and immediate deletion is not available. +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 really need to delete a group immediately on GitLab.com, you can open a [support ticket](https://about.gitlab.com/support/). {{< /alert >}} @@ -347,7 +342,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 **Delete group immediately** section, select **Delete group immediately**. +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 b65a653e3dd7ba..9acc62faeadbee 100644 --- a/doc/user/project/working_with_projects.md +++ b/doc/user/project/working_with_projects.md @@ -347,10 +347,10 @@ 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**. -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, +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 @@ -371,17 +371,14 @@ You can also [delete projects using the Rails console](troubleshooting.md#delete - 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. -- [Instance setting](../../administration/settings/visibility_and_access_controls.md#immediate-deletion) - to allow immediate deletion for groups or projects scheduled for deletion - [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/205556) in GitLab 18.5 - [with a flag](../../administration/feature_flags/_index.md) named `allow_immediate_namespaces_deletion`. - Enabled by default. Disabled on GitLab.com and Dedicated. +- [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, after a project is deleted, its data is retained for 30 days, and immediate deletion is not available. +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 really need to delete a project immediately on GitLab.com, you can open a [support ticket](https://about.gitlab.com/support/). {{< /alert >}} @@ -398,7 +395,7 @@ To immediately delete a project marked 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 immediately** section, select **Delete project immediately**. +1. In the **Delete project** 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/ee/app/views/groups/settings/_delayed_deletion.html.haml b/ee/app/views/groups/settings/_delayed_deletion.html.haml index ae7933b8cce245..0d37da1846f505 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 5002ee7a063960..8648d1ddaf78f9 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/locale/gitlab.pot b/locale/gitlab.pot index 1c145f98890b93..c75dff857bbc2e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -32492,7 +32492,10 @@ msgstr "" msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group" msgstr "" -msgid "GroupSettings|Delete group immediately" +msgid "GroupSettings|Delete group" +msgstr "" + +msgid "GroupSettings|Delete immediately" msgstr "" msgid "GroupSettings|Disable personal access tokens" @@ -51638,10 +51641,10 @@ msgstr "" msgid "ProjectSettings|Data sources" msgstr "" -msgid "ProjectSettings|Delete project" +msgid "ProjectSettings|Delete immediately" msgstr "" -msgid "ProjectSettings|Delete project immediately" +msgid "ProjectSettings|Delete project" msgstr "" msgid "ProjectSettings|Determine what happens to the commit history when you merge a merge request." @@ -68255,12 +68258,6 @@ msgstr "" msgid "To continue, you need to select the link in the confirmation email we sent to verify your email address. If you didn't get our email, select %{strongStart}Resend confirmation email.%{strongEnd}" msgstr "" -msgid "To delete immediately this group, you must delete immediately its parent group." -msgstr "" - -msgid "To delete immediately this project, you must delete immediately its parent group." -msgstr "" - msgid "To disable the setting, set this value to 0." msgstr "" diff --git a/spec/helpers/namespaces/deletable_helper_spec.rb b/spec/helpers/namespaces/deletable_helper_spec.rb index f67f69574a77e9..8d2c29278eb66c 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, -- GitLab From 8bd24f2cda6f3ce6e95da2cc880cb9abf84fd9db Mon Sep 17 00:00:00 2001 From: Peter Hegman Date: Mon, 6 Oct 2025 12:52:06 -0700 Subject: [PATCH 4/8] Fix spec failures and add feature specs Add coverage to feature specs for different states --- app/helpers/namespaces/deletable_helper.rb | 8 +- app/views/projects/_delete.html.haml | 2 +- spec/features/groups/group_settings_spec.rb | 90 +++++++++++++++++-- spec/features/groups_spec.rb | 4 +- spec/features/projects_spec.rb | 90 +++++++++++++++---- .../namespaces/deletable_helper_spec.rb | 40 ++------- 6 files changed, 174 insertions(+), 60 deletions(-) diff --git a/app/helpers/namespaces/deletable_helper.rb b/app/helpers/namespaces/deletable_helper.rb index da611f243b6e7f..8dfcd71793bd1e 100644 --- a/app/helpers/namespaces/deletable_helper.rb +++ b/app/helpers/namespaces/deletable_helper.rb @@ -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 diff --git a/app/views/projects/_delete.html.haml b/app/views/projects/_delete.html.haml index b54ef367d1b809..b236bfd02ba3dd 100644 --- a/app/views/projects/_delete.html.haml +++ b/app/views/projects/_delete.html.haml @@ -9,7 +9,7 @@ - c.with_body do - 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, s_('ProjectSettings|Delete 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 - help_page_link = link_to('', help_page_path('user/project/working_with_projects.md', anchor: 'delete-a-project-immediately'), target: '_blank', rel: 'noopener noreferrer') diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb index 7c82395f11e5e0..e4a6c18da38a04 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,24 +394,100 @@ 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 - visit edit_group_path(group) + context 'when "Allow immediate deletion" setting is enabled' do + before do + 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 - it 'deletes the project immediately', :sidekiq_inline do - expect { remove_with_confirm('Delete group immediately', group.path) }.to change { Group.count }.by(-1) + context 'when there are subgroups and projects' do + let_it_be(:subgroup) { create(:group, parent: group) } + let_it_be(:project) { create(:project, namespace: group) } - expect(page).to have_content "is being deleted" + 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 'does not allow immediate deletion' do + visit edit_group_path(group) + + 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)}." + expect(page).to have_link("How do I immediately delete a group?", href: help_page_path('user/group/_index.md', anchor: 'delete-a-group-immediately')) + end end end end diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 95454706ac35d0..0c02b458b601f6 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 428197e3c7493d..f6834b273feb18 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,95 @@ 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.") + expect(page).to have_link("How do I immediately delete a project?", href: help_page_path('user/project/working_with_projects.md', anchor: 'delete-a-project-immediately')) + end + end end end diff --git a/spec/helpers/namespaces/deletable_helper_spec.rb b/spec/helpers/namespaces/deletable_helper_spec.rb index 8d2c29278eb66c..cbba3eec20a3f6 100644 --- a/spec/helpers/namespaces/deletable_helper_spec.rb +++ b/spec/helpers/namespaces/deletable_helper_spec.rb @@ -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 -- GitLab From 53259f9ad22c56fa8c92504f1a0d1978631d5baa Mon Sep 17 00:00:00 2001 From: Peter Hegman Date: Wed, 8 Oct 2025 10:18:21 -0700 Subject: [PATCH 5/8] Remove unnecessary .gl-flex.gl-grow from card headers --- app/views/groups/settings/_delayed_deletion.html.haml | 3 +-- app/views/groups/settings/_immediately_remove.html.haml | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/views/groups/settings/_delayed_deletion.html.haml b/app/views/groups/settings/_delayed_deletion.html.haml index e80a13afc9d5e6..f9b6c901013a00 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= _('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 c19a6088109886..cfe8271f4d501f 100644 --- a/app/views/groups/settings/_immediately_remove.html.haml +++ b/app/views/groups/settings/_immediately_remove.html.haml @@ -7,9 +7,8 @@ = 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 - = s_('GroupSettings|Delete group') + %h4.gl-text-base.gl-leading-24.gl-m-0 + = s_('GroupSettings|Delete group') - c.with_body do - if immediate_deletion_available -- GitLab From fbd00c6cba1781edf2d04ad6f638af6263702973 Mon Sep 17 00:00:00 2001 From: Abdul Wadood Date: Mon, 13 Oct 2025 15:23:32 +0530 Subject: [PATCH 6/8] Remove redundant feature flag and add rollout issue to another FF --- .../allow_immediate_namespaces_deletion.yml | 4 ++-- .../gitlab_com_derisk/disallow_immediate_deletion.yml | 10 ---------- 2 files changed, 2 insertions(+), 12 deletions(-) rename config/feature_flags/{wip => gitlab_com_derisk}/allow_immediate_namespaces_deletion.yml (74%) delete mode 100644 config/feature_flags/gitlab_com_derisk/disallow_immediate_deletion.yml 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 2ff9d65b003072..38519989628724 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 9a11c78ca5c136..00000000000000 --- 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 -- GitLab From 2da95b0111a08505572f3d3e7249abf84745680a Mon Sep 17 00:00:00 2001 From: Peter Hegman Date: Tue, 14 Oct 2025 10:42:24 -0700 Subject: [PATCH 7/8] Remove "How do I immediately delete a group/project?" link After talking with UX, to simplify --- app/views/groups/settings/_immediately_remove.html.haml | 3 --- app/views/projects/_delete.html.haml | 3 --- ee/spec/features/projects_spec.rb | 2 +- locale/gitlab.pot | 6 ------ spec/features/groups/group_settings_spec.rb | 1 - spec/features/projects_spec.rb | 1 - 6 files changed, 1 insertion(+), 15 deletions(-) diff --git a/app/views/groups/settings/_immediately_remove.html.haml b/app/views/groups/settings/_immediately_remove.html.haml index cfe8271f4d501f..7adb69486d6a40 100644 --- a/app/views/groups/settings/_immediately_remove.html.haml +++ b/app/views/groups/settings/_immediately_remove.html.haml @@ -19,8 +19,5 @@ - else -# Immediate deletion not available: either because of ancestor, or instance setting - - help_page_link = link_to('', help_page_path('user/group/_index.md', anchor: 'delete-a-group-immediately'), target: '_blank', rel: 'noopener noreferrer') %p.gl-m-0 = self_or_ancestors_deletion_in_progress_or_scheduled_message(group) - - if group.self_deletion_scheduled? - = safe_format(s_('GroupSettings|%{link_start}How do I immediately delete a group?%{link_end}'), tag_pair(help_page_link, :link_start, :link_end)) diff --git a/app/views/projects/_delete.html.haml b/app/views/projects/_delete.html.haml index b236bfd02ba3dd..1e5acf338710a9 100644 --- a/app/views/projects/_delete.html.haml +++ b/app/views/projects/_delete.html.haml @@ -12,10 +12,7 @@ #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 - - help_page_link = link_to('', help_page_path('user/project/working_with_projects.md', anchor: 'delete-a-project-immediately'), target: '_blank', rel: 'noopener noreferrer') %p.gl-m-0 = self_or_ancestors_deletion_in_progress_or_scheduled_message(@project) - - if @project.self_deletion_scheduled? - = safe_format(s_('ProjectSettings|%{link_start}How do I immediately delete a project?%{link_end}'), tag_pair(help_page_link, :link_start, :link_end)) - else = render 'delete_delayed' diff --git a/ee/spec/features/projects_spec.rb b/ee/spec/features/projects_spec.rb index 7d89802d98a117..3b663fc9867b35 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 c75dff857bbc2e..de85a117598f01 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -32408,9 +32408,6 @@ msgstr "" msgid "GroupSelect|Select a group" msgstr "" -msgid "GroupSettings|%{link_start}How do I immediately delete a group?%{link_end}" -msgstr "" - msgid "GroupSettings|Add additional webhook triggers for group access token expiration" msgstr "" @@ -51524,9 +51521,6 @@ msgstr "" msgid "ProjectService|Trigger event when an issue is created, updated, or closed." msgstr "" -msgid "ProjectSettings|%{link_start}How do I immediately delete a project?%{link_end}" -msgstr "" - msgid "ProjectSettings|%{link_start}What are description templates?%{link_end}" msgstr "" diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb index e4a6c18da38a04..8e2269fd6d7078 100644 --- a/spec/features/groups/group_settings_spec.rb +++ b/spec/features/groups/group_settings_spec.rb @@ -486,7 +486,6 @@ def remove_with_confirm(button_text, confirm_with, confirm_button_text = 'Confir 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)}." - expect(page).to have_link("How do I immediately delete a group?", href: help_page_path('user/group/_index.md', anchor: 'delete-a-group-immediately')) end end end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index f6834b273feb18..eb71c587e837ac 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -367,7 +367,6 @@ def remove_with_confirm(button_text, confirm_with) 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.") - expect(page).to have_link("How do I immediately delete a project?", href: help_page_path('user/project/working_with_projects.md', anchor: 'delete-a-project-immediately')) end end end -- GitLab From fa3b3753ab8bacfe3d7200b9049127a9db343099 Mon Sep 17 00:00:00 2001 From: Peter Hegman Date: Tue, 14 Oct 2025 16:19:23 -0700 Subject: [PATCH 8/8] Address TW reviewer feedback --- doc/user/group/_index.md | 25 ++++++++--------- doc/user/project/working_with_projects.md | 34 +++++++++++------------ 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/doc/user/group/_index.md b/doc/user/group/_index.md index 06f77338804e98..cddbb5ebb52afc 100644 --- a/doc/user/group/_index.md +++ b/doc/user/group/_index.md @@ -133,10 +133,7 @@ This page shows groups that you are a member of through: {{< /history >}} -A group is inactive when: - -- It is pending deletion. -- It has been archived. +A group is inactive when it is either pending deletion or it has been archived. To view all inactive groups: @@ -144,13 +141,15 @@ To view all inactive groups: 1. Select **View all my groups**. 1. Select the **Inactive** tab. -Each group in the list shows: +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: -- A badge indicating that the group is archived or marked for deletion. - If the group is marked for deletion, the list also shows: - - The time the group was marked for deletion. - - The time the group is scheduled for final deletion. - - A **Restore** action to stop the group being eventually deleted. +- 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 @@ -291,7 +290,7 @@ To delete a group and its contents: 1. Select **Settings** > **General**. 1. Expand the **Advanced** section. 1. In the **Delete group** section, select **Delete**. -1. On the confirmation dialog, type the group name and select **Confirm**. +1. On the confirmation dialog, enter the group name and select **Confirm**. You can also delete a group from the groups dashboard: @@ -299,7 +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. 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). @@ -326,7 +325,7 @@ If the user who scheduled the group deletion regains Owner role or administrator {{< alert type="warning" >}} 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 really need to delete a group immediately on GitLab.com, you can open a [support ticket](https://about.gitlab.com/support/). +If you must delete a group immediately on GitLab.com, you can open a [support ticket](https://about.gitlab.com/support/). {{< /alert >}} diff --git a/doc/user/project/working_with_projects.md b/doc/user/project/working_with_projects.md index 9acc62faeadbee..f7ca8dd0b1a147 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 @@ -379,26 +378,27 @@ You can also [delete projects using the Rails console](troubleshooting.md#delete {{< 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 really need to delete a project immediately on GitLab.com, you can open a [support ticket](https://about.gitlab.com/support/). +If you must delete a project immediately on GitLab.com, you can open a [support ticket](https://about.gitlab.com/support/). {{< /alert >}} -If you don't want to wait, you can delete a project immediately. +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 [marked the project for deletion](#delete-a-project). +- You have [scheduled the project for deletion](#delete-a-project). -To immediately delete a project marked for deletion: +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. Select **Settings** > **General**. 1. Expand **Advanced**. 1. In the **Delete project** section, select **Delete immediately**. -1. Confirm the action when asked to. +1. On the confirmation dialog, enter the project name and select **Confirm**. -This action deletes the group, its subgroups, projects, and all related resources, including issues and merge requests. +This action deletes the project and all related resources, including issues and merge requests. ### Restore a project -- GitLab