]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-1608 Make it possible to modify the key of projects
authorFabrice Bellingard <bellingard@gmail.com>
Fri, 29 Jun 2012 09:21:53 +0000 (11:21 +0200)
committerFabrice Bellingard <bellingard@gmail.com>
Fri, 29 Jun 2012 09:23:56 +0000 (11:23 +0200)
21 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/DefaultResourceTypes.java
plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/core.properties
sonar-core/src/main/java/org/sonar/core/persistence/DaoUtils.java
sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java
sonar-core/src/main/java/org/sonar/core/resource/ResourceDto.java
sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterDao.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterMapper.java [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/core/resource/ResourceKeyUpdaterMapper.xml [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/resource/ResourceKeyUpdaterDaoTest.java [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shared.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKey-result.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules-result.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldUpdateKey-result.xml [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/project_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/models/project.rb
sonar-server/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb
sonar-server/src/main/webapp/WEB-INF/app/views/project/_key_modules.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/project/key.html.erb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/app/views/project/settings.html.erb

index 3ddd12dc63a8f8656cb2356ca63986b43b2f20a2..78ff963e9e0b91d37af2cc965959510d915a0db5 100644 (file)
@@ -38,8 +38,11 @@ public final class DefaultResourceTypes extends ExtensionProvider implements Bat
             .setProperty("deletable", "true")
             .setProperty("modifiable_history", "true")
             .setProperty("hasRolePolicy", "true")
+            .setProperty("updatable_key", "true")
+            .build())
+        .addType(ResourceType.builder(Qualifiers.MODULE)
+            .setProperty("updatable_key", "true")
             .build())
-        .addType(ResourceType.builder(Qualifiers.MODULE).build())
         .addType(ResourceType.builder(Qualifiers.DIRECTORY).build())
         .addType(ResourceType.builder(Qualifiers.PACKAGE).build())
         .addType(ResourceType.builder(Qualifiers.FILE).hasSourceCode().build())
index 74b8ddaf5185b6b6c0c5ac63a9425d711ee190fa..755f6f02243375bf85b9f102e548882cf2093360 100644 (file)
@@ -329,6 +329,7 @@ update_center.page=Update Center
 lcom4_viewer.page=LCOM4
 dependencies.page=Dependencies
 resource_deletion.page={0} Deletion
+update_key.page=Update Key
 
 
 # GWT pages
@@ -947,6 +948,30 @@ project_history.event_created=Event "{0}" was created.
 project_history.event_already_exists=Event "{0}" already exists.
 
 
+#------------------------------------------------------------------------------
+#
+# PROJECT / MODULE "UPDATE KEY" PAGE
+#
+#------------------------------------------------------------------------------
+update_key.update_resource_key=Update Key
+update_key.bulk_update=Bulk Update
+update_key.fine_grained_key_update=Fine-grained Update
+update_key.old_key=Old key
+update_key.new_key=New key
+update_key.rename=Rename
+update_key.reset=Reset
+update_key.new_key_cant_be_blank_for_x=The new key can not be blank for "{0}".
+update_key.same_key_for_x=The new key is the same as the original one ("{0}"), nothing has been updated.
+update_key.cant_update_x_because_resource_already_exist_with_key_x="{0}" can not be renamed because "{1}" is the key of an existing resource. The update has been canceled.
+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 ?
+
 #------------------------------------------------------------------------------
 #
 # PROJECT (RESOURCE) DELETION PAGE
index 8014061c509f1dbbb88143dab68cdb3cae187bf2..602181cdd449d3032f71ee1d4d63480ff76430e9 100644 (file)
@@ -28,6 +28,7 @@ import org.sonar.core.properties.PropertiesDao;
 import org.sonar.core.purge.PurgeDao;
 import org.sonar.core.resource.ResourceDao;
 import org.sonar.core.resource.ResourceIndexerDao;
+import org.sonar.core.resource.ResourceKeyUpdaterDao;
 import org.sonar.core.review.ReviewCommentDao;
 import org.sonar.core.review.ReviewDao;
 import org.sonar.core.rule.RuleDao;
@@ -54,6 +55,7 @@ public final class DaoUtils {
         PurgeDao.class,
         ResourceIndexerDao.class,
         ResourceDao.class,
+        ResourceKeyUpdaterDao.class,
         ReviewCommentDao.class,
         ReviewDao.class,
         RuleDao.class);
index 6f1d450e016fc32f7961981eac66954927344f0b..dd7d58cb20ae10a8792838f16d0beefafdb23690 100644 (file)
@@ -61,6 +61,7 @@ import org.sonar.core.purge.PurgeableSnapshotDto;
 import org.sonar.core.resource.ResourceDto;
 import org.sonar.core.resource.ResourceIndexDto;
 import org.sonar.core.resource.ResourceIndexerMapper;
+import org.sonar.core.resource.ResourceKeyUpdaterMapper;
 import org.sonar.core.resource.ResourceMapper;
 import org.sonar.core.resource.SnapshotDto;
 import org.sonar.core.review.ReviewCommentDto;
@@ -130,6 +131,7 @@ public class MyBatis implements BatchComponent, ServerComponent {
     loadMapper(conf, PurgeMapper.class);
     loadMapper(conf, PurgeVendorMapper.class);
     loadMapper(conf, ResourceMapper.class);
+    loadMapper(conf, ResourceKeyUpdaterMapper.class);
     loadMapper(conf, ReviewCommentMapper.class);
     loadMapper(conf, ReviewMapper.class);
     loadMapper(conf, ResourceIndexerMapper.class);
index 212b954fc88120682460d17217efce0aada5ef05..193984ccbff897890595e780a1722802dc00232b 100644 (file)
@@ -91,4 +91,30 @@ public final class ResourceDto {
     this.qualifier = qualifier;
     return this;
   }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((id == null) ? 0 : id.hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj)
+      return true;
+    if (obj == null)
+      return false;
+    if (getClass() != obj.getClass())
+      return false;
+    ResourceDto other = (ResourceDto) obj;
+    if (id == null) {
+      if (other.id != null)
+        return false;
+    } else if (!id.equals(other.id))
+      return false;
+    return true;
+  }
+
 }
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
new file mode 100644 (file)
index 0000000..d6d8dbf
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Sonar, open source software quality management 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
+ */
+package org.sonar.core.resource;
+
+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.Set;
+
+/**
+ * Class used to rename the key of a project and its resources. 
+ * 
+ * @since 3.2
+ */
+public class ResourceKeyUpdaterDao {
+  private MyBatis mybatis;
+
+  public ResourceKeyUpdaterDao(MyBatis mybatis) {
+    this.mybatis = mybatis;
+  }
+
+  public void updateKey(long projectId, String newKey) {
+    SqlSession session = mybatis.openBatchSession();
+    ResourceKeyUpdaterMapper mapper = session.getMapper(ResourceKeyUpdaterMapper.class);
+    try {
+      if (mapper.countResourceByKey(newKey) > 0) {
+        throw new IllegalStateException("Impossible to update key: a resource with \"" + newKey + "\" key already exists.");
+      }
+
+      // must SELECT first everything
+      ResourceDto project = mapper.selectProject(projectId);
+      String projectOldKey = project.getKey();
+      List<ResourceDto> resources = mapper.selectProjectResources(projectId);
+      resources.add(project);
+
+      // and then proceed with the batch UPDATE at once
+      runBatchUpdateForAllResources(resources, projectOldKey, newKey, mapper);
+
+      session.commit();
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  public void bulkUpdateKey(long projectId, String oldPrefix, String newPrefix) {
+    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);
+      for (ResourceDto module : modules) {
+        allResources.addAll(mapper.selectProjectResources(module.getId()));
+      }
+
+      // and then proceed with the batch UPDATE at once
+      runBatchUpdateForAllResources(allResources, oldPrefix, newPrefix, mapper);
+
+      session.commit();
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  private String computeNewKey(ResourceDto resource, String oldPrefix, String newPrefix) {
+    String resourceKey = resource.getKey();
+    return newPrefix + resourceKey.substring(oldPrefix.length(), resourceKey.length());
+  }
+
+  private void runBatchUpdateForAllResources(Collection<ResourceDto> resources, String oldPrefix, String newPrefix, ResourceKeyUpdaterMapper mapper) {
+    for (ResourceDto resource : resources) {
+      resource.setKey(computeNewKey(resource, oldPrefix, newPrefix));
+      mapper.update(resource);
+    }
+  }
+
+  private Set<ResourceDto> collectAllModules(long projectId, String oldPrefix, ResourceKeyUpdaterMapper mapper) {
+    ResourceDto project = mapper.selectProject(projectId);
+    Set<ResourceDto> modules = Sets.newHashSet();
+    if (project.getKey().startsWith(oldPrefix)) {
+      modules.add(project);
+    }
+    for (ResourceDto submodule : mapper.selectDescendantProjects(projectId)) {
+      modules.addAll(collectAllModules(submodule.getId(), oldPrefix, mapper));
+    }
+    return modules;
+  }
+
+  private void checkNewNameOfAllModules(Set<ResourceDto> modules, String oldPrefix, String newPrefix, ResourceKeyUpdaterMapper mapper) {
+    for (ResourceDto module : modules) {
+      String newName = computeNewKey(module, oldPrefix, newPrefix);
+      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/main/java/org/sonar/core/resource/ResourceKeyUpdaterMapper.java b/sonar-core/src/main/java/org/sonar/core/resource/ResourceKeyUpdaterMapper.java
new file mode 100644 (file)
index 0000000..a6ccff9
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sonar, open source software quality management 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
+ */
+package org.sonar.core.resource;
+
+import java.util.List;
+
+/**
+ * @since 3.2
+ */
+public interface ResourceKeyUpdaterMapper {
+
+  int countResourceByKey(String key);
+
+  ResourceDto selectProject(long projectId);
+
+  List<ResourceDto> selectProjectResources(long projectId);
+
+  List<ResourceDto> selectDescendantProjects(long projectId);
+
+  void update(ResourceDto resource);
+
+}
diff --git a/sonar-core/src/main/resources/org/sonar/core/resource/ResourceKeyUpdaterMapper.xml b/sonar-core/src/main/resources/org/sonar/core/resource/ResourceKeyUpdaterMapper.xml
new file mode 100644 (file)
index 0000000..2fc0a3f
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.core.resource.ResourceKeyUpdaterMapper">
+
+  <resultMap id="resourceResultMap" type="Resource">
+    <id property="id" column="id"/>
+    <result property="key" column="kee"/>
+    <result property="rootId" column="root_id"/>
+    <result property="scope" column="scope"/>
+  </resultMap>
+
+  <select id="countResourceByKey" parameterType="String" resultType="int">
+    SELECT count(*)
+    FROM projects
+    WHERE kee = #{key}
+  </select>
+
+  <select id="selectProject" parameterType="long" resultMap="resourceResultMap">
+    select * from projects where id=#{id}
+  </select>
+
+  <select id="selectProjectResources" parameterType="long" resultMap="resourceResultMap">
+    select * from projects where root_id=#{id} AND scope!='PRJ'
+  </select>
+
+  <select id="selectDescendantProjects" parameterType="long" resultMap="resourceResultMap">
+    select * from projects where scope='PRJ' and root_id=#{id}
+  </select>
+  
+  <update id="update" parameterType="Resource">
+    update projects 
+    set kee = #{key, jdbcType=VARCHAR}
+    where id = #{id}
+  </update>
+  
+</mapper>
+
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
new file mode 100644 (file)
index 0000000..bfa64d8
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Sonar, open source software quality management 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
+ */
+package org.sonar.core.resource;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.core.persistence.DaoTestCase;
+
+public class ResourceKeyUpdaterDaoTest extends DaoTestCase {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  private ResourceKeyUpdaterDao dao;
+
+  @Before
+  public void createDao() {
+    dao = new ResourceKeyUpdaterDao(getMyBatis());
+  }
+
+  @Test
+  public void shouldUpdateKey() {
+    setupData("shared");
+
+    dao.updateKey(2, "struts:core");
+
+    checkTables("shouldUpdateKey", "projects");
+  }
+
+  @Test
+  public void shouldNotUpdateKey() {
+    setupData("shared");
+
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Impossible to update key: a resource with \"org.struts:struts-ui\" key already exists.");
+
+    dao.updateKey(2, "org.struts:struts-ui");
+  }
+
+  @Test
+  public void shouldBulkUpdateKey() {
+    setupData("shared");
+
+    dao.bulkUpdateKey(1, "org.struts", "org.apache.struts");
+
+    checkTables("shouldBulkUpdateKey", "projects");
+  }
+
+  @Test
+  public void shouldFailBulkUpdateKeyIfKeyAlreadyExist() {
+    setupData("shared");
+
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Impossible to update key: a resource with \"foo:struts-core\" key already exists.");
+
+    dao.bulkUpdateKey(1, "org.struts", "foo");
+  }
+
+  @Test
+  public void shouldNotUpdateAllSubmodules() throws Exception {
+    setupData("shouldNotUpdateAllSubmodules");
+
+    dao.bulkUpdateKey(1, "org.struts", "org.apache.struts");
+
+    checkTables("shouldNotUpdateAllSubmodules", "projects");
+  }
+
+}
diff --git a/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shared.xml b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shared.xml
new file mode 100644 (file)
index 0000000..e81e23c
--- /dev/null
@@ -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-ui" 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-ui: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-ui: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-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKey-result.xml b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldBulkUpdateKey-result.xml
new file mode 100644 (file)
index 0000000..8c2ef06
--- /dev/null
@@ -0,0 +1,50 @@
+<dataset>
+
+  <!-- root project -->
+  <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.apache.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.apache.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.apache.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.apache.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.apache.struts:struts-ui" 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.apache.struts:struts-ui: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.apache.struts:struts-ui: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-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules-result.xml b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules-result.xml
new file mode 100644 (file)
index 0000000..b8f9d1d
--- /dev/null
@@ -0,0 +1,44 @@
+<dataset>
+
+  <!-- root project -->
+  <projects id="1" root_id="[null]" scope="PRJ" qualifier="TRK" kee="org.apache.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.apache.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.apache.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.apache.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 THAT HAS A DIFFERENT GROUP ID => MUST NOT BE UPDATED **************** -->
+  <projects id="5" root_id="1" kee="foo:struts-ui" 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="foo:struts-ui: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="foo:struts-ui:org.struts.RequestContext"
+            name="RequestContext" root_id="5"
+            description="[null]" profile_id="1"
+            enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+</dataset>
diff --git a/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules.xml b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldNotUpdateAllSubmodules.xml
new file mode 100644 (file)
index 0000000..1b33265
--- /dev/null
@@ -0,0 +1,44 @@
+<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 THAT HAS A DIFFERENT GROUP ID => MUST NOT BE UPDATED **************** -->
+  <projects id="5" root_id="1" kee="foo:struts-ui" 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="foo:struts-ui: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="foo:struts-ui:org.struts.RequestContext"
+            name="RequestContext" root_id="5"
+            description="[null]" profile_id="1"
+            enabled="[true]" language="java" copy_resource_id="[null]" person_id="[null]"/>
+
+</dataset>
diff --git a/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldUpdateKey-result.xml b/sonar-core/src/test/resources/org/sonar/core/resource/ResourceKeyUpdaterDaoTest/shouldUpdateKey-result.xml
new file mode 100644 (file)
index 0000000..229d8f4
--- /dev/null
@@ -0,0 +1,52 @@
+<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 **************** -->
+  <!-- ONLY THIS PROJECT MUST HAVE BEEN UPDATED            -->
+  <!--                                                     -->
+  <projects id="2" root_id="1" kee="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="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="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-ui" 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-ui: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-ui: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>
index f254097120a05beff09c5061b20e367347717764..4c126d653ca40dbd950b8c083e6f0cfd2f3ef1f6 100644 (file)
@@ -37,7 +37,11 @@ import org.sonar.api.resources.ResourceTypes;
 import org.sonar.api.rules.RulePriority;
 import org.sonar.api.rules.RuleRepository;
 import org.sonar.api.utils.ValidationMessages;
-import org.sonar.api.web.*;
+import org.sonar.api.web.Footer;
+import org.sonar.api.web.NavigationSection;
+import org.sonar.api.web.Page;
+import org.sonar.api.web.RubyRailsWebservice;
+import org.sonar.api.web.Widget;
 import org.sonar.api.workflow.Review;
 import org.sonar.api.workflow.internal.DefaultReview;
 import org.sonar.api.workflow.internal.DefaultWorkflowContext;
@@ -47,6 +51,7 @@ import org.sonar.core.persistence.Database;
 import org.sonar.core.persistence.DatabaseMigrator;
 import org.sonar.core.purge.PurgeDao;
 import org.sonar.core.resource.ResourceIndexerDao;
+import org.sonar.core.resource.ResourceKeyUpdaterDao;
 import org.sonar.core.workflow.WorkflowEngine;
 import org.sonar.markdown.Markdown;
 import org.sonar.server.configuration.Backup;
@@ -58,12 +63,17 @@ import org.sonar.server.notifications.reviews.ReviewsNotificationManager;
 import org.sonar.server.platform.GlobalSettingsUpdater;
 import org.sonar.server.platform.Platform;
 import org.sonar.server.platform.ServerIdGenerator;
-import org.sonar.server.plugins.*;
+import org.sonar.server.plugins.DefaultServerPluginRepository;
+import org.sonar.server.plugins.PluginDeployer;
+import org.sonar.server.plugins.PluginDownloader;
+import org.sonar.server.plugins.UpdateCenterMatrix;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
 import org.sonar.server.rules.ProfilesConsole;
 import org.sonar.server.rules.RulesConsole;
 import org.sonar.updatecenter.common.Version;
 
 import javax.annotation.Nullable;
+
 import java.net.InetAddress;
 import java.sql.Connection;
 import java.util.Collection;
@@ -494,4 +504,15 @@ public final class JRubyFacade {
       throw e;
     }
   }
+
+  // UPDATE PROJECT KEY ------------------------------------------------------------------
+
+  public void updateResourceKey(long projectId, String newKey) {
+    getContainer().getComponentByType(ResourceKeyUpdaterDao.class).updateKey(projectId, newKey);
+  }
+
+  public void bulkUpdateKey(long projectId, String oldPrefix, String newPrefix) {
+    getContainer().getComponentByType(ResourceKeyUpdaterDao.class).bulkUpdateKey(projectId, oldPrefix, newPrefix);
+  }
+
 }
index 69d70119bf6fde8f706d56694fa37bfaf70475eb..dbe65bc7a5f74692f46be39bf38c497359973be8 100644 (file)
@@ -18,7 +18,8 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
 #
 class ProjectController < ApplicationController
-  verify :method => :post, :only => [:set_links, :set_exclusions, :delete_exclusions], :redirect_to => {:action => :index}
+  verify :method => :post, :only => [:set_links, :set_exclusions, :delete_exclusions, :update_key, :perform_key_bulk_update], 
+         :redirect_to => {:action => :index}
   verify :method => :delete, :only => [:delete], :redirect_to => {:action => :index}
 
   SECTION=Navigation::SECTION_RESOURCE
@@ -28,9 +29,7 @@ class ProjectController < ApplicationController
   end
 
   def deletion
-    @project=Project.by_key(params[:id])
-    not_found("Project not found") unless @project
-    access_denied unless is_admin?(@project)
+    @project = get_current_project(params[:id])
 
     unless java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'deletable')
       redirect_to :action => 'index', :id => params[:id]
@@ -48,11 +47,84 @@ class ProjectController < ApplicationController
     end
     redirect_to_default
   end
+  
+  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
+    project = get_current_project(params[:id])
+    
+    new_key = params[:new_key].strip
+    if new_key.blank?
+      flash[:error] = message('update_key.new_key_cant_be_blank_for_x', :params => project.key)
+    elsif new_key == project.key
+      flash[:warning] = message('update_key.same_key_for_x', :params => project.key)
+    elsif Project.by_key(new_key)
+      flash[:error] = message('update_key.cant_update_x_because_resource_already_exist_with_key_x', :params => [project.key, new_key])
+    else
+      begin
+        java_facade.updateResourceKey(project.id, new_key)
+        flash[:notice] = message('update_key.key_updated')
+      rescue Exception => e
+        flash[:error] = message('update_key.error_occured_while_renaming_key_of_x', 
+                                :params => [project.key, Api::Utils.exception_message(e, :backtrace => false)])
+      end
+    end
+    
+    redirect_to :action => 'key', :id => project.root_project.id
+  end
+  
+  def prepare_key_bulk_update
+    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])
+    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])  
+      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
+    project = get_current_project(params[:id])
+    
+    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)
+      begin
+        java_facade.bulkUpdateKey(project.id, old_prefix, new_prefix)
+        flash[:notice] = message('update_key.key_updated')
+      rescue Exception => e
+        flash[:error] = message('update_key.error_occured_while_renaming_key_of_x', 
+                                :params => [project.key, Api::Utils.exception_message(e, :backtrace => false)])
+      end
+    end
+    
+    redirect_to :action => 'key', :id => project.id
+  end
 
   def history
