diff options
author | Marius Balteanu <marius.balteanu@zitec.com> | 2022-05-17 20:45:41 +0000 |
---|---|---|
committer | Marius Balteanu <marius.balteanu@zitec.com> | 2022-05-17 20:45:41 +0000 |
commit | 883aa3b5cca1645f2aae353f4f180f77c5693c7e (patch) | |
tree | 97c45c8f806b96f5a41dd4853d89dffd27b9f3c8 /app | |
parent | 3719eb32f44681987df6bd55c9ca1888190daecb (diff) | |
download | redmine-883aa3b5cca1645f2aae353f4f180f77c5693c7e.tar.gz redmine-883aa3b5cca1645f2aae353f4f180f77c5693c7e.zip |
Background job for project deletion (#36691).
Due to the deletion of dependent objects (issues etc), project deletion may take a long time.
This patch moves the actual project deletion into an ActiveJob job. It also introduces a new project status (SCHEDULED_FOR_DELETION) that is used to effectively hide the project that is about to be deleted (and any potential descendant projects) from the system immediately.
A security notification is sent out to the user that deleted the project, informing about success / failure.
The projects list is extended to be able to filter for the new status, so in case of a failure, the project can still be accessed for examination.
Patch by Jens Krämer.
git-svn-id: https://svn.redmine.org/redmine/trunk@21591 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'app')
-rw-r--r-- | app/controllers/projects_controller.rb | 3 | ||||
-rw-r--r-- | app/helpers/admin_helper.rb | 3 | ||||
-rw-r--r-- | app/jobs/application_job.rb | 4 | ||||
-rw-r--r-- | app/jobs/destroy_project_job.rb | 77 | ||||
-rw-r--r-- | app/models/project.rb | 7 | ||||
-rw-r--r-- | app/models/project_query.rb | 1 | ||||
-rw-r--r-- | app/views/context_menus/projects.html.erb | 2 | ||||
-rw-r--r-- | app/views/projects/_list.html.erb | 4 |
8 files changed, 95 insertions, 6 deletions
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index da7259da1..d8b2e8b9a 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -300,7 +300,8 @@ class ProjectsController < ApplicationController @project_to_destroy = @project if api_request? || params[:confirm] == @project_to_destroy.identifier - @project_to_destroy.destroy + DestroyProjectJob.schedule(@project_to_destroy) + flash[:notice] = l(:notice_successful_delete) respond_to do |format| format.html do redirect_to( diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb index 7b4ef9b1b..68cb31277 100644 --- a/app/helpers/admin_helper.rb +++ b/app/helpers/admin_helper.rb @@ -22,7 +22,8 @@ module AdminHelper options_for_select([[l(:label_all), ''], [l(:project_status_active), '1'], [l(:project_status_closed), '5'], - [l(:project_status_archived), '9']], selected.to_s) + [l(:project_status_archived), '9'], + [l(:project_status_scheduled_for_deletion), '10']], selected.to_s) end def plugin_data_for_updates(plugins) diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 000000000..d92ffddcb --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class ApplicationJob < ActiveJob::Base +end diff --git a/app/jobs/destroy_project_job.rb b/app/jobs/destroy_project_job.rb new file mode 100644 index 000000000..372e77240 --- /dev/null +++ b/app/jobs/destroy_project_job.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +class DestroyProjectJob < ApplicationJob + include Redmine::I18n + + def self.schedule(project, user: User.current) + # make the project (and any children) disappear immediately + project.self_and_descendants.update_all status: Project::STATUS_SCHEDULED_FOR_DELETION + perform_later project.id, user.id, user.remote_ip + end + + def perform(project_id, user_id, remote_ip) + user_current_was = User.current + + unless @user = User.active.find_by_id(user_id) + info "User check failed: User #{user_id} triggering project destroy does not exist anymore or isn't active." + return + end + @user.remote_ip = remote_ip + User.current = @user + set_language_if_valid @user.language || Setting.default_language + + unless @project = Project.find_by_id(project_id) + info "Project check failed: Project has already been deleted." + return + end + + unless @project.deletable? + info "Project check failed: User #{user_id} lacks permissions." + return + end + + message = if @project.descendants.any? + :mail_destroy_project_with_subprojects_successful + else + :mail_destroy_project_successful + end + delete_project ? success(message) : failure + ensure + User.current = user_current_was + info "End destroy project" + end + + private + + def delete_project + info "Starting with project deletion" + return !!@project.destroy + rescue + info "Error while deleting project: #{$!}" + false + end + + def success(message) + Mailer.deliver_security_notification( + @user, @user, + message: message, + value: @project.name, + url: {controller: 'admin', action: 'projects'}, + title: :label_project_plural + ) + end + + def failure + Mailer.deliver_security_notification( + @user, @user, + message: :mail_destroy_project_failed, + value: @project.name, + url: {controller: 'admin', action: 'projects'}, + title: :label_project_plural + ) + end + + def info(msg) + Rails.logger.info("[DestroyProjectJob] --- #{msg}") + end +end diff --git a/app/models/project.rb b/app/models/project.rb index 7ba9b4197..81ec37d10 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -25,6 +25,7 @@ class Project < ActiveRecord::Base STATUS_ACTIVE = 1 STATUS_CLOSED = 5 STATUS_ARCHIVED = 9 + STATUS_SCHEDULED_FOR_DELETION = 10 # Maximum length for project identifiers IDENTIFIER_MAX_LENGTH = 100 @@ -182,7 +183,7 @@ class Project < ActiveRecord::Base perm = Redmine::AccessControl.permission(permission) base_statement = if perm && perm.read? - "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" + "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND #{Project.table_name}.status <> #{Project::STATUS_SCHEDULED_FOR_DELETION}" else "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}" end @@ -399,6 +400,10 @@ class Project < ActiveRecord::Base self.status == STATUS_ARCHIVED end + def scheduled_for_deletion? + self.status == STATUS_SCHEDULED_FOR_DELETION + end + # Archives the project and its descendants def archive # Check that there is no issue of a non descendant project that is assigned diff --git a/app/models/project_query.rb b/app/models/project_query.rb index 35f05fbae..852beb95c 100644 --- a/app/models/project_query.rb +++ b/app/models/project_query.rb @@ -111,6 +111,7 @@ class ProjectQuery < Query values = super if self.admin_projects values << [l(:project_status_archived), Project::STATUS_ARCHIVED.to_s] + values << [l(:project_status_scheduled_for_deletion), Project::STATUS_SCHEDULED_FOR_DELETION.to_s] end values end diff --git a/app/views/context_menus/projects.html.erb b/app/views/context_menus/projects.html.erb index c5db9084a..444d84c58 100644 --- a/app/views/context_menus/projects.html.erb +++ b/app/views/context_menus/projects.html.erb @@ -1,5 +1,5 @@ <ul> - <% if @project %> + <% if @project && !@project.scheduled_for_deletion? %> <% if @project.archived? %> <li><%= context_menu_link l(:button_unarchive), unarchive_project_path(@project), method: :post, class: 'icon icon-unlock' %></li> <% else %> diff --git a/app/views/projects/_list.html.erb b/app/views/projects/_list.html.erb index 39978ee64..4c592247c 100644 --- a/app/views/projects/_list.html.erb +++ b/app/views/projects/_list.html.erb @@ -40,13 +40,13 @@ </tr> <% end %> <tr id="project-<%= entry.id %>" class="<%= cycle('odd', 'even') %> hascontextmenu <%= entry.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>"> - <% if @admin_list %> + <% if @admin_list && !entry.scheduled_for_deletion? %> <td class="checkbox hide-when-print"><%= check_box_tag("ids[]", entry.id, false, :id => nil) %></td> <% end %> <% @query.inline_columns.each do |column| %> <%= content_tag('td', column_content(column, entry), :class => column.css_classes) %> <% end %> - <% if @admin_list %> + <% if @admin_list && !entry.scheduled_for_deletion? %> <td class="buttons"><%= link_to_context_menu %></td> <% end %> </tr> |