From e6fa0eca60331bf2a050c19bca1b7c61b4b9284e Mon Sep 17 00:00:00 2001 From: Fabrice Bellingard Date: Wed, 4 Jul 2012 15:07:05 +0200 Subject: [PATCH] SONAR-1608 Display all impacts of bulk change in confirmation page + allow to replace any string of the key, not only the beginning. --- .../resources/org/sonar/l10n/core.properties | 5 +- .../core/resource/ResourceKeyUpdaterDao.java | 61 ++++++++++++++----- .../resource/ResourceKeyUpdaterDaoTest.java | 24 ++++++++ ...BulkUpdateKeyOnOnlyOneSubmodule-result.xml | 50 +++++++++++++++ .../java/org/sonar/server/ui/JRubyFacade.java | 8 ++- .../app/controllers/project_controller.rb | 34 ++++------- .../app/views/project/_prepare_keys.html.erb | 19 ++++++ .../WEB-INF/app/views/project/key.html.erb | 36 +---------- .../project/prepare_key_bulk_update.html.erb | 49 +++++++++++++++ 9 files changed, 210 insertions(+), 76 deletions(-) create mode 100644 sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKeyOnOnlyOneSubmodule-result.xml create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/project/_prepare_keys.html.erb create mode 100644 sonar-server/src/main/webapp/WEB-INF/app/views/project/prepare_key_bulk_update.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 089ea73a2ac..bc17add3c72 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 @@ -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 "{1}". 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 "{0}{1}" into "{2}{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 #------------------------------------------------------------------------------ diff --git a/sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterDao.java b/sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterDao.java index d6d8dbf1a70..e04f0df982c 100644 --- a/sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterDao.java +++ b/sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterDao.java @@ -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 checkModuleKeysBeforeRenaming(long projectId, String stringToReplace, String replacementString) { + SqlSession session = mybatis.openSession(); + ResourceKeyUpdaterMapper mapper = session.getMapper(ResourceKeyUpdaterMapper.class); + Map result = Maps.newHashMap(); + try { + Set 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 modules = collectAllModules(projectId, oldPrefix, mapper); - checkNewNameOfAllModules(modules, oldPrefix, newPrefix, mapper); - Set allResources = Sets.newHashSet(modules); + Set modules = collectAllModules(projectId, stringToReplace, mapper); + checkNewNameOfAllModules(modules, stringToReplace, replacementString, mapper); + Map> 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 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 resources, String oldPrefix, String newPrefix, ResourceKeyUpdaterMapper mapper) { + private void runBatchUpdateForAllResources(Collection 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 collectAllModules(long projectId, String oldPrefix, ResourceKeyUpdaterMapper mapper) { + private Set collectAllModules(long projectId, String stringToReplace, ResourceKeyUpdaterMapper mapper) { ResourceDto project = mapper.selectProject(projectId); Set 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 modules, String oldPrefix, String newPrefix, ResourceKeyUpdaterMapper mapper) { + private void checkNewNameOfAllModules(Set 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."); } diff --git a/sonar-core/src/test/java/org/sonar/core/resource/ResourceKeyUpdaterDaoTest.java b/sonar-core/src/test/java/org/sonar/core/resource/ResourceKeyUpdaterDaoTest.java index bfa64d86dc4..d1688d27a37 100644 --- a/sonar-core/src/test/java/org/sonar/core/resource/ResourceKeyUpdaterDaoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/resource/ResourceKeyUpdaterDaoTest.java @@ -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 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"); + } + } diff --git a/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKeyOnOnlyOneSubmodule-result.xml b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKeyOnOnlyOneSubmodule-result.xml new file mode 100644 index 00000000000..461018f4fa5 --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKeyOnOnlyOneSubmodule-result.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 f930f742a0a..76ba04b7168 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 @@ -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 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); } } diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb index b03aebea158..ba9e1eb1059 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb @@ -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') diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/project/_prepare_keys.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/project/_prepare_keys.html.erb new file mode 100644 index 00000000000..1624108b2d8 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/project/_prepare_keys.html.erb @@ -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 +%> + + + <%= h(current_module.key) -%> + + + <%= future_key ? future_key : current_module.key -%> + + + <% 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 %> \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/project/key.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/project/key.html.erb index e28d9f59481..aa2bd82a763 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/project/key.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/project/key.html.erb @@ -1,6 +1,7 @@ <% if controller.java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'updatable_key') has_modules = !@project.modules.empty? + reset_cycle 'modules_tree' %> -<% - if @old_prefix && @new_prefix - # validation screen for bulk update - key_suffix = @project.key[@old_prefix.size..@project.key.size] -%> - -

<%= message('update_key.bulk_update') -%>

-
-

- <%= message('update_key.are_you_sure_to_bulk_rename_x_into_x', :params => [@old_prefix, key_suffix, @new_prefix]) -%> -

- <% form_tag( {:action => 'perform_key_bulk_update', :id => @project.id }) do -%> - - - -
- <%= submit_tag message('update_key.rename'), :id => 'bulk_update_button', :class => 'action', - :onclick => "update_launched();$('loading_bulk_update').show();" %> - <%= message('cancel') -%> - - <% end %> - -<% - else - # main screen - reset_cycle 'modules_tree' -%> - <% if has_modules %>

<%= message('update_key.bulk_update') -%>


@@ -77,7 +50,7 @@

<%= has_modules ? message('update_key.fine_grained_key_update') : message('update_key.update_resource_key') -%>


- +
@@ -89,7 +62,4 @@
<%= message('update_key.old_key') -%>
-<% - end - end -%> \ No newline at end of file +<% end %> \ No newline at end of file diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/project/prepare_key_bulk_update.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/project/prepare_key_bulk_update.html.erb new file mode 100644 index 00000000000..7d03a1af676 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/project/prepare_key_bulk_update.html.erb @@ -0,0 +1,49 @@ +<% + if @old_prefix && @new_prefix + # validation screen for bulk update + reset_cycle 'modules_tree' +%> + + + +

<%= message('update_key.bulk_update') -%>

+
+

+ <%= @can_update ? message('update_key.keys_will_be_updated_as_follows') : message('update_key.cant_update_because_duplicate_keys', :params => [@old_prefix, @new_prefix]) -%> +

+ + + + + + + + + + <%= render :partial => 'prepare_keys', :locals => {:current_module => @project, :module_depth => 0, :key_check_results => @key_check_results} -%> + +
<%= message('update_key.old_key') -%><%= message('update_key.new_key') -%>
+ + <% if @can_update %> + <% form_tag( {:action => 'perform_key_bulk_update', :id => @project.id }) do -%> + + + +
+ <%= submit_tag message('update_key.rename'), :id => 'bulk_update_button', :class => 'action', + :onclick => "update_launched();$('loading_bulk_update').show();" %> + <%= message('cancel') -%> + + <% end %> + <% else %> +
+ <%= message('back') -%> + <% end %> + +<% end %> \ No newline at end of file -- 2.39.5