-    @project=Project.by_key(params[:id])
-    not_found("Project not found") unless @project
-    access_denied unless is_admin?(@project)
+    @project = get_current_project(params[:id])
 
     # NOTE: we keep "@project.view? || @project.subview?" in the test for backward compatibility with the Views plugin
     unless java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'modifiable_history') || @project.view? || @project.subview?
@@ -65,9 +137,7 @@ class ProjectController < ApplicationController
   end
 
   def delete_snapshot_history
-    @project=Project.by_key(params[:id])
-    not_found("Project not found") unless @project
-    access_denied unless is_admin?(@project)
+    @project = get_current_project(params[:id])
 
     sid = params[:snapshot_id]
     if sid
@@ -79,9 +149,7 @@ class ProjectController < ApplicationController
   end
 
   def links
-    @project=Project.by_key(params[:id])
-    not_found("Project not found") unless @project
-    access_denied unless is_admin?(@project)
+    @project = get_current_project(params[:id])
 
     @snapshot=@project.last_snapshot
     if !@project.project?
@@ -90,9 +158,7 @@ class ProjectController < ApplicationController
   end
 
   def set_links
-    project = Project.by_key(params[:project_id])
-    not_found("Project not found") unless project
-    access_denied unless is_admin?(project)
+    project = get_current_project(params[:project_id])
 
     project.links.clear
 
@@ -118,9 +184,7 @@ class ProjectController < ApplicationController
 
 
   def settings
-    @project=Project.by_key(params[:id])
-    not_found("Project not found") unless @project
-    access_denied unless is_admin?(@project)
+    @project = get_current_project(params[:id])
 
     @snapshot=@project.last_snapshot
     if !@project.project? && !@project.module?
@@ -166,9 +230,7 @@ class ProjectController < ApplicationController
 
 
   def exclusions
-    @project=Project.by_key(params[:id])
-    not_found("Project not found") unless @project
-    access_denied unless is_admin?(@project)
+    @project = get_current_project(params[:id])
 
     @snapshot=@project.last_snapshot
     if !@project.project? && !@project.module?
@@ -177,9 +239,7 @@ class ProjectController < ApplicationController
   end
 
   def set_exclusions
-    @project = Project.find(params[:id])
-    not_found("Project not found") unless @project
-    access_denied unless is_admin?(@project)
+    @project = get_current_project(params[:id])
 
     patterns=params['patterns'].reject { |p| p.blank? }.uniq
     if patterns.empty?
@@ -193,9 +253,7 @@ class ProjectController < ApplicationController
   end
 
   def delete_exclusions
-    @project = Project.find(params[:id])
-    not_found("Project not found") unless @project
-    access_denied unless is_admin?(@project)
+    @project = get_current_project(params[:id])
 
     Property.clear('sonar.exclusions', @project.id)
     flash[:notice]='Filters deleted'
@@ -310,6 +368,13 @@ class ProjectController < ApplicationController
 
   protected
 
+  def get_current_project(project_id)
+    project=Project.by_key(project_id)
+    not_found("Project not found") unless project
+    access_denied unless is_admin?(project)
+    project
+  end
+  
   def find_project_snapshots(root_snapshot_id)
     snapshots = Snapshot.find(:all, :include => 'events', :conditions => ["(root_snapshot_id = ? OR id = ?) AND scope = 'PRJ'", root_snapshot_id, root_snapshot_id])
   end
index d482c8670aadd95a5b6480f6550116cb68d1d8a9..07e3a25128235125a601fca8e9cbafc0ec06300a 100644 (file)
@@ -62,6 +62,13 @@ class Project < ActiveRecord::Base
       end
   end
 
+  def modules
+    @modules ||=
+      begin
+        Project.find(:all, :conditions => {:root_id => self.id, :scope => 'PRJ'})
+      end
+  end
+
   # bottom-up array of projects,
   def ancestor_projects
     node, nodes = self, []
index 3039a5a157010a1403be189b44f04be68ddf85da..67cb894ef68f1f9263c9148c6218fdf3af9cddae 100644 (file)
                 <li class="<%= 'selected' if request.request_uri.include?('/project/history') -%>">
                   <a href="<%= ApplicationController.root_context -%>/project/history/<%= @project.id -%>"><%= message('project_history.page') -%></a></li>
               <% end %>
+              <% if controller.java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'updatable_key') %>
+                <li class="<%= 'selected' if request.request_uri.include?('/project/key') -%>">
+                  <a href="<%= ApplicationController.root_context -%>/project/key/<%= @project.id -%>"><%= message('update_key.page') -%></a></li>
+              <% end %>
               <% if controller.java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'deletable') %>
                 <li class="<%= 'selected' if request.request_uri.include?('/project/deletion') -%>">
                   <a href="<%= ApplicationController.root_context -%>/project/deletion/<%= @project.id -%>"><%= message('resource_deletion.page', :params => message('qualifier.' + @project.qualifier)) -%></a></li>
diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/project/_key_modules.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/project/_key_modules.html.erb
new file mode 100644 (file)
index 0000000..5906511
--- /dev/null
@@ -0,0 +1,19 @@
+    <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">
+        <% form_tag( {:action => 'update_key', :id => current_module.id }) do -%>
+          <input type="text" value="<%= h(current_module.key) -%>" name="new_key" id="key_<%= id_prefix -%>" size="40">
+          <%= submit_tag message('update_key.rename'), :id => 'update_key_' + id_prefix, :class => 'action',
+                         :onclick => "update_launched();$('loading_#{id_prefix}').show();",
+                         :confirm => message('update_key.are_you_sure_to_rename_x', :params => current_module.key) %>
+          <a href="#" onclick="$('key_<%= id_prefix -%>').value='<%= h(current_module.key) -%>';"><%= message('update_key.reset') -%></a>
+          <span class="loading" id="loading_<%= id_prefix -%>" style="display: none; padding: 3px 10px;"></span>
+        <% end %>
+      </td>
+    </tr>
+    <% current_module.modules.each_with_index do |sub_module, index| %>
+      <%= render :partial => 'key_modules', :locals => {:current_module => sub_module, :module_depth => module_depth+1, 
+                                                        :id_prefix => id_prefix + (index+1).to_s} -%>
+    <% 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
new file mode 100644 (file)
index 0000000..e827469
--- /dev/null
@@ -0,0 +1,92 @@
+<% 
+  if controller.java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'updatable_key')
+    has_modules = !@project.modules.empty?
+%>
+
+  <script type="text/javascript">
+    function update_launched() {
+      $$('input.action').each(function(input) {
+        input.disabled=true;
+      });
+    }
+  </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/>
+  <p>
+    <%= message('update_key.bulk_change_description') -%>
+    <br/><br/>
+    <%= message('update_key.current_key_for_project_x_is_x', :params => [@project.name, @project.key]) -%>
+  </p>
+  <br/>
+  <% form_tag( {:action => 'prepare_key_bulk_update', :id => @project.id }) do -%>
+    <table>
+      <tr>
+        <td style="padding-right: 20px">Replace:</td>
+        <td><input type="text" value="" name="old_prefix" id="old_prefix" size="40"></td>
+      </tr>
+      <tr>
+        <td style="padding-right: 20px">By:</td>
+        <td><input type="text" value="" name="new_prefix" id="new_prefix" size="40"></td>
+      </tr>
+      <tr>
+        <td></td>
+        <td style="padding-top: 5px">
+          <%= submit_tag message('update_key.rename'), :id => 'bulk_update_button', :class => 'action',
+                         :onclick => "update_launched();$('loading_bulk_update').show();" %>
+          <span class="loading" id="loading_bulk_update" style="display: none; padding: 3px 10px;"></span>
+        </td>
+      </tr>      
+    </table>
+  <% end %>
+  <br/>
+  <br/>
+<% end %>
+
+  <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%">
+    <thead>
+      <tr>
+        <th><%= message('update_key.old_key') -%></th>
+        <th><%= message('update_key.new_key') -%></th>
+      </tr>
+    </thead>
+    <tbody>
+      <%= render :partial => 'key_modules', :locals => {:current_module => @project, :module_depth => 0, :id_prefix => "0"} -%>
+    </tbody>
+  </table>
+  
+<% 
+    end
+  end 
+%>
\ No newline at end of file
index 8e2f68f187035230c87dda77c8725b3ad5ed75ca..bf155ec2999da5d1a5792488def1204ac0719a6d 100644 (file)
@@ -1,9 +1,5 @@
 <div name="settings">
-
-  <h1 class="marginbottom10">Settings</h1>
-  
   <div class="yui-g widget" id="widget_plugins">
     <%= render :partial => 'settings/settings', :locals => {:project=>@project} %>
   </div>
-
 </div>
\ No newline at end of file