Commit 80637ccb authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Allow immediate deletion of groups

When a group is already scheduled for deletion, we allow users to
immediately delete the group by going to the settings again and
confirming the deletion.

Changelog: added
EE: true
parent d0fe524e
...@@ -143,7 +143,7 @@ module GroupsHelper ...@@ -143,7 +143,7 @@ module GroupsHelper
end end
def remove_group_message(group) def remove_group_message(group)
_("You are going to remove %{group_name}, this will also delete all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") % _("You are going to remove %{group_name}. This will also delete all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?") %
{ group_name: group.name } { group_name: group.name }
end end
......
...@@ -28,3 +28,4 @@ ...@@ -28,3 +28,4 @@
= render 'groups/settings/transfer', group: @group = render 'groups/settings/transfer', group: @group
= render 'groups/settings/remove', group: @group = render 'groups/settings/remove', group: @group
= render_if_exists 'groups/settings/restore', group: @group = render_if_exists 'groups/settings/restore', group: @group
= render_if_exists 'groups/settings/immediately_remove', group: @group
...@@ -430,6 +430,28 @@ Specifically: ...@@ -430,6 +430,28 @@ Specifically:
- In [GitLab 13.6 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/39504), if the user who sets up the deletion is removed from the group before the - In [GitLab 13.6 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/39504), if the user who sets up the deletion is removed from the group before the
deletion happens, the job is cancelled, and the group is no longer scheduled for deletion. deletion happens, the job is cancelled, and the group is no longer scheduled for deletion.
## Remove a group immediately **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/336985) in GitLab 14.2.
If you don't want to wait, you can remove a group immediately.
Prerequisites:
- You must have at least the Owner role for a group.
- You have [marked the group for deletion](#remove-a-group).
To immediately remove a group marked for deletion:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Settings > General**.
1. Expand **Advanced**.
1. In the "Permanently remove group" section, select **Remove group**.
1. Confirm the action when asked to.
Your group, its subgroups, projects, and all related resources, including issues and merge requests,
are deleted.
## Restore a group **(PREMIUM)** ## Restore a group **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33257) in GitLab 12.8. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33257) in GitLab 12.8.
......
...@@ -32,6 +32,7 @@ module EE ...@@ -32,6 +32,7 @@ module EE
override :destroy override :destroy
def destroy def destroy
return super unless group.adjourned_deletion? return super unless group.adjourned_deletion?
return super if group.marked_for_deletion? && ::Gitlab::Utils.to_boolean(params[:permanently_remove])
result = ::Groups::MarkForDeletionService.new(group, current_user).execute result = ::Groups::MarkForDeletionService.new(group, current_user).execute
......
...@@ -51,6 +51,7 @@ module EE ...@@ -51,6 +51,7 @@ module EE
override :remove_group_message override :remove_group_message
def remove_group_message(group) def remove_group_message(group)
return super unless group.licensed_feature_available?(:adjourned_deletion_for_projects_and_groups) return super unless group.licensed_feature_available?(:adjourned_deletion_for_projects_and_groups)
return super if group.marked_for_deletion?
date = permanent_deletion_date(Time.now.utc) date = permanent_deletion_date(Time.now.utc)
...@@ -58,6 +59,18 @@ module EE ...@@ -58,6 +59,18 @@ module EE
{ date: date, deletion_adjourned_period: deletion_adjourned_period } { date: date, deletion_adjourned_period: deletion_adjourned_period }
end end
def immediately_remove_group_message(group)
message = _('This action will %{strongOpen}permanently remove%{strongClose} %{codeOpen}%{group}%{codeClose} %{strongOpen}immediately%{strongClose}.')
html_escape(message) % {
group: group.path,
strongOpen: '<strong>'.html_safe,
strongClose: '</strong>'.html_safe,
codeOpen: '<code>'.html_safe,
codeClose: '</code>'.html_safe
}
end
def permanent_deletion_date(date) def permanent_deletion_date(date)
(date + deletion_adjourned_period.days).strftime('%F') (date + deletion_adjourned_period.days).strftime('%F')
end end
......
- if group.marked_for_deletion?
.sub-section
%h4.gl-text-red-500= _('Permanently remove group')
= form_tag(group, method: :delete) do
%p
%strong= _('Removing this group also removes all child projects, including archived projects, and their resources.')
%p= immediately_remove_group_message(group)
%p
%strong= _('Are you ABSOLUTELY SURE you wish to remove this group?')
= hidden_field_tag(:permanently_remove, true)
= render 'groups/settings/remove_button', group: group
...@@ -221,6 +221,32 @@ RSpec.describe GroupsController do ...@@ -221,6 +221,32 @@ RSpec.describe GroupsController do
expect(flash[:alert]).to include 'error' expect(flash[:alert]).to include 'error'
end end
end end
context 'when group is already marked for deletion' do
before do
create(:group_deletion_schedule, group: group, marked_for_deletion_on: Date.current)
end
context 'when permanently_remove param is set' do
it 'deletes the group immediately' do
expect(GroupDestroyWorker).to receive(:perform_async)
delete :destroy, params: { id: group.to_param, permanently_remove: true }
expect(response).to redirect_to(root_path)
expect(flash[:alert]).to include "Group '#{group.name}' was scheduled for deletion."
end
end
context 'when permanently_remove param is not set' do
it 'does nothing' do
subject
expect(response).to redirect_to(edit_group_path(group))
expect(flash[:alert]).to include "Group has been already marked for deletion"
end
end
end
end end
context 'delayed deletion feature is not available' do context 'delayed deletion feature is not available' do
......
...@@ -158,16 +158,36 @@ RSpec.describe 'Edit group settings' do ...@@ -158,16 +158,36 @@ RSpec.describe 'Edit group settings' do
stub_licensed_features(adjourned_deletion_for_projects_and_groups: true) stub_licensed_features(adjourned_deletion_for_projects_and_groups: true)
end end
let_it_be(:subgroup) { create(:group, parent: group) } it_behaves_like 'a cascading setting' do
let_it_be(:subgroup) { create(:group, parent: group) }
let(:form_group_selector) { '[data-testid="delayed-project-removal-form-group"]' }
let(:setting_field_selector) { '[data-testid="delayed-project-removal-checkbox"]' }
let(:setting_path) { edit_group_path(group, anchor: 'js-permissions-settings') }
let(:group_path) { edit_group_path(group) }
let(:subgroup_path) { edit_group_path(subgroup) }
let(:click_save_button) { save_permissions_group }
end
describe 'immediately deleting a project marked for deletion', :js do
before do
create(:group_deletion_schedule, group: group, marked_for_deletion_on: 2.days.from_now)
visit edit_group_path(group)
end
it 'deletes the project immediately', :sidekiq_inline do
expect { remove_with_confirm('Remove group', group.path) }.to change { Group.count }.by(-1)
let(:form_group_selector) { '[data-testid="delayed-project-removal-form-group"]' } expect(page).to have_content "scheduled for deletion"
let(:setting_field_selector) { '[data-testid="delayed-project-removal-checkbox"]' } end
let(:setting_path) { edit_group_path(group, anchor: 'js-permissions-settings') }
let(:group_path) { edit_group_path(group) }
let(:subgroup_path) { edit_group_path(subgroup) }
let(:click_save_button) { save_permissions_group }
it_behaves_like 'a cascading setting' 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
end
end end
context 'when custom_project_templates feature' do context 'when custom_project_templates feature' do
......
...@@ -176,6 +176,17 @@ RSpec.describe GroupsHelper do ...@@ -176,6 +176,17 @@ RSpec.describe GroupsHelper do
it 'returns the message related to delayed deletion' do it 'returns the message related to delayed deletion' do
expect(subject).to include("The contents of this group, its subgroups and projects will be permanently removed after") expect(subject).to include("The contents of this group, its subgroups and projects will be permanently removed after")
end end
context 'group is already marked for deletion' do
before do
create(:group_deletion_schedule, group: group, marked_for_deletion_on: Date.current)
end
it 'returns the message related to permanent deletion' do
expect(subject).to include("You are going to remove #{group.name}")
expect(subject).to include("Removed groups CANNOT be restored!")
end
end
end end
context 'delayed deletion feature is not available' do context 'delayed deletion feature is not available' do
...@@ -190,6 +201,14 @@ RSpec.describe GroupsHelper do ...@@ -190,6 +201,14 @@ RSpec.describe GroupsHelper do
end end
end end
describe '#immediately_remove_group_message' do
subject { helper.immediately_remove_group_message(group) }
it 'returns the message related to immediate deletion' do
expect(subject).to match(/permanently remove.*#{group.path}.*immediately/)
end
end
describe '#show_discover_group_security?' do describe '#show_discover_group_security?' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
......
...@@ -4299,6 +4299,9 @@ msgstr "" ...@@ -4299,6 +4299,9 @@ msgstr ""
msgid "Are you ABSOLUTELY SURE you wish to delete this project?" msgid "Are you ABSOLUTELY SURE you wish to delete this project?"
msgstr "" msgstr ""
msgid "Are you ABSOLUTELY SURE you wish to remove this group?"
msgstr ""
msgid "Are you sure that you want to archive this project?" msgid "Are you sure that you want to archive this project?"
msgstr "" msgstr ""
...@@ -24042,6 +24045,9 @@ msgstr "" ...@@ -24042,6 +24045,9 @@ msgstr ""
msgid "Permanently delete project" msgid "Permanently delete project"
msgstr "" msgstr ""
msgid "Permanently remove group"
msgstr ""
msgid "Permissions" msgid "Permissions"
msgstr "" msgstr ""
...@@ -33646,6 +33652,9 @@ msgstr "" ...@@ -33646,6 +33652,9 @@ msgstr ""
msgid "This action will %{strongOpen}permanently delete%{strongClose} %{codeOpen}%{project}%{codeClose} %{strongOpen}on %{date}%{strongClose}, including its repositories and all related resources, including issues and merge requests." msgid "This action will %{strongOpen}permanently delete%{strongClose} %{codeOpen}%{project}%{codeClose} %{strongOpen}on %{date}%{strongClose}, including its repositories and all related resources, including issues and merge requests."
msgstr "" msgstr ""
msgid "This action will %{strongOpen}permanently remove%{strongClose} %{codeOpen}%{group}%{codeClose} %{strongOpen}immediately%{strongClose}."
msgstr ""
msgid "This also resolves all related threads" msgid "This also resolves all related threads"
msgstr "" msgstr ""
...@@ -37576,7 +37585,7 @@ msgstr "" ...@@ -37576,7 +37585,7 @@ msgstr ""
msgid "You are going to delete %{project_full_name}. Deleted projects CANNOT be restored! Are you ABSOLUTELY sure?" msgid "You are going to delete %{project_full_name}. Deleted projects CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr "" msgstr ""
msgid "You are going to remove %{group_name}, this will also delete all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgid "You are going to remove %{group_name}. This will also delete all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr "" msgstr ""
msgid "You are going to remove the fork relationship from %{project_full_name}. Are you ABSOLUTELY sure?" msgid "You are going to remove the fork relationship from %{project_full_name}. Are you ABSOLUTELY sure?"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment