Browse Source

SONAR-1608 Display all impacts of bulk change in confirmation page

+ allow to replace any string of the key, not only the beginning.
tags/3.2
Fabrice Bellingard 12 years ago
parent
commit
e6fa0eca60

+ 3
- 2
plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties View File

@@ -969,15 +969,16 @@ update_key.cant_update_x_because_resource_already_exist_with_key_x="{0}" can not
update_key.error_occured_while_renaming_key_of_x=An error occurred while renaming the key "{0}": {1}
update_key.key_updated=The key has successfully been updated for all required resources.
update_key.fieds_cant_be_blank_for_bulk_update=The two fields can not be blank for the bulk update.
update_key.key_does_not_start_with_x=The current key ("{0}") does not start with "{1}". The update is impossible.
update_key.bulk_change_description=The bulk update allows to replace the beginning of the current key by another string on the current project and all its submodules - if applicable.
update_key.current_key_for_project_x_is_x=The key of the "{0}" project is currently "<b>{1}</b>".
update_key.are_you_sure_to_rename_x=Are you sure you want to rename "{0}", as well as all its modules and resources ?
update_key.are_you_sure_to_bulk_rename_x_into_x=Are you sure you want to rename "<b>{0}</b>{1}" into "<b>{2}</b>{1}", as well as all its modules and resources ?
update_key.replace=Replace
update_key.by=By
update_key.replace_example=Ex.: "org.myCompany"
update_key.by_example=Ex.: "com.myNewCompany"
update_key.cant_update_because_duplicate_keys=The replacement of "{0}" by "{1}" is impossible as it would result in duplicate keys (in red below):
update_key.keys_will_be_updated_as_follows=The resources will be updated as follows (updated keys in bold):
update_key.duplicate_key=Duplicate key


#------------------------------------------------------------------------------

+ 45
- 16
sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterDao.java View File

@@ -19,12 +19,15 @@
*/
package org.sonar.core.resource;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.ibatis.session.SqlSession;
import org.sonar.core.persistence.MyBatis;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
@@ -62,20 +65,46 @@ public class ResourceKeyUpdaterDao {
}
}

public void bulkUpdateKey(long projectId, String oldPrefix, String newPrefix) {
public Map<String, String> checkModuleKeysBeforeRenaming(long projectId, String stringToReplace, String replacementString) {
SqlSession session = mybatis.openSession();
ResourceKeyUpdaterMapper mapper = session.getMapper(ResourceKeyUpdaterMapper.class);
Map<String, String> result = Maps.newHashMap();
try {
Set<ResourceDto> modules = collectAllModules(projectId, stringToReplace, mapper);
for (ResourceDto module : modules) {
String newKey = computeNewKey(module, stringToReplace, replacementString);
if (mapper.countResourceByKey(newKey) > 0) {
result.put(module.getKey(), "#duplicate_key#");
} else {
result.put(module.getKey(), newKey);
}
}
} finally {
MyBatis.closeQuietly(session);
}
return result;
}

public void bulkUpdateKey(long projectId, String stringToReplace, String replacementString) {
SqlSession session = mybatis.openBatchSession();
ResourceKeyUpdaterMapper mapper = session.getMapper(ResourceKeyUpdaterMapper.class);
try {
// must SELECT first everything
Set<ResourceDto> modules = collectAllModules(projectId, oldPrefix, mapper);
checkNewNameOfAllModules(modules, oldPrefix, newPrefix, mapper);
Set<ResourceDto> allResources = Sets.newHashSet(modules);
Set<ResourceDto> modules = collectAllModules(projectId, stringToReplace, mapper);
checkNewNameOfAllModules(modules, stringToReplace, replacementString, mapper);
Map<ResourceDto, List<ResourceDto>> allResourcesByModuleMap = Maps.newHashMap();
for (ResourceDto module : modules) {
allResources.addAll(mapper.selectProjectResources(module.getId()));
allResourcesByModuleMap.put(module, mapper.selectProjectResources(module.getId()));
}

// and then proceed with the batch UPDATE at once
runBatchUpdateForAllResources(allResources, oldPrefix, newPrefix, mapper);
for (ResourceDto module : modules) {
String oldModuleKey = module.getKey();
String newModuleKey = computeNewKey(module, stringToReplace, replacementString);
Collection<ResourceDto> resources = Lists.newArrayList(module);
resources.addAll(allResourcesByModuleMap.get(module));
runBatchUpdateForAllResources(resources, oldModuleKey, newModuleKey, mapper);
}

session.commit();
} finally {
@@ -83,33 +112,33 @@ public class ResourceKeyUpdaterDao {
}
}

private String computeNewKey(ResourceDto resource, String oldPrefix, String newPrefix) {
String resourceKey = resource.getKey();
return newPrefix + resourceKey.substring(oldPrefix.length(), resourceKey.length());
private String computeNewKey(ResourceDto resource, String stringToReplace, String replacementString) {
return resource.getKey().replaceAll(stringToReplace, replacementString);
}

private void runBatchUpdateForAllResources(Collection<ResourceDto> resources, String oldPrefix, String newPrefix, ResourceKeyUpdaterMapper mapper) {
private void runBatchUpdateForAllResources(Collection<ResourceDto> resources, String oldKey, String newKey, ResourceKeyUpdaterMapper mapper) {
for (ResourceDto resource : resources) {
resource.setKey(computeNewKey(resource, oldPrefix, newPrefix));
String resourceKey = resource.getKey();
resource.setKey(newKey + resourceKey.substring(oldKey.length(), resourceKey.length()));
mapper.update(resource);
}
}

private Set<ResourceDto> collectAllModules(long projectId, String oldPrefix, ResourceKeyUpdaterMapper mapper) {
private Set<ResourceDto> collectAllModules(long projectId, String stringToReplace, ResourceKeyUpdaterMapper mapper) {
ResourceDto project = mapper.selectProject(projectId);
Set<ResourceDto> modules = Sets.newHashSet();
if (project.getKey().startsWith(oldPrefix)) {
if (project.getKey().contains(stringToReplace)) {
modules.add(project);
}
for (ResourceDto submodule : mapper.selectDescendantProjects(projectId)) {
modules.addAll(collectAllModules(submodule.getId(), oldPrefix, mapper));
modules.addAll(collectAllModules(submodule.getId(), stringToReplace, mapper));
}
return modules;
}

private void checkNewNameOfAllModules(Set<ResourceDto> modules, String oldPrefix, String newPrefix, ResourceKeyUpdaterMapper mapper) {
private void checkNewNameOfAllModules(Set<ResourceDto> modules, String stringToReplace, String replacementString, ResourceKeyUpdaterMapper mapper) {
for (ResourceDto module : modules) {
String newName = computeNewKey(module, oldPrefix, newPrefix);
String newName = computeNewKey(module, stringToReplace, replacementString);
if (mapper.countResourceByKey(newName) > 0) {
throw new IllegalStateException("Impossible to update key: a resource with \"" + newName + "\" key already exists.");
}

+ 24
- 0
sonar-core/src/test/java/org/sonar/core/resource/ResourceKeyUpdaterDaoTest.java View File

@@ -25,6 +25,10 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.core.persistence.DaoTestCase;

import java.util.Map;

import static org.fest.assertions.Assertions.assertThat;

public class ResourceKeyUpdaterDaoTest extends DaoTestCase {

@Rule
@@ -65,6 +69,15 @@ public class ResourceKeyUpdaterDaoTest extends DaoTestCase {
checkTables("shouldBulkUpdateKey", "projects");
}

@Test
public void shouldBulkUpdateKeyOnOnlyOneSubmodule() {
setupData("shared");

dao.bulkUpdateKey(1, "struts-ui", "struts-web");

checkTables("shouldBulkUpdateKeyOnOnlyOneSubmodule", "projects");
}

@Test
public void shouldFailBulkUpdateKeyIfKeyAlreadyExist() {
setupData("shared");
@@ -84,4 +97,15 @@ public class ResourceKeyUpdaterDaoTest extends DaoTestCase {
checkTables("shouldNotUpdateAllSubmodules", "projects");
}

@Test
public void shouldCheckModuleKeysBeforeRenaming() {
setupData("shared");

Map<String, String> checkResults = dao.checkModuleKeysBeforeRenaming(1, "org.struts", "foo");
assertThat(checkResults.size()).isEqualTo(3);
assertThat(checkResults.get("org.struts:struts")).isEqualTo("foo:struts");
assertThat(checkResults.get("org.struts:struts-core")).isEqualTo("update_impossible");
assertThat(checkResults.get("org.struts:struts-ui")).isEqualTo("foo:struts-ui");
}

}

+ 50
- 0
sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKeyOnOnlyOneSubmodule-result.xml View File

@@ -0,0 +1,50 @@
<dataset>

<!-- root project -->
<projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.struts:struts" name="Struts"
description="[null]" long_name="Apache Struts" profile_id="1"
enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>


<!-- **************** First sub project **************** -->
<projects id="2" root_id="1" kee="org.struts:struts-core" name="Struts Core"
scope="PRJ" qualifier="BRC" long_name="Struts Core" profile_id="1"
description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>

<!-- directory -->
<projects long_name="org.struts" id="3" scope="DIR" qualifier="PAC" kee="org.struts:struts-core:org.struts"
name="org.struts" root_id="2"
description="[null]" profile_id="1"
enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>

<!-- file -->
<projects long_name="org.struts.RequestContext" id="4" scope="FIL" qualifier="CLA" kee="org.struts:struts-core:org.struts.RequestContext"
name="RequestContext" root_id="2"
description="[null]" profile_id="1"
enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>


<!-- **************** Second sub project **************** -->
<projects id="5" root_id="1" kee="org.struts:struts-web" name="Struts UI"
scope="PRJ" qualifier="BRC" long_name="Struts UI" profile_id="1"
description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>

<!-- directory -->
<projects long_name="org.struts" id="6" scope="DIR" qualifier="PAC" kee="org.struts:struts-web:org.struts"
name="org.struts" root_id="5"
description="[null]" profile_id="1"
enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>

<!-- file -->
<projects long_name="org.struts.RequestContext" id="7" scope="FIL" qualifier="CLA" kee="org.struts:struts-web:org.struts.RequestContext"
name="RequestContext" root_id="5"
description="[null]" profile_id="1"
enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>


<!-- **************** Another independent project **************** -->
<projects id="8" root_id="[null]" kee="foo:struts-core" name="Foo Struts Core"
scope="PRJ" qualifier="BRC" long_name="Foo Struts Core" profile_id="1"
description="[null]" enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>

</dataset>

+ 6
- 2
sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java View File

@@ -516,8 +516,12 @@ public final class JRubyFacade {
getContainer().getComponentByType(ResourceKeyUpdaterDao.class).updateKey(projectId, newKey);
}

public void bulkUpdateKey(long projectId, String oldPrefix, String newPrefix) {
getContainer().getComponentByType(ResourceKeyUpdaterDao.class).bulkUpdateKey(projectId, oldPrefix, newPrefix);
public Map<String, String> checkModuleKeysBeforeRenaming(long projectId, String stringToReplace, String replacementString) {
return getContainer().getComponentByType(ResourceKeyUpdaterDao.class).checkModuleKeysBeforeRenaming(projectId, stringToReplace, replacementString);
}

public void bulkUpdateKey(long projectId, String stringToReplace, String replacementString) {
getContainer().getComponentByType(ResourceKeyUpdaterDao.class).bulkUpdateKey(projectId, stringToReplace, replacementString);
}

}

+ 11
- 23
sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb View File

@@ -71,11 +71,6 @@ class ProjectController < ApplicationController
def key
@project = get_current_project(params[:id])
if params[:old_prefix] && params[:new_prefix]
@old_prefix = params[:old_prefix]
@new_prefix = params[:new_prefix]
end
end
def update_key
@@ -102,27 +97,20 @@ class ProjectController < ApplicationController
end
def prepare_key_bulk_update
project = get_current_project(params[:id])
@project = get_current_project(params[:id])
old_prefix = params[:old_prefix].strip
new_prefix = params[:new_prefix].strip
if old_prefix.blank? || new_prefix.blank?
error_message = message('update_key.fieds_cant_be_blank_for_bulk_update')
elsif !project.key.start_with?(old_prefix)
error_message = message('update_key.key_does_not_start_with_x', :params => [project.key, old_prefix])
@old_prefix = params[:old_prefix].strip
@new_prefix = params[:new_prefix].strip
if @old_prefix.blank? || @new_prefix.blank?
flash[:error] = message('update_key.fieds_cant_be_blank_for_bulk_update')
redirect_to :action => 'key', :id => @project.id
else
new_key = new_prefix + project.key[old_prefix.size..project.key.size]
if Project.by_key(new_key)
error_message = message('update_key.cant_update_x_because_resource_already_exist_with_key_x', :params => [project.key, new_key])
@key_check_results = java_facade.checkModuleKeysBeforeRenaming(@project.id, @old_prefix, @new_prefix)
@can_update = true
@key_check_results.each do |key, value|
@can_update = false if value=="#duplicate_key#"
end
end
if error_message
flash[:error] = error_message
redirect_to :action => 'key', :id => project.id
else
redirect_to :action => 'key', :id => project.id, :old_prefix => old_prefix, :new_prefix => new_prefix
end
end

def perform_key_bulk_update
@@ -131,7 +119,7 @@ class ProjectController < ApplicationController
old_prefix = params[:old_prefix].strip
new_prefix = params[:new_prefix].strip
unless old_prefix.blank? || new_prefix.blank? || !project.key.start_with?(old_prefix)
unless old_prefix.blank? || new_prefix.blank?
begin
java_facade.bulkUpdateKey(project.id, old_prefix, new_prefix)
flash[:notice] = message('update_key.key_updated')

+ 19
- 0
sonar-server/src/main/webapp/WEB-INF/app/views/project/_prepare_keys.html.erb View File

@@ -0,0 +1,19 @@
<%
future_key = key_check_results[current_module.key]
if future_key=="#duplicate_key#"
duplicate_key = true
future_key = message('update_key.duplicate_key')
end
%>
<tr class="<%= cycle 'even', 'odd', :name => 'modules_tree' -%>">
<td class="thin nowrap" style="padding-left: <%= 3+ module_depth*15 -%>px;">
<%= h(current_module.key) -%>
</td>
<td class="thin nowrap <%= 'error' if duplicate_key -%>" style="<%= 'font-weight: bold;' if future_key -%>">
<%= future_key ? future_key : current_module.key -%>
</td>
</tr>
<% current_module.modules.each_with_index do |sub_module, index| %>
<%= render :partial => 'prepare_keys', :locals => {:current_module => sub_module, :module_depth => module_depth+1,
:key_check_results => key_check_results} -%>
<% end %>

+ 3
- 33
sonar-server/src/main/webapp/WEB-INF/app/views/project/key.html.erb View File

@@ -1,6 +1,7 @@
<%
if controller.java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'updatable_key')
has_modules = !@project.modules.empty?
reset_cycle 'modules_tree'
%>

<script type="text/javascript">
@@ -11,34 +12,6 @@
}
</script>

<%
if @old_prefix && @new_prefix
# validation screen for bulk update
key_suffix = @project.key[@old_prefix.size..@project.key.size]
%>
<h1><%= message('update_key.bulk_update') -%></h1>
<br/>
<p>
<%= message('update_key.are_you_sure_to_bulk_rename_x_into_x', :params => [@old_prefix, key_suffix, @new_prefix]) -%>
</p>
<% form_tag( {:action => 'perform_key_bulk_update', :id => @project.id }) do -%>
<input type="hidden" value="<%= @project.id -%>" name="id" id="project_id">
<input type="hidden" value="<%= @old_prefix -%>" name="old_prefix" id="old_prefix">
<input type="hidden" value="<%= @new_prefix -%>" name="new_prefix" id="new_prefix">
<br/>
<%= submit_tag message('update_key.rename'), :id => 'bulk_update_button', :class => 'action',
:onclick => "update_launched();$('loading_bulk_update').show();" %>
<a href="<%= url_for :action => 'key', :id => @project.key -%>"><%= message('cancel') -%></a>
<span class="loading" id="loading_bulk_update" style="display: none; padding: 3px 10px;"></span>
<% end %>
<%
else
# main screen
reset_cycle 'modules_tree'
%>

<% if has_modules %>
<h1><%= message('update_key.bulk_update') -%></h1>
<br/>
@@ -77,7 +50,7 @@

<h1><%= has_modules ? message('update_key.fine_grained_key_update') : message('update_key.update_resource_key') -%></h1>
<br/>
<table id="project-history" class="data" style="width:1%">
<table class="data" style="width:1%">
<thead>
<tr>
<th><%= message('update_key.old_key') -%></th>
@@ -89,7 +62,4 @@
</tbody>
</table>
<%
end
end
%>
<% end %>

+ 49
- 0
sonar-server/src/main/webapp/WEB-INF/app/views/project/prepare_key_bulk_update.html.erb View File

@@ -0,0 +1,49 @@
<%
if @old_prefix && @new_prefix
# validation screen for bulk update
reset_cycle 'modules_tree'
%>

<script type="text/javascript">
function update_launched() {
$$('input.action').each(function(input) {
input.disabled=true;
});
}
</script>
<h1><%= message('update_key.bulk_update') -%></h1>
<br/>
<p>
<%= @can_update ? message('update_key.keys_will_be_updated_as_follows') : message('update_key.cant_update_because_duplicate_keys', :params => [@old_prefix, @new_prefix]) -%>
</p>
<table class="data" style="width:1%; margin-top: 10px">
<thead>
<tr>
<th><%= message('update_key.old_key') -%></th>
<th><%= message('update_key.new_key') -%></th>
</tr>
</thead>
<tbody>
<%= render :partial => 'prepare_keys', :locals => {:current_module => @project, :module_depth => 0, :key_check_results => @key_check_results} -%>
</tbody>
</table>
<% if @can_update %>
<% form_tag( {:action => 'perform_key_bulk_update', :id => @project.id }) do -%>
<input type="hidden" value="<%= @project.id -%>" name="id" id="project_id">
<input type="hidden" value="<%= @old_prefix -%>" name="old_prefix" id="old_prefix">
<input type="hidden" value="<%= @new_prefix -%>" name="new_prefix" id="new_prefix">
<br/>
<%= submit_tag message('update_key.rename'), :id => 'bulk_update_button', :class => 'action',
:onclick => "update_launched();$('loading_bulk_update').show();" %>
<a href="<%= url_for :action => 'key', :id => @project.key -%>"><%= message('cancel') -%></a>
<span class="loading" id="loading_bulk_update" style="display: none; padding: 3px 10px;"></span>
<% end %>
<% else %>
<br/>
<a href="<%= url_for :action => 'key', :id => @project.key -%>"><%= message('back') -%></a>
<% end %>
<% end %>

Loading…
Cancel
Save