Commit 17528418 authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Allow immediate deletion of projects

Allow users to immediately delete a project that is scheduled for
deletion.

Changelog: added
EE: true
parent e20771af
...@@ -364,13 +364,18 @@ namespace if needed. ...@@ -364,13 +364,18 @@ namespace if needed.
#### Delete a project #### Delete a project
NOTE: You can mark a project to be deleted.
Only project Owners and administrators have [permissions](../../permissions.md#project-members-permissions) to delete a project.
Prerequisite:
- You must have at least the Owner role for a project.
To delete a project: To delete a project:
1. Navigate to your project, and select **Settings > General > Advanced**. 1. On the top bar, select **Menu > Projects** and find your project.
1. In the "Delete project" section, click the **Delete project** button. 1. On the left sidebar, select **Settings > General**.
1. Expand **Advanced**.
1. In the "Delete project" section, select **Delete project**.
1. Confirm the action when asked to. 1. Confirm the action when asked to.
This action deletes a project including all associated resources (issues, merge requests, and so on). This action deletes a project including all associated resources (issues, merge requests, and so on).
...@@ -385,6 +390,28 @@ WARNING: ...@@ -385,6 +390,28 @@ WARNING:
The default behavior of [delayed project deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/32935) in GitLab 12.6 was changed to The default behavior of [delayed project deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/32935) in GitLab 12.6 was changed to
[Immediate deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) in GitLab 13.2. [Immediate deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) in GitLab 13.2.
#### Delete a project immediately **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/191367) in GitLab 14.1.
If you don't want to wait, you can delete a project immediately.
Prerequisites:
- You must have at least 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 top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Settings > General**.
1. Expand **Advanced**.
1. In the "Permanently delete project" section, select **Delete project**.
1. Confirm the action when asked to.
Your project, its repository, and all related resources, including issues and merge requests,
are deleted.
#### Restore a project **(PREMIUM)** #### Restore a project **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32935) in GitLab 12.6. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32935) in GitLab 12.6.
......
...@@ -36,6 +36,8 @@ module EE ...@@ -36,6 +36,8 @@ module EE
override :destroy override :destroy
def destroy def destroy
return super unless project.adjourned_deletion? return super unless project.adjourned_deletion?
return super if project.marked_for_deletion? && params[:permanently_delete].present?
return access_denied! unless can?(current_user, :remove_project, project) return access_denied! unless can?(current_user, :remove_project, project)
result = ::Projects::MarkForDeletionService.new(project, current_user, {}).execute result = ::Projects::MarkForDeletionService.new(project, current_user, {}).execute
......
...@@ -22,4 +22,5 @@ ...@@ -22,4 +22,5 @@
#js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: project.path } } #js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: project.path } }
- else - else
= render 'projects/settings/restore', project: project = render 'projects/settings/restore', project: project
= render 'projects/settings/permanently_delete', project: project
.sub-section
%h4.danger-title= _('Permanently delete project')
%p
%strong= _('Deleting the project will delete its repository and all related resources including issues, merge requests, etc.')
%p= permanent_delete_message(project)
%p
%strong= _('Are you ABSOLUTELY SURE you wish to delete this project?')
#js-project-delete-button{ data: { form_path: project_path(project, permanently_delete: true), confirm_phrase: project.path } }
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
- date = permanent_deletion_date(project.marked_for_deletion_at) - date = permanent_deletion_date(project.marked_for_deletion_at)
.sub-section .sub-section
%h4.danger-title= _('Restore project') %h4= _('Restore project')
%p %p
%strong= _('This project will be deleted on %{date}') %{ date: date } %strong= _('This project will be deleted on %{date}') %{ date: date }
%p %p
= _("Restoring the project will prevent the project from being removed on this date and restore people's ability to make changes to it.") = _("Restoring the project will prevent the project from being removed on this date and restore people's ability to make changes to it.")
= _("The repository can be committed to, and issues, comments and other entities can be created.") = _("The repository can be committed to, and issues, comments and other entities can be created.")
%strong= _('Only active this projects shows up in the search and on the dashboard.') %strong= _('Only active projects show up in the search and on the dashboard.')
= link_to _('Restore project'), namespace_project_restore_path(project.namespace, project), = link_to _('Restore project'), namespace_project_restore_path(project.namespace, project),
method: :post, class: "gl-button btn btn-danger" method: :post, class: "gl-button btn"
...@@ -749,6 +749,34 @@ RSpec.describe ProjectsController do ...@@ -749,6 +749,34 @@ RSpec.describe ProjectsController do
expect(response).to redirect_to(dashboard_projects_path) expect(response).to redirect_to(dashboard_projects_path)
end end
end end
context 'when project is already marked for deletion' do
let(:project) { create(:project, group: group, marked_for_deletion_at: Date.current) }
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 }
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
context 'when permanently_delete param is not set' do
it 'does nothing' do
expect(ProjectDestroyWorker).not_to receive(:perform_async)
delete :destroy, params: { namespace_id: project.namespace, id: project }
expect(project.reload.pending_delete).to eq(false)
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(project_path(project))
end
end
end
end end
context 'when feature is disabled for group' do context 'when feature is disabled for group' do
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Project', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/222234' do RSpec.describe 'Project', :js do
describe 'when creating from group template' do describe 'when creating from group template', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/222234' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group, name: 'parent-group') } let(:group) { create(:group, name: 'parent-group') }
let(:template_subgroup) { create(:group, parent: group, name: 'template-subgroup') } let(:template_subgroup) { create(:group, parent: group, name: 'template-subgroup') }
...@@ -35,4 +35,29 @@ RSpec.describe 'Project', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab ...@@ -35,4 +35,29 @@ RSpec.describe 'Project', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab
expect(find('.js-select-namespace')).to have_content other_subgroup.name expect(find('.js-select-namespace')).to have_content other_subgroup.name
end end
end end
describe 'immediately deleting a project marked for deletion' do
let(:project) { create(:project, marked_for_deletion_at: Date.current) }
let(:user) { project.owner }
before do
stub_licensed_features(adjourned_deletion_for_projects_and_groups: true)
sign_in user
visit edit_project_path(project)
end
it 'deletes the project immediately', :sidekiq_inline do
expect { remove_with_confirm('Delete project', project.path, 'Yes, delete project') }.to change { Project.count }.by(-1)
expect(page).to have_content "Project '#{project.full_name}' is in the process of being deleted."
expect(Project.all.count).to be_zero
end
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
...@@ -23063,7 +23063,7 @@ msgstr "" ...@@ -23063,7 +23063,7 @@ msgstr ""
msgid "Only Project Members" msgid "Only Project Members"
msgstr "" msgstr ""
msgid "Only active this projects shows up in the search and on the dashboard." msgid "Only active projects show up in the search and on the dashboard."
msgstr "" msgstr ""
msgid "Only admins can delete project" msgid "Only admins can delete project"
...@@ -23909,6 +23909,9 @@ msgstr "" ...@@ -23909,6 +23909,9 @@ msgstr ""
msgid "PerformanceBar|Trace" msgid "PerformanceBar|Trace"
msgstr "" msgstr ""
msgid "Permanently delete project"
msgstr ""
msgid "Permissions" msgid "Permissions"
msgstr "" msgstr ""
......
...@@ -256,7 +256,7 @@ RSpec.describe 'Project' do ...@@ -256,7 +256,7 @@ RSpec.describe 'Project' do
expect(page).to have_selector '#confirm_name_input:focus' expect(page).to have_selector '#confirm_name_input:focus'
end end
it 'deletes a project', :sidekiq_might_not_need_inline do it 'deletes a project', :sidekiq_inline do
expect { remove_with_confirm('Delete project', project.path, 'Yes, delete project') }.to change { Project.count }.by(-1) expect { remove_with_confirm('Delete project', project.path, 'Yes, delete project') }.to change { Project.count }.by(-1)
expect(page).to have_content "Project '#{project.full_name}' is in the process of being deleted." expect(page).to have_content "Project '#{project.full_name}' is in the process of being deleted."
expect(Project.all.count).to be_zero expect(Project.all.count).to be_zero
......
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