From fa4998240092288ac95a7919dd50c60bbee09da4 Mon Sep 17 00:00:00 2001 From: Fabrice Bellingard Date: Wed, 4 Jul 2012 10:16:36 +0200 Subject: [PATCH] SONAR-2614 Provide a bulk project deletion page --- .../resources/org/sonar/l10n/core.properties | 21 ++++ .../java/org/sonar/server/ui/JRubyFacade.java | 7 +- .../controllers/bulk_deletion_controller.rb | 92 ++++++++++++++ .../app/models/database_migration_manager.rb | 2 +- .../app/models/resource_deletion_manager.rb | 110 +++++++++++++++++ .../app/views/bulk_deletion/index.html.erb | 116 ++++++++++++++++++ .../bulk_deletion/pending_deletions.html.erb | 34 +++++ .../app/views/layouts/_layout.html.erb | 2 + 8 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/controllers/bulk_deletion_controller.rb create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/models/resource_deletion_manager.rb create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/index.html.erb create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/pending_deletions.html.erb diff --git a/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties index fb9a261cbb9..7a7d91c3bc6 100644 --- a/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties +++ b/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties @@ -332,6 +332,7 @@ dependencies.page=Dependencies resource_deletion.page={0} Deletion update_key.page=Update Key project_quality_profile.page=Quality Profile +bulk_deletion.page=Bulk Deletion # GWT pages @@ -1246,6 +1247,26 @@ my_profile.password.mismatch=Password mismatch my_profile.password.wrong_old=Wrong old password +#------------------------------------------------------------------------------ +# +# BULK RESOURCE DELETION +# +#------------------------------------------------------------------------------ +bulk_deletion.resource.projects=Projects +bulk_deletion.resource.views=Views +bulk_deletion.resource.devs=Developers +bulk_deletion.no_resource_to_delete=No resource to delete +bulk_deletion.resource_name_filter_by_name=Filter resources by name +bulk_deletion.search=Search +bulk_deletion.page_size=Page size +bulk_deletion.select_all=Select all +bulk_deletion.select_all_x_resources=Select all {0} resources +bulk_deletion.clear_selection=Clear selection of all {0} resources +bulk_deletion.following_deletions_failed=The following resources could not be deleted. Please check the logs to know more about it. +bulk_deletion.hide_message=Hide message +bulk_deletion.sure_to_delete_the_resources=Are you sure you want to delete the selected resources? + + #------------------------------------------------------------------------------ # # TREEMAP diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java index 4c126d653ca..f930f742a0a 100644 --- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java +++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java @@ -447,7 +447,12 @@ public final class JRubyFacade { } public void deleteResourceTree(long rootProjectId) { - getContainer().getComponentByType(PurgeDao.class).deleteResourceTree(rootProjectId); + try { + getContainer().getComponentByType(PurgeDao.class).deleteResourceTree(rootProjectId); + } catch (RuntimeException e) { + LoggerFactory.getLogger(JRubyFacade.class).error("Fail to delete resource with ID: " + rootProjectId, e); + throw e; + } } public void logError(String message) { diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/bulk_deletion_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/bulk_deletion_controller.rb new file mode 100644 index 00000000000..a7d74f7887d --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/bulk_deletion_controller.rb @@ -0,0 +1,92 @@ +# +# Sonar, entreprise quality control tool. +# Copyright (C) 2008-2012 SonarSource +# mailto:contact AT sonarsource DOT com +# +# Sonar is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# Sonar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with Sonar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 +# +class BulkDeletionController < ApplicationController + + SECTION=Navigation::SECTION_CONFIGURATION + + before_filter :admin_required + verify :method => :post, :only => [:delete_resources], :redirect_to => { :action => :index } + + def index + deletion_manager = ResourceDeletionManager.instance + + if deletion_manager.currently_deleting_resources? || + (!deletion_manager.currently_deleting_resources? && deletion_manager.deletion_failures_occured?) + # a mass deletion is happening or it has just finished with errors => display the message from the Resource Deletion Manager + @deletion_manager = deletion_manager + render :template => 'bulk_deletion/pending_deletions' + else + @selected_tab = params[:resource_type] || 'projects' + + # search if there are VIEWS or DEVS to know if we should display the tabs or not + @should_display_views_tab = Project.count(:all, :conditions => {:qualifier => 'VW'}) > 0 + @should_display_devs_tab = Project.count(:all, :conditions => {:qualifier => 'DEV'}) > 0 + + # Search for resources + conditions = "scope=:scope AND qualifier=:qualifier" + values = {:scope => 'PRJ'} + qualifier = 'TRK' + if @selected_tab == 'views' + qualifier = 'VW' + elsif @selected_tab == 'devs' + qualifier = 'DEV' + end + values[:qualifier] = qualifier + if params[:name_filter] + conditions += " AND name LIKE :name" + values[:name] = '%' + params[:name_filter].strip + '%' + end + + @resources = Project.find(:all, :conditions => [conditions, values]) + end + end + + def delete_resources + resource_to_delete = params[:resources] || [] + resource_to_delete = params[:all_resources].split(',') if params[:all_resources] && !params[:all_resources].blank? + + # Ask the resource deletion manager to start the migration + # => this is an asynchronous AJAX call + ResourceDeletionManager.instance.delete_resources(resource_to_delete) + + # and return some text that will actually never be displayed + render :text => ResourceDeletionManager.instance.message + end + + def pending_deletions + deletion_manager = ResourceDeletionManager.instance + + if deletion_manager.currently_deleting_resources? || + (!deletion_manager.currently_deleting_resources? && deletion_manager.deletion_failures_occured?) + # display the same page again and again + @deletion_manager = deletion_manager + else + redirect_to :action => 'index' + end + end + + def dismiss_message + # It is important to reinit the ResourceDeletionManager so that the deletion screens can be available again + ResourceDeletionManager.instance.reinit + + redirect_to :action => 'index' + end + +end diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/database_migration_manager.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/database_migration_manager.rb index 5d412ea6284..f68e8abadba 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/database_migration_manager.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/database_migration_manager.rb @@ -19,7 +19,7 @@ # # -# Class taht centralizes the management the DB migration +# Class that centralizes the management the DB migration # require 'singleton' diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/resource_deletion_manager.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/resource_deletion_manager.rb new file mode 100644 index 00000000000..f828cc3e085 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/resource_deletion_manager.rb @@ -0,0 +1,110 @@ +# +# Sonar, entreprise quality control tool. +# Copyright (C) 2008-2012 SonarSource +# mailto:contact AT sonarsource DOT com +# +# Sonar is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# Sonar is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with Sonar; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 +# + +# +# Class that centralizes the management of resource deletions in Sonar Web UI +# + +require 'singleton' +require 'thread' + +class ResourceDeletionManager + + # mixin the singleton module to ensure we have only one instance of the class + # it will be accessible with "ResourceDeletionManager.instance" + include Singleton + + # the status of the migration + @status + AVAILABLE = "AVAILABLE" + WORKING = "WORKING" + + # the corresponding message that can be given to the user + @message + + # list of resources that could not be deleted because of a problem + @failed_deletions + + def initialize + reinit() + end + + def reinit + @message = nil + @status = AVAILABLE + @failed_deletions = [] + end + + def message + @message + end + + def currently_deleting_resources? + @status==WORKING + end + + def deletion_failures_occured? + !failed_deletions.empty? + end + + def failed_deletions + @failed_deletions + end + + def delete_resources(resource_ids=[]) + # Use an exclusive block of code to ensure that only 1 thread will be able to proceed with the deletion + can_start_deletion = false + Thread.exclusive do + unless currently_deleting_resources? + reinit() + @status = WORKING + @message = "Deleting resources..." + can_start_deletion = true + end + end + + if can_start_deletion + if resource_ids.empty? + @status = AVAILABLE + @message = "No resource to delete." + else + java_facade = Java::OrgSonarServerUi::JRubyFacade.getInstance() + # launch the deletion + resource_ids.each_with_index do |resource_id, index| + resource = Project.find(:first, :conditions => {:id => resource_id.to_i}) + @message = "Currently deleting resources... (" + (index+1).to_s + " out of " + resource_ids.size.to_s + ")" + if resource && + # next line add 'VW' and 'DEV' tests because those resource types don't have the 'deletable' property yet... + (java_facade.getResourceTypeBooleanProperty(resource.qualifier, 'deletable') || resource.qualifier=='VW' || resource.qualifier=='DEV') + begin + java_facade.deleteResourceTree(resource.id) + rescue Exception => e + @failed_deletions << resource.name + # no need to rethrow the exception as it has been logged by the JRubyFacade + end + end + end + @status = AVAILABLE + @message = "Resource deletion completed." + end + end + end + +end diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/index.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/index.html.erb new file mode 100644 index 00000000000..80ce6a6f31d --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/index.html.erb @@ -0,0 +1,116 @@ +

<%= message('bulk_deletion.page') -%>

+ + + +
+ +<% + found_resources_count = @resources.size + found_resources_ids = @resources.map {|r| r.id.to_s}.join(',') + page_size = (params[:page_size] && params[:page_size].to_i) || 20 +%> + + <% form_tag( {:action => 'index'}, :method => :get ) do %> + <%= message('bulk_deletion.resource_name_filter_by_name') -%>: + + <%= submit_tag message('bulk_deletion.search'), :id => 'filter_resources' %> + <% end %> + + <% if @resources.empty? %> +
+ <%= message('bulk_deletion.no_resource_to_delete') -%> + <% else %> + + <% form_remote_tag( :url => {:action => 'delete_resources'}, :loading => "window.location='#{url_for :action => 'pending_deletions'}';") do %> + + + + + + + + + + + + + + + + + + <% @resources.each_with_index do |resource, index| %> + + + + + <% end %> + +
<%= paginate @resources, {:page_size => page_size} %>
+ <%= submit_tag message('delete'), :id => 'delete_resources', :class => 'action red-button', :confirm => message('bulk_deletion.sure_to_delete_the_resources') %> +
+ « <%= message('bulk_deletion.select_all') -%> + <% if found_resources_count - @resources.size > 0 %> + + + <% end %> +
+ + <%= resource.name -%>
+ + <% end %> + + + + <% end %> + +
\ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/pending_deletions.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/pending_deletions.html.erb new file mode 100644 index 00000000000..e5e67a6d57d --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/bulk_deletion/pending_deletions.html.erb @@ -0,0 +1,34 @@ +<% + pending_deletions = @deletion_manager.currently_deleting_resources? + failed_deletions = @deletion_manager.failed_deletions +%> + +<% if pending_deletions %> + +<% end %> + +

<%= message('bulk_deletion.page') -%>

+ +
+ <% if pending_deletions %> + <%= image_tag 'loading.gif' -%> + <% end %> + + <%= @deletion_manager.message -%> +
+
+ + <% if !pending_deletions && !failed_deletions.empty? %> +

+ <%= message('bulk_deletion.following_deletions_failed') -%> +

+

+

+ <%= link_to message('bulk_deletion.hide_message'), :action => 'dismiss_message' -%> +

+ <% end %> +
\ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb index 37a9379ace6..989ec8f92ca 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb @@ -158,6 +158,8 @@ <%= message('settings.page') -%>
  • <%= message('backup.page') -%>
  • +
  • <%= message('bulk_deletion.page') -%> +
  • <%= message('system_info.page') -%>
  • <% update_center_activated = controller.java_facade.getSettings().getBoolean('sonar.updatecenter.activate') -- 2.39.5