diff options
author | Fabrice Bellingard <bellingard@gmail.com> | 2012-07-04 15:07:05 +0200 |
---|---|---|
committer | Fabrice Bellingard <bellingard@gmail.com> | 2012-07-04 16:03:00 +0200 |
commit | e6fa0eca60331bf2a050c19bca1b7c61b4b9284e (patch) | |
tree | c129495856f0cbce4bc3a6a6e0bbaf1c7e78e348 | |
parent | df87d3af318a83f788e329b32a040f8b5d34f4a9 (diff) | |
download | sonarqube-e6fa0eca60331bf2a050c19bca1b7c61b4b9284e.tar.gz sonarqube-e6fa0eca60331bf2a050c19bca1b7c61b4b9284e.zip |
SONAR-1608 Display all impacts of bulk change in confirmation page
+ allow to replace any string of the key, not only the beginning.
9 files changed, 210 insertions, 76 deletions
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 "<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 #------------------------------------------------------------------------------ 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<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."); } 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 @@ -66,6 +70,15 @@ public class ResourceKeyUpdaterDaoTest extends DaoTestCase { } @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"); + } + } 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 @@ +<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> 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<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); } } 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 +%> + <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 %>
\ 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' %> <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 -%>
\ 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' +%> + + <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 %>
\ No newline at end of file |