resource_deletion.page={0} Deletion
update_key.page=Update Key
project_quality_profile.page=Quality Profile
+bulk_deletion.page=Bulk Deletion
# GWT pages
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
}
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) {
--- /dev/null
+#
+# 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
#
#
-# Class taht centralizes the management the DB migration
+# Class that centralizes the management the DB migration
#
require 'singleton'
--- /dev/null
+#
+# 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
--- /dev/null
+<h1 class="marginbottom10"><%= message('bulk_deletion.page') -%></h1>
+
+<ul class="tabs">
+ <li>
+ <a href="<%= url_for :action => 'index', :resource_type => 'projects' %>" <%= "class='selected'" if @selected_tab=='projects' -%>><%= message('bulk_deletion.resource.projects') -%></a>
+ </li>
+ <% if @should_display_views_tab %>
+ <li>
+ <a href="<%= url_for :action => 'index', :resource_type => 'views' -%>" <%= "class='selected'" if @selected_tab=='views' -%>><%= message('bulk_deletion.resource.views') -%></a>
+ </li>
+ <% end %>
+ <% if @should_display_devs_tab %>
+ <li>
+ <a href="<%= url_for :action => 'index', :resource_type => 'devs' -%>" <%= "class='selected'" if @selected_tab=='devs' -%>><%= message('bulk_deletion.resource.devs') -%></a>
+ </li>
+ <% end %>
+</ul>
+
+<div class="tabs-panel marginbottom10">
+
+<%
+ 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') -%>: <input type="text" id="resource_filter" name="name_filter" size="40px" value=""/>
+ <input type="hidden" name="resource_type" value="<%= @selected_tab -%>"/>
+ <%= submit_tag message('bulk_deletion.search'), :id => 'filter_resources' %>
+ <% end %>
+
+ <% if @resources.empty? %>
+ <br/>
+ <%= message('bulk_deletion.no_resource_to_delete') -%>
+ <% else %>
+
+ <% form_remote_tag( :url => {:action => 'delete_resources'}, :loading => "window.location='#{url_for :action => 'pending_deletions'}';") do %>
+
+ <table class="data">
+ <tfoot>
+ <tr>
+ <td colspan="2"><%= paginate @resources, {:page_size => page_size} %></td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <%= submit_tag message('delete'), :id => 'delete_resources', :class => 'action red-button', :confirm => message('bulk_deletion.sure_to_delete_the_resources') %>
+ </td>
+ </tr>
+ </tfoot>
+ <thead>
+ <tr>
+ <th><input id="r-all" type="checkbox" onclick="selectOrDeselect()"></th>
+ <th>
+ <span>« <%= message('bulk_deletion.select_all') -%></span>
+ <% if found_resources_count - @resources.size > 0 %>
+ <a id="select_all_action" style="padding-left: 10px; font-weight: normal; display: none"
+ href="#" onclick="handleSelectAllAction(); return false;"><%= message('bulk_deletion.select_all_x_resources', :params => found_resources_count) -%></a>
+ <input type="hidden" id="all_resources" name="all_resources" value=""/>
+ <% end %>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <% @resources.each_with_index do |resource, index| %>
+ <tr class="<%= cycle 'even', 'odd' -%>">
+ <td class="thin">
+ <input id="r-<%= index -%>" type="checkbox" value="<%= resource.id -%>" name="resources[]">
+ </td>
+ <td><%= resource.name -%></td>
+ </tr>
+ <% end %>
+ </tbody>
+ </table>
+
+ <% end %>
+
+ <script>
+ function selectOrDeselect() {
+ status = $('r-all').checked
+ $$('tbody input').each(function(input) {
+ input.checked = status;
+ });
+ <% if found_resources_count - @resources.size > 0 %>
+ selectNotAllResources();
+ if (status) {
+ $('select_all_action').show();
+ } else {
+ $('select_all_action').hide();
+ }
+ <% end %>
+ }
+
+ function handleSelectAllAction() {
+ if ($('all_resources').value=='') {
+ selectAllResources();
+ } else {
+ $('r-all').checked = false;
+ selectOrDeselect();
+ }
+ }
+
+ function selectAllResources() {
+ $('all_resources').value = '<%= found_resources_ids -%>';
+ $('select_all_action').text = '<%= message('bulk_deletion.clear_selection', :params => found_resources_count) -%>';
+ }
+
+ function selectNotAllResources() {
+ $('all_resources').value = '';
+ $('select_all_action').text = '<%= message('bulk_deletion.select_all_x_resources', :params => found_resources_count) -%>';
+ }
+ </script>
+
+ <% end %>
+
+</div>
\ No newline at end of file
--- /dev/null
+<%
+ pending_deletions = @deletion_manager.currently_deleting_resources?
+ failed_deletions = @deletion_manager.failed_deletions
+%>
+
+<% if pending_deletions %>
+ <meta http-equiv='refresh' content='5;'>
+<% end %>
+
+<h1 class="marginbottom10"><%= message('bulk_deletion.page') -%></h1>
+
+<div class="<%= pending_deletions ? 'admin' : 'error' -%>" style="padding:10px">
+ <% if pending_deletions %>
+ <%= image_tag 'loading.gif' -%>
+ <% end %>
+
+ <b><%= @deletion_manager.message -%></b>
+ <br/>
+ <br/>
+
+ <% if !pending_deletions && !failed_deletions.empty? %>
+ <p>
+ <%= message('bulk_deletion.following_deletions_failed') -%>
+ <ul style="list-style: none outside; padding-left: 30px;">
+ <% failed_deletions.each do |name| %>
+ <li style="list-style: disc outside; padding: 2px;"><%= name -%></li>
+ <% end %>
+ </ul>
+ </p>
+ <p>
+ <%= link_to message('bulk_deletion.hide_message'), :action => 'dismiss_message' -%>
+ </p>
+ <% end %>
+</div>
\ No newline at end of file
<a href="<%= ApplicationController.root_context -%>/settings/index"><%= message('settings.page') -%></a></li>
<li class="<%= 'selected' if controller.controller_path=='backup' -%>"><a href="<%= ApplicationController.root_context -%>/backup"><%= message('backup.page') -%></a>
</li>
+ <li class="<%= 'selected' if controller.controller_path=='bulk_deletion' -%>"><a href="<%= ApplicationController.root_context -%>/bulk_deletion"><%= message('bulk_deletion.page') -%></a>
+ </li>
<li class="<%= 'selected' if controller.controller_path=='system' -%>">
<a href="<%= ApplicationController.root_context -%>/system"><%= message('system_info.page') -%></a></li>
<% update_center_activated = controller.java_facade.getSettings().getBoolean('sonar.updatecenter.activate')