]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7919 Rewrite "Update Key" project page (#1140)
authorStas Vilchik <vilchiks@gmail.com>
Wed, 10 Aug 2016 16:16:54 +0000 (18:16 +0200)
committerGitHub <noreply@github.com>
Wed, 10 Aug 2016 16:16:54 +0000 (18:16 +0200)
32 files changed:
it/it-tests/src/test/java/it/projectAdministration/ProjectAdministrationTest.java
it/it-tests/src/test/java/it/projectAdministration/ProjectKeyPageTest.java [new file with mode: 0644]
it/it-tests/src/test/java/pageobjects/Navigation.java
it/it-tests/src/test/java/pageobjects/ProjectKeyPage.java [new file with mode: 0644]
it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-impossible-because-duplicate-keys.html [deleted file]
it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-impossible-because-no-match.html [deleted file]
it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-success.html [deleted file]
it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/fine-grained-update-impossible.html [deleted file]
it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/fine-grained-update-success.html [deleted file]
server/sonar-web/src/main/js/api/components.js
server/sonar-web/src/main/js/apps/project-admin/app.js
server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdate.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateForm.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateResults.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/key/FineGrainedUpdate.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/key/Header.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/key/Key.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.hbs [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/store/actions.js
server/sonar-web/src/main/js/apps/project-admin/store/components.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/store/modulesByProject.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js
server/sonar-web/src/main/less/style.less
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/project_controller.rb
server/sonar-web/src/main/webapp/WEB-INF/app/views/project/_key_modules.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/project/_prepare_keys.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/project/key.html.erb
server/sonar-web/src/main/webapp/WEB-INF/app/views/project/prepare_key_bulk_update.html.erb [deleted file]
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 001d25538c03ca9ac29686ee77c87a9105ba76eb..f9863aa85b4b6849dd400889afcd81d7b6376ff5 100644 (file)
@@ -179,39 +179,6 @@ public class ProjectAdministrationTest {
     assertThat(orchestrator.getServer().getAdminWsClient().findAll(PropertyQuery.createForResource(null, "sample"))).isNotEmpty();
   }
 
-  /**
-   * SONAR-1608
-   */
-  @Test
-  public void bulk_update_project_keys() {
-    SonarScanner build = SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"));
-    orchestrator.executeBuild(build);
-
-    Selenese selenese = Selenese.builder()
-      .setHtmlTestsInClasspath("project-bulk-update-keys",
-        "/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-impossible-because-duplicate-keys.html",
-        "/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-impossible-because-no-match.html",
-        "/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-success.html")
-      .build();
-    new SeleneseTest(selenese).runOn(orchestrator);
-  }
-
-  /**
-   * SONAR-1608
-   */
-  @Test
-  public void fine_grain_update_project_keys() {
-    SonarScanner build = SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"));
-    orchestrator.executeBuild(build);
-
-    Selenese selenese = Selenese.builder()
-      .setHtmlTestsInClasspath("project-fine-grained-update-keys",
-        "/projectAdministration/ProjectAdministrationTest/project-update-keys/fine-grained-update-impossible.html",
-        "/projectAdministration/ProjectAdministrationTest/project-update-keys/fine-grained-update-success.html")
-      .build();
-    new SeleneseTest(selenese).runOn(orchestrator);
-  }
-
   /**
    * SONAR-4060
    */
diff --git a/it/it-tests/src/test/java/it/projectAdministration/ProjectKeyPageTest.java b/it/it-tests/src/test/java/it/projectAdministration/ProjectKeyPageTest.java
new file mode 100644 (file)
index 0000000..29ea28a
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package it.projectAdministration;
+
+import com.sonar.orchestrator.Orchestrator;
+import com.sonar.orchestrator.build.SonarScanner;
+import it.Category1Suite;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonarqube.ws.client.PostRequest;
+import org.sonarqube.ws.client.WsClient;
+import pageobjects.Navigation;
+import pageobjects.ProjectKeyPage;
+
+import static com.codeborne.selenide.Condition.visible;
+import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.WebDriverRunner.url;
+import static org.assertj.core.api.Assertions.assertThat;
+import static util.ItUtils.newAdminWsClient;
+import static util.ItUtils.projectDir;
+
+public class ProjectKeyPageTest {
+
+  @ClassRule
+  public static Orchestrator ORCHESTRATOR = Category1Suite.ORCHESTRATOR;
+
+  @Rule
+  public Navigation nav = Navigation.get(ORCHESTRATOR);
+
+  private static WsClient wsClient;
+
+  @BeforeClass
+  public static void setUp() {
+    wsClient = newAdminWsClient(ORCHESTRATOR);
+  }
+
+  @Before
+  public void cleanUp() {
+    ORCHESTRATOR.resetData();
+  }
+
+  @Test
+  public void change_key_when_no_modules() {
+    createProject("sample");
+
+    ProjectKeyPage page = openPage("sample");
+    page.assertSimpleUpdate().trySimpleUpdate("another");
+
+    assertThat(url()).endsWith("/project/key?id=another");
+  }
+
+  @Test
+  public void fail_to_change_key_when_no_modules() {
+    createProject("sample");
+    createProject("another");
+
+    ProjectKeyPage page = openPage("sample");
+    page.assertSimpleUpdate().trySimpleUpdate("another");
+
+    $(".alert.alert-danger").shouldBe(visible);
+    assertThat(url()).endsWith("/project/key?id=sample");
+  }
+
+  @Test
+  public void change_key_of_multi_modules_project() {
+    analyzeProject("shared/xoo-multi-modules-sample", "sample");
+
+    ProjectKeyPage page = openPage("sample");
+    page.openFineGrainedUpdate().tryFineGrainedUpdate("sample", "another");
+
+    assertThat(url()).endsWith("/project/key?id=another");
+  }
+
+  @Test
+  public void fail_to_change_key_of_multi_modules_project() {
+    analyzeProject("shared/xoo-multi-modules-sample", "sample");
+    createProject("another");
+
+    ProjectKeyPage page = openPage("sample");
+    page.openFineGrainedUpdate().tryFineGrainedUpdate("sample", "another");
+
+    $(".alert.alert-danger").shouldBe(visible);
+    assertThat(url()).endsWith("/project/key?id=sample");
+  }
+
+  @Test
+  public void change_key_of_module_of_multi_modules_project() {
+    analyzeProject("shared/xoo-multi-modules-sample", "sample");
+
+    ProjectKeyPage page = openPage("sample");
+    page.openFineGrainedUpdate().tryFineGrainedUpdate("sample:module_a:module_a1", "another");
+
+    $("#update-key-confirmation-form").shouldNotBe(visible);
+
+    nav.openProjectKey("another");
+    assertThat(url()).endsWith("/project/key?id=another");
+  }
+
+  @Test
+  public void fail_to_change_key_of_module_of_multi_modules_project() {
+    analyzeProject("shared/xoo-multi-modules-sample", "sample");
+    createProject("another");
+
+    ProjectKeyPage page = openPage("sample");
+    page.openFineGrainedUpdate().tryFineGrainedUpdate("sample:module_a:module_a1", "another");
+
+    $(".alert.alert-danger").shouldBe(visible);
+  }
+
+  @Test
+  public void bulk_change() {
+    analyzeProject("shared/xoo-multi-modules-sample", "sample");
+
+    ProjectKeyPage page = openPage("sample");
+    page.assertBulkChange().simulateBulkChange("sample", "another");
+
+    $("#bulk-update-results").shouldBe(visible);
+    page.assertBulkChangeSimulationResult("sample", "another")
+      .assertBulkChangeSimulationResult("sample:module_a:module_a1", "another:module_a:module_a1");
+
+    page.confirmBulkUpdate().assertSuccessfulBulkUpdate();
+  }
+
+  @Test
+  public void fail_to_bulk_change_because_no_changed_key() {
+    analyzeProject("shared/xoo-multi-modules-sample", "sample");
+
+    ProjectKeyPage page = openPage("sample");
+    page.assertBulkChange().simulateBulkChange("random", "another");
+
+    $("#bulk-update-nothing").shouldBe(visible);
+    $("#bulk-update-results").shouldNotBe(visible);
+  }
+
+  @Test
+  public void fail_to_bulk_change_because_of_duplications() {
+    analyzeProject("shared/xoo-multi-modules-sample", "sample");
+
+    ProjectKeyPage page = openPage("sample");
+    page.assertBulkChange().simulateBulkChange("module_a1", "module_a2");
+
+    $("#bulk-update-duplicate").shouldBe(visible);
+    $("#bulk-update-results").shouldBe(visible);
+
+    page.assertBulkChangeSimulationResult("sample:module_a:module_a1", "sample:module_a:module_a2")
+      .assertDuplicated("sample:module_a:module_a1");
+  }
+
+  private ProjectKeyPage openPage(String projectKey) {
+    nav.logIn().submitCredentials("admin", "admin");
+    return nav.openProjectKey(projectKey);
+  }
+
+  private static void createProject(String projectKey) {
+    wsClient.wsConnector().call(new PostRequest("api/projects/create")
+      .setParam("key", projectKey)
+      .setParam("name", projectKey));
+  }
+
+  private static void analyzeProject(String path, String projectKey) {
+    ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir(path))
+      .setProjectKey(projectKey));
+  }
+}
index d7d4a79461429fcbcde2fbb4eb0e4f93b27ba918..a4b3f9af62dd7d38d6c75aee8c71435f598bc092 100644 (file)
@@ -68,6 +68,12 @@ public class Navigation extends ExternalResource {
     return open(url, ProjectHistoryPage.class);
   }
 
+  public ProjectKeyPage openProjectKey(String projectKey) {
+    // TODO encode projectKey
+    String url = "/project/key?id=" + projectKey;
+    return open(url, ProjectKeyPage.class);
+  }
+
   public void open(String relativeUrl) {
     Selenide.open(relativeUrl);
   }
diff --git a/it/it-tests/src/test/java/pageobjects/ProjectKeyPage.java b/it/it-tests/src/test/java/pageobjects/ProjectKeyPage.java
new file mode 100644 (file)
index 0000000..43f85c1
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package pageobjects;
+
+import com.codeborne.selenide.SelenideElement;
+
+import static com.codeborne.selenide.Condition.exist;
+import static com.codeborne.selenide.Condition.hasText;
+import static com.codeborne.selenide.Condition.visible;
+import static com.codeborne.selenide.Selenide.$;
+
+public class ProjectKeyPage {
+
+  public ProjectKeyPage() {
+    $("#project-key").should(exist);
+  }
+
+  public ProjectKeyPage assertSimpleUpdate() {
+    $("#update-key-new-key").shouldBe(visible);
+    $("#update-key-submit").shouldBe(visible);
+    return this;
+  }
+
+  public ProjectKeyPage trySimpleUpdate(String newKey) {
+    $("#update-key-new-key").val(newKey);
+    $("#update-key-submit").click();
+    $("#update-key-confirm").click();
+    return this;
+  }
+
+  public ProjectKeyPage openFineGrainedUpdate() {
+    $("#update-key-tab-fine").click();
+    $("#project-key-fine-grained-update").shouldBe(visible);
+    return this;
+  }
+
+  public ProjectKeyPage tryFineGrainedUpdate(String key, String newKey) {
+    SelenideElement form = $(".js-fine-grained-update[data-key=\"" + key + "\"]");
+    form.shouldBe(visible);
+
+    form.$("input").val(newKey);
+    form.$("button").click();
+
+    $("#update-key-confirm").click();
+    return this;
+  }
+
+  public ProjectKeyPage assertBulkChange() {
+    $("#bulk-update-replace").shouldBe(visible);
+    $("#bulk-update-by").shouldBe(visible);
+    $("#bulk-update-see-results").shouldBe(visible);
+    return this;
+  }
+
+  public ProjectKeyPage simulateBulkChange(String replace, String by) {
+    $("#bulk-update-replace").val(replace);
+    $("#bulk-update-by").val(by);
+    $("#bulk-update-see-results").click();
+
+    $("#bulk-update-simulation").shouldBe(visible);
+    return this;
+  }
+
+  public ProjectKeyPage assertBulkChangeSimulationResult(String oldKey, String newKey) {
+    SelenideElement row = $("#bulk-update-results").$("[data-key=\"" + oldKey + "\"]");
+    row.$(".js-old-key").should(hasText(oldKey));
+    row.$(".js-new-key").should(hasText(newKey));
+    return this;
+  }
+
+  public ProjectKeyPage assertDuplicated(String oldKey) {
+    SelenideElement row = $("#bulk-update-results").$("[data-key=\"" + oldKey + "\"]");
+    row.$(".js-new-key").$(".badge-danger").shouldBe(visible);
+    return this;
+  }
+
+  public ProjectKeyPage confirmBulkUpdate() {
+    $("#bulk-update-confirm").click();
+    return this;
+  }
+
+  public ProjectKeyPage assertSuccessfulBulkUpdate() {
+    $("#project-key-bulk-update").$(".alert.alert-success").shouldBe(visible);
+    return this;
+  }
+}
diff --git a/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-impossible-because-duplicate-keys.html b/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-impossible-because-duplicate-keys.html
deleted file mode 100644 (file)
index dcffc75..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>bulk-update-impossible-because-duplicate-keys</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
-    <tbody>
-    <tr>
-       <td>open</td>
-       <td>/sessions/logout</td>
-       <td></td>
-</tr>
-<tr>
-       <td>open</td>
-       <td>/sessions/login</td>
-       <td></td>
-</tr>
-<tr>
-       <td>type</td>
-       <td>login</td>
-       <td>admin</td>
-</tr>
-<tr>
-       <td>type</td>
-       <td>password</td>
-       <td>admin</td>
-</tr>
-<tr>
-       <td>clickAndWait</td>
-       <td>commit</td>
-       <td></td>
-</tr>
-<tr>
-       <td>waitForElementPresent</td>
-       <td>css=.js-user-authenticated</td>
-       <td></td>
-</tr>
-<tr>
-       <td>open</td>
-       <td>/dashboard/index/com.sonarsource.it.samples:multi-modules-sample</td>
-       <td></td>
-</tr>
-<tr>
-    <td>click</td>
-    <td>css=#context-navigation .navbar-admin-link</td>
-    <td></td>
-</tr>
-<tr>
-       <td>clickAndWait</td>
-       <td>link=Update Key</td>
-       <td></td>
-</tr>
-<tr>
-       <td>type</td>
-       <td>id=string_to_replace</td>
-       <td>com.sonarsource.it.samples:multi-modules-sample:module_a </td>
-</tr>
-<tr>
-       <td>type</td>
-       <td>id=replacement_string</td>
-       <td>com.sonarsource.it.samples:multi-modules-sample:module_b </td>
-</tr>
-<tr>
-       <td>clickAndWait</td>
-       <td>id=bulk_update_button</td>
-       <td></td>
-</tr>
-<tr>
-       <td>waitForText</td>
-       <td>css=#content h1</td>
-       <td>*Bulk update can not be performed*</td>
-</tr>
-<tr>
-       <td>assertText</td>
-       <td>css=#content p</td>
-       <td>*The replacement of &quot;com.sonarsource.it.samples:multi-modules-sample:module_a&quot; by &quot;com.sonarsource.it.samples:multi-modules-sample:module_b&quot; is impossible as it would result in duplicate keys (in red below):*</td>
-</tr>
-<tr>
-       <td>assertText</td>
-       <td>css=#content .data</td>
-       <td>*Duplicate key*</td>
-</tr>
-<tr>
-       <td>assertElementNotPresent</td>
-       <td>id=bulk_update_button</td>
-       <td></td>
-</tr>
-<tr>
-       <td>clickAndWait</td>
-       <td>Link=Back</td>
-       <td></td>
-</tr>
-<tr>
-       <td>waitForText</td>
-       <td>content</td>
-       <td>*Update Key*com.sonarsource.it.samples:multi-modules-sample*</td>
-</tr>
-</tbody>
-</table>
-</body>
-</html>
diff --git a/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-impossible-because-no-match.html b/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-impossible-because-no-match.html
deleted file mode 100644 (file)
index f4e01d0..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>bulk-update-impossible-because-no-match</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
-    <tbody>
-    <tr>
-        <td>open</td>
-        <td>/sessions/logout</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>open</td>
-        <td>/sessions/login</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>type</td>
-        <td>login</td>
-        <td>admin</td>
-    </tr>
-    <tr>
-        <td>type</td>
-        <td>password</td>
-        <td>admin</td>
-    </tr>
-    <tr>
-        <td>clickAndWait</td>
-        <td>commit</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>waitForElementPresent</td>
-        <td>css=.js-user-authenticated</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>open</td>
-        <td>/dashboard/index/com.sonarsource.it.samples:multi-modules-sample</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>click</td>
-        <td>css=#context-navigation .navbar-admin-link</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>waitForElementPresent</td>
-        <td>link=Update Key</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>clickAndWait</td>
-        <td>link=Update Key</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>type</td>
-        <td>id=string_to_replace</td>
-        <td>foo</td>
-    </tr>
-    <tr>
-        <td>type</td>
-        <td>id=replacement_string</td>
-        <td>org.sonar</td>
-    </tr>
-    <tr>
-        <td>clickAndWait</td>
-        <td>id=bulk_update_button</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>waitForText</td>
-        <td>css=#content h1</td>
-        <td>*Bulk update can not be performed*</td>
-    </tr>
-    <tr>
-        <td>assertText</td>
-        <td>css=#content</td>
-        <td>*Bulk update can not be performed*No key contains the string to replace (&quot;foo&quot;).*</td>
-    </tr>
-    <tr>
-        <td>assertElementNotPresent</td>
-        <td>id=bulk_update_button</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>clickAndWait</td>
-        <td>Link=Back</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>waitForText</td>
-        <td>content</td>
-        <td>*Update Key*com.sonarsource.it.samples:multi-modules-sample*</td>
-    </tr>
-    </tbody>
-</table>
-</body>
-</html>
diff --git a/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-success.html b/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-success.html
deleted file mode 100644 (file)
index e756639..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>bulk-update-success</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
-    <tbody>
-    <tr>
-       <td>open</td>
-       <td>/sessions/logout</td>
-       <td></td>
-</tr>
-<tr>
-       <td>open</td>
-       <td>/sessions/login</td>
-       <td></td>
-</tr>
-<tr>
-       <td>type</td>
-       <td>login</td>
-       <td>admin</td>
-</tr>
-<tr>
-       <td>type</td>
-       <td>password</td>
-       <td>admin</td>
-</tr>
-<tr>
-       <td>clickAndWait</td>
-       <td>commit</td>
-       <td></td>
-</tr>
-<tr>
-       <td>waitForElementPresent</td>
-       <td>css=.js-user-authenticated</td>
-       <td></td>
-</tr>
-<tr>
-       <td>open</td>
-       <td>/dashboard/index/com.sonarsource.it.samples:multi-modules-sample</td>
-       <td></td>
-</tr>
-<tr>
-    <td>click</td>
-    <td>css=#context-navigation .navbar-admin-link</td>
-    <td></td>
-</tr>
-<tr>
-       <td>waitForElementPresent</td>
-       <td>link=Update Key</td>
-       <td></td>
-</tr>
-<tr>
-       <td>clickAndWait</td>
-       <td>link=Update Key</td>
-       <td></td>
-</tr>
-<tr>
-       <td>type</td>
-       <td>id=string_to_replace</td>
-       <td>com.sonarsource</td>
-</tr>
-<tr>
-       <td>type</td>
-       <td>id=replacement_string</td>
-       <td>org.sonar</td>
-</tr>
-<tr>
-       <td>clickAndWait</td>
-       <td>id=bulk_update_button</td>
-       <td></td>
-</tr>
-<tr>
-       <td>waitForText</td>
-       <td>content</td>
-       <td>*Do you really want to perform the bulk update on project keys?*</td>
-</tr>
-<tr>
-       <td>waitForText</td>
-       <td>content</td>
-       <td>*com.sonarsource.it.samples:multi-modules-sample*org.sonar.it.samples:multi-modules-sample*</td>
-</tr>
-<tr>
-       <td>waitForText</td>
-       <td>content</td>
-       <td>*com.sonarsource.it.samples:multi-modules-sample:module_a*org.sonar.it.samples:multi-modules-sample:module_a*</td>
-</tr>
-<tr>
-       <td>clickAndWait</td>
-       <td>id=bulk_update_button</td>
-       <td></td>
-</tr>
-<tr>
-       <td>waitForText</td>
-       <td>content</td>
-       <td>*The key has successfully been updated for all required resources.*</td>
-</tr>
-<tr>
-       <td>waitForText</td>
-       <td>content</td>
-       <td>*org.sonar.it.samples:multi-modules-sample*</td>
-</tr>
-<tr>
-       <td>assertTextNotPresent</td>
-       <td>content</td>
-       <td>*com.sonarsource.it.samples:multi-modules-sample*</td>
-</tr>
-</tbody>
-</table>
-</body>
-</html>
diff --git a/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/fine-grained-update-impossible.html b/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/fine-grained-update-impossible.html
deleted file mode 100644 (file)
index d074667..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>fine-grained-update-impossible</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
-    <tbody>
-    <tr>
-        <td>open</td>
-        <td>/sessions/logout</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>open</td>
-        <td>/sessions/login</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>type</td>
-        <td>login</td>
-        <td>admin</td>
-    </tr>
-    <tr>
-        <td>type</td>
-        <td>password</td>
-        <td>admin</td>
-    </tr>
-    <tr>
-        <td>clickAndWait</td>
-        <td>commit</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>waitForElementPresent</td>
-        <td>css=.js-user-authenticated</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>open</td>
-        <td>/dashboard/index/com.sonarsource.it.samples:multi-modules-sample</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>click</td>
-        <td>css=#context-navigation .navbar-admin-link</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>waitForElementPresent</td>
-        <td>link=Update Key</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>clickAndWait</td>
-        <td>link=Update Key</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>type</td>
-        <td>id=key_05</td>
-        <td>com.sonarsource.it.samples:multi-modules-sample:module_b</td>
-    </tr>
-    <tr>
-        <td>clickAndWait</td>
-        <td>id=update_key_05</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>assertConfirmation</td>
-        <td>*Are you sure you want to rename &quot;com.sonarsource.it.samples:multi-modules-sample:module_*&quot;, as well as all its modules and resources?*</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>waitForText</td>
-        <td>id=error</td>
-        <td>*&quot;com.sonarsource.it.samples:multi-modules-sample:module_*&quot; can not be renamed because &quot;com.sonarsource.it.samples:multi-modules-sample:module_b&quot; is the key of an existing resource. The update has been canceled.*</td>
-    </tr>
-    </tbody>
-</table>
-</body>
-</html>
diff --git a/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/fine-grained-update-success.html b/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/fine-grained-update-success.html
deleted file mode 100644 (file)
index 4f2d9d5..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>fine-grained-update-success</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
-    <tbody>
-    <tr>
-        <td>open</td>
-        <td>/sessions/logout</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>open</td>
-        <td>/sessions/login</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>type</td>
-        <td>login</td>
-        <td>admin</td>
-    </tr>
-    <tr>
-        <td>type</td>
-        <td>password</td>
-        <td>admin</td>
-    </tr>
-    <tr>
-        <td>clickAndWait</td>
-        <td>commit</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>waitForElementPresent</td>
-        <td>css=.js-user-authenticated</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>open</td>
-        <td>/dashboard/index/com.sonarsource.it.samples:multi-modules-sample</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>click</td>
-        <td>css=#context-navigation .navbar-admin-link</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>waitForElementPresent</td>
-        <td>link=Update Key</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>clickAndWait</td>
-        <td>link=Update Key</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>type</td>
-        <td>id=key_02</td>
-        <td>com.sonarsource.it.samples:module_c1</td>
-    </tr>
-    <tr>
-        <td>clickAndWait</td>
-        <td>id=update_key_02</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>assertConfirmation</td>
-        <td>*Are you sure you want to rename "com.sonarsource.it.samples:multi-modules-sample:module_*", as well as all its modules and resources?*</td>
-        <td></td>
-    </tr>
-    <tr>
-        <td>waitForText</td>
-        <td>content</td>
-        <td>*The key has successfully been updated for all required resources.*</td>
-    </tr>
-    <tr>
-        <td>waitForText</td>
-        <td>content</td>
-        <td>*com.sonarsource.it.samples:module_c1*</td>
-    </tr>
-    </tbody>
-</table>
-</body>
-</html>
index f0f49a636b4d7b713e48bc9ebaba6fa870a94e31..6b3238022fb4f33b61174cdc53f47ebfb2290bec 100644 (file)
@@ -109,3 +109,29 @@ export function getMyProjects (data) {
   const url = '/api/projects/search_my_projects';
   return getJSON(url, data);
 }
+
+/**
+ * Change component's key
+ * @param {string} key
+ * @param {string} newKey
+ * @returns {Promise}
+ */
+export function changeKey (key, newKey) {
+  const url = '/api/components/update_key';
+  const data = { key, newKey };
+  return post(url, data);
+}
+
+/**
+ * Bulk change component's key
+ * @param {string} key
+ * @param {string} from
+ * @param {string} to
+ * @param {boolean} dryRun
+ * @returns {Promise}
+ */
+export function bulkChangeKey (key, from, to, dryRun = false) {
+  const url = '/api/components/bulk_update_key';
+  const data = { key, from, to, dryRun };
+  return postJSON(url, data);
+}
index 7981c936e87530d8ba08211c3140cc2566b7425a..903ed046cb9dd4f06ed4f3282d709d4a5abfe25f 100644 (file)
@@ -26,6 +26,7 @@ import Deletion from './deletion/Deletion';
 import QualityProfiles from './quality-profiles/QualityProfiles';
 import QualityGate from './quality-gate/QualityGate';
 import Links from './links/Links';
+import Key from './key/Key';
 import rootReducer from './store/rootReducer';
 import configureStore from '../../components/store/configureStore';
 
@@ -56,6 +57,9 @@ window.sonarqube.appStarted.then(options => {
           <Route
               path="/links"
               component={withComponent(Links)}/>
+          <Route
+              path="/key"
+              component={withComponent(Key)}/>
         </Router>
       </Provider>
   ), el);
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdate.js b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdate.js
new file mode 100644 (file)
index 0000000..1624fad
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import BulkUpdateForm from './BulkUpdateForm';
+import BulkUpdateResults from './BulkUpdateResults';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { bulkChangeKey } from '../../../api/components';
+import { getComponentUrl } from '../../../helpers/urls';
+
+export default class BulkUpdate extends React.Component {
+  static propTypes = {
+    component: React.PropTypes.object.isRequired
+  };
+
+  state = {
+    updating: false,
+    updated: false,
+    newComponentKey: null
+  };
+
+  handleSubmit (replace, by) {
+    this.loadResults(replace, by);
+  }
+
+  handleConfirm () {
+    this.setState({ updating: true });
+    
+    const { component } = this.props;
+    const { replace, by } = this.state;
+    bulkChangeKey(component.key, replace, by).then(r => {
+      const result = r.keys.find(result => result.key === component.key);
+      const newComponentKey = result != null ? result.newKey : component.key;
+      this.setState({
+        updating: false,
+        updated: true,
+        newComponentKey
+      });
+    });
+  }
+
+  loadResults (replace, by) {
+    const { component } = this.props;
+    bulkChangeKey(component.key, replace, by, true).then(r => {
+      this.setState({ results: r.keys, replace, by });
+    });
+  }
+
+  renderUpdating () {
+    return (
+        <div id="project-key-bulk-update">
+          <i className="spinner"/>
+        </div>
+    );
+  }
+
+  renderUpdated () {
+    return (
+        <div id="project-key-bulk-update">
+          <div className="alert alert-success">
+            {translate('update_key.key_updated')}
+            {' '}
+            <a href={getComponentUrl(this.state.newComponentKey)}>
+              {translate('check_project')}
+            </a>
+          </div>
+        </div>
+    );
+  }
+
+  render () {
+    const { component } = this.props;
+    const { updating, updated } = this.state;
+    const { results, replace, by } = this.state;
+
+    if (updating) {
+      return this.renderUpdating();
+    }
+
+    if (updated) {
+      return this.renderUpdated();
+    }
+
+    return (
+        <div id="project-key-bulk-update">
+          <header className="big-spacer-bottom">
+            <div className="spacer-bottom">
+              {translate('update_key.bulk_change_description')}
+            </div>
+            <div>
+              {translateWithParameters(
+                  'update_key.current_key_for_project_x_is_x',
+                  component.name,
+                  component.key
+              )}
+            </div>
+          </header>
+
+          <BulkUpdateForm onSubmit={this.handleSubmit.bind(this)}/>
+
+          {results != null && (
+              <BulkUpdateResults
+                  results={results}
+                  replace={replace}
+                  by={by}
+                  onConfirm={this.handleConfirm.bind(this)}/>
+          )}
+        </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateForm.js b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateForm.js
new file mode 100644 (file)
index 0000000..db31d98
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import { translate } from '../../../helpers/l10n';
+
+export default class BulkUpdateForm extends React.Component {
+  static propTypes = {
+    onSubmit: React.PropTypes.func.isRequired
+  };
+
+  handleSubmit (e) {
+    e.preventDefault();
+    this.refs.submit.blur();
+
+    const replace = this.refs.replace.value;
+    const by = this.refs.by.value;
+
+    this.props.onSubmit(replace, by);
+  }
+
+  render () {
+    return (
+        <form onSubmit={this.handleSubmit.bind(this)}>
+          <div className="modal-field">
+            <label htmlFor="bulk-update-replace">
+              {translate('update_key.replace')}
+            </label>
+            <input
+                ref="replace"
+                id="bulk-update-replace"
+                name="replace"
+                type="text"
+                placeholder={translate('update_key.replace_example')}
+                required/>
+          </div>
+
+          <div className="modal-field">
+            <label htmlFor="bulk-update-by">
+              {translate('update_key.by')}
+            </label>
+            <input
+                ref="by"
+                id="bulk-update-by"
+                name="by"
+                type="text"
+                placeholder={translate('update_key.by_example')}
+                required/>
+            <button
+                ref="submit"
+                id="bulk-update-see-results"
+                className="big-spacer-left">
+              {translate('update_key.see_results')}
+            </button>
+          </div>
+        </form>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateResults.js b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateResults.js
new file mode 100644 (file)
index 0000000..56f5531
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import some from 'lodash/some';
+import { translateWithParameters, translate } from '../../../helpers/l10n';
+
+export default class BulkUpdateResults extends React.Component {
+  static propTypes = {
+    results: React.PropTypes.array.isRequired,
+    onConfirm: React.PropTypes.func.isRequired
+  };
+
+  handleConfirm (e) {
+    e.preventDefault();
+    e.target.blur();
+    this.props.onConfirm();
+  }
+
+  render () {
+    const { results, replace, by } = this.props;
+    const isEmpty = results.length === 0;
+    const hasDuplications = some(results, r => r.duplicate);
+    const canUpdate = !isEmpty && !hasDuplications;
+
+    return (
+        <div id="bulk-update-simulation" className="big-spacer-top">
+          {isEmpty && (
+              <div id="bulk-update-nothing" className="alert alert-warning">
+                {translateWithParameters(
+                    'update_key.no_key_to_update',
+                    replace
+                )}
+              </div>
+          )}
+
+          {hasDuplications && (
+              <div id="bulk-update-duplicate" className="alert alert-danger">
+                {translateWithParameters(
+                    'update_key.cant_update_because_duplicate_keys',
+                    replace,
+                    by
+                )}
+              </div>
+          )}
+
+          {canUpdate && (
+              <div className="spacer-bottom">
+                {translate('update_key.keys_will_be_updated_as_follows')}
+              </div>
+          )}
+
+          {!isEmpty && (
+              <table
+                  id="bulk-update-results"
+                  className="data zebra zebra-hover">
+                <thead>
+                  <tr>
+                    <th>{translate('update_key.old_key')}</th>
+                    <th>{translate('update_key.new_key')}</th>
+                  </tr>
+                </thead>
+                <tbody>
+                  {results.map(result => (
+                      <tr key={result.key} data-key={result.key}>
+                        <td className="js-old-key">
+                          {result.key}
+                        </td>
+                        <td className="js-new-key">
+                          {result.duplicate && (
+                              <span className="spacer-right badge badge-danger">
+                                {translate('update_key.duplicate_key')}
+                              </span>
+                          )}
+                          {result.newKey}
+                        </td>
+                      </tr>
+                  ))}
+                </tbody>
+              </table>
+          )}
+
+          <div className="big-spacer-top">
+            {canUpdate && (
+                <button
+                    id="bulk-update-confirm"
+                    onClick={this.handleConfirm.bind(this)}>
+                  {translate('update_verb')}
+                </button>
+            )}
+          </div>
+        </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/FineGrainedUpdate.js b/server/sonar-web/src/main/js/apps/project-admin/key/FineGrainedUpdate.js
new file mode 100644 (file)
index 0000000..6f7deac
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import UpdateKeyForm from './UpdateKeyForm';
+import QualifierIcon from '../../../components/shared/qualifier-icon';
+import { translate } from '../../../helpers/l10n';
+
+export default class FineGrainedUpdate extends React.Component {
+  render () {
+    const { component, modules } = this.props;
+    const components = [component, ...modules];
+
+    return (
+        <div id="project-key-fine-grained-update">
+          <table className="data zebra">
+            <tbody>
+              {components.map(component => (
+                  <tr key={component.key}>
+                    <td className="width-40">
+                      <QualifierIcon qualifier={component.qualifier}/>
+                      {' '}
+                      {component.name}
+                    </td>
+                    <td>
+                      <UpdateKeyForm
+                          component={component}
+                          onKeyChange={this.props.onKeyChange}/>
+                    </td>
+                  </tr>
+              ))}
+            </tbody>
+          </table>
+        </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/Header.js b/server/sonar-web/src/main/js/apps/project-admin/key/Header.js
new file mode 100644 (file)
index 0000000..c730528
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import { translate } from '../../../helpers/l10n';
+
+export default class Header extends React.Component {
+  render () {
+    return (
+        <header className="page-header">
+          <h1 className="page-title">
+            {translate('update_key.page')}
+          </h1>
+          <div className="page-description">
+            {translate('update_key.page.description')}
+          </div>
+        </header>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/Key.js b/server/sonar-web/src/main/js/apps/project-admin/key/Key.js
new file mode 100644 (file)
index 0000000..47b61bc
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import shallowCompare from 'react-addons-shallow-compare';
+import { connect } from 'react-redux';
+import Header from './Header';
+import UpdateForm from './UpdateForm';
+import BulkUpdate from './BulkUpdate';
+import FineGrainedUpdate from './FineGrainedUpdate';
+import { getProjectModules } from '../store/rootReducer';
+import { fetchProjectModules, changeKey } from '../store/actions';
+import { translate } from '../../../helpers/l10n';
+
+class Key extends React.Component {
+  static propTypes = {
+    component: React.PropTypes.object.isRequired,
+    fetchProjectModules: React.PropTypes.func.isRequired,
+    changeKey: React.PropTypes.func.isRequired
+  };
+
+  state = {
+    tab: 'bulk'
+  };
+
+  componentDidMount () {
+    this.props.fetchProjectModules(this.props.component.key);
+  }
+
+  shouldComponentUpdate (nextProps, nextState) {
+    return shallowCompare(this, nextProps, nextState);
+  }
+
+  handleChangeKey (key, newKey) {
+    return this.props.changeKey(key, newKey).then(() => {
+      if (key === this.props.component.key) {
+        window.location = window.baseUrl +
+            '/project/key?id=' + encodeURIComponent(newKey);
+      }
+    });
+  }
+
+  handleChangeTab (tab, e) {
+    e.preventDefault();
+    e.target.blur();
+    this.setState({ tab });
+  }
+
+  render () {
+    const { component, modules } = this.props;
+
+    const noModules = modules != null && modules.length === 0;
+    const hasModules = modules != null && modules.length > 0;
+
+    const { tab } = this.state;
+
+    return (
+        <div id="project-key" className="page page-limited">
+          <Header/>
+
+          {modules == null && (
+              <i className="spinner"/>
+          )}
+
+          {noModules && (
+              <UpdateForm
+                  component={component}
+                  onKeyChange={this.handleChangeKey.bind(this)}/>
+          )}
+
+          {hasModules && (
+              <div>
+                <div className="big-spacer-bottom">
+                  <ul className="tabs">
+                    <li>
+                      <a id="update-key-tab-bulk"
+                         className={tab === 'bulk' ? 'selected' : ''}
+                         href="#"
+                         onClick={this.handleChangeTab.bind(this, 'bulk')}>
+                        {translate('update_key.bulk_update')}
+                      </a>
+                    </li>
+                    <li>
+                      <a id="update-key-tab-fine"
+                         className={tab === 'fine' ? 'selected' : ''}
+                         href="#"
+                         onClick={this.handleChangeTab.bind(this, 'fine')}>
+                        {translate('update_key.fine_grained_key_update')}
+                      </a>
+                    </li>
+                  </ul>
+                </div>
+
+                {tab === 'bulk' && (
+                    <BulkUpdate component={component}/>
+                )}
+
+                {tab === 'fine' && (
+                    <FineGrainedUpdate
+                        component={component}
+                        modules={modules}
+                        onKeyChange={this.handleChangeKey.bind(this)}/>
+                )}
+              </div>
+          )}
+        </div>
+    );
+  }
+}
+
+const mapStateToProps = (state, ownProps) => ({
+  modules: getProjectModules(state, ownProps.component.key)
+});
+
+export default connect(
+    mapStateToProps,
+    { fetchProjectModules, changeKey }
+)(Key);
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.js b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.js
new file mode 100644 (file)
index 0000000..701d207
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import UpdateKeyConfirmation from './views/UpdateKeyConfirmation';
+import { translate } from '../../../helpers/l10n';
+
+export default class UpdateForm extends React.Component {
+  static propTypes = {
+    component: React.PropTypes.object.isRequired,
+    onKeyChange: React.PropTypes.func.isRequired
+  };
+
+  state = { newKey: null };
+
+  handleSubmit (e) {
+    e.preventDefault();
+
+    const newKey = this.refs.newKey.value;
+
+    new UpdateKeyConfirmation({
+      newKey,
+      component: this.props.component,
+      onChange: this.props.onKeyChange
+    }).render();
+  }
+
+  handleChange (e) {
+    const newKey = e.target.value;
+    this.setState({ newKey });
+  }
+
+  render () {
+    const value = this.state.newKey != null ?
+        this.state.newKey :
+        this.props.component.key;
+
+    const hasChanged = value !== this.props.component.key;
+
+    return (
+        <form onSubmit={this.handleSubmit.bind(this)}>
+          <input
+              ref="newKey"
+              id="update-key-new-key"
+              className="input-super-large"
+              value={value}
+              type="text"
+              placeholder={translate('update_key.new_key')}
+              required
+              onChange={this.handleChange.bind(this)}/>
+
+          <div className="spacer-top">
+            <button id="update-key-submit" disabled={!hasChanged}>
+              {translate('update_verb')}
+            </button>
+          </div>
+        </form>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.js b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.js
new file mode 100644 (file)
index 0000000..f23d1f7
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import UpdateKeyConfirmation from './views/UpdateKeyConfirmation';
+import { translate } from '../../../helpers/l10n';
+
+export default class UpdateKeyForm extends React.Component {
+  static propTypes = {
+    component: React.PropTypes.object.isRequired
+  };
+
+  state = {};
+
+  componentWillMount () {
+    this.handleInputChange = this.handleInputChange.bind(this);
+    this.handleUpdateClick = this.handleUpdateClick.bind(this);
+    this.handleResetClick = this.handleResetClick.bind(this);
+  }
+
+  handleInputChange (e) {
+    const key = e.target.value;
+    this.setState({ key });
+  }
+
+  handleUpdateClick (e) {
+    e.preventDefault();
+    e.target.blur();
+
+    const newKey = this.refs.newKey.value;
+
+    new UpdateKeyConfirmation({
+      newKey,
+      component: this.props.component,
+      onChange: this.props.onKeyChange
+    }).render();
+  }
+
+  handleResetClick (e) {
+    e.preventDefault();
+    e.target.blur();
+    this.setState({ key: null });
+  }
+
+  render () {
+    const { component } = this.props;
+
+    const value = this.state.key != null ?
+        this.state.key :
+        component.key;
+
+    const hasChanged = this.state.key != null &&
+        this.state.key !== component.key;
+
+    return (
+        <div className="js-fine-grained-update" data-key={component.key}>
+          <input
+              ref="newKey"
+              className="input-super-large big-spacer-right"
+              type="text"
+              value={value}
+              onChange={this.handleInputChange}/>
+
+          <div className="button-group">
+            <button disabled={!hasChanged} onClick={this.handleUpdateClick}>
+              {translate('update_verb')}
+            </button>
+
+            <button disabled={!hasChanged} onClick={this.handleResetClick}>
+              {translate('reset_verb')}
+            </button>
+          </div>
+        </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.hbs b/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.hbs
new file mode 100644 (file)
index 0000000..bec83b7
--- /dev/null
@@ -0,0 +1,30 @@
+<form id="update-key-confirmation-form" autocomplete="off">
+  <div class="modal-head">
+    <h2>{{t 'update_key.page'}}</h2>
+  </div>
+  <div class="modal-body">
+    <div class="js-modal-messages"></div>
+    {{tp 'update_key.are_you_sure_to_change_key' component.name}}
+    <div class="spacer-top">
+      <div class="display-inline-block text-right" style="width: 80px;">
+        {{t 'update_key.old_key'}}:
+      </div>
+      <div class="display-inline-block">
+        {{component.key}}
+      </div>
+    </div>
+    <div class="spacer-top">
+      <div class="display-inline-block text-right" style="width: 80px;">
+        {{t 'update_key.new_key'}}:
+      </div>
+      <div class="display-inline-block">
+        {{newKey}}
+      </div>
+    </div>
+  </div>
+  <div class="modal-foot">
+    <i class="js-modal-spinner spinner spacer-right hidden"></i>
+    <button id="update-key-confirm">{{t 'update_verb'}}</button>
+    <a href="#" class="js-modal-close">{{t 'cancel'}}</a>
+  </div>
+</form>
diff --git a/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.js b/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.js
new file mode 100644 (file)
index 0000000..9b1c4ab
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import ModalForm from '../../../../components/common/modal-form';
+import Template from './UpdateKeyConfirmation.hbs';
+import { parseError } from '../../../code/utils';
+
+export default ModalForm.extend({
+  template: Template,
+
+  onFormSubmit () {
+    ModalForm.prototype.onFormSubmit.apply(this, arguments);
+    this.disableForm();
+    this.showSpinner();
+
+    this.options.onChange(this.options.component.key, this.options.newKey)
+        .then(() => this.destroy())
+        .catch(e => {
+          parseError(e).then(msg => this.showSingleError(msg));
+          this.hideSpinner();
+          this.enableForm();
+        });
+  },
+
+  serializeData () {
+    return {
+      component: this.options.component,
+      newKey: this.options.newKey
+    };
+  }
+});
+
index 29a627e9d54cd15d358d48a38bf4e53e07cc074c..ca20001b763134f35acad765eccf0442a16c7033 100644 (file)
@@ -30,6 +30,8 @@ import {
     dissociateGateWithProject
 } from '../../../api/quality-gates';
 import { getProjectLinks, createLink } from '../../../api/projectLinks';
+import { getTree } from '../../../api/components';
+import { changeKey as changeKeyApi } from '../../../api/components';
 import { addGlobalSuccessMessage } from '../../../components/store/globalMessages';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 
@@ -157,3 +159,29 @@ export const deleteProjectLink = (projectKey, linkId) => ({
   projectKey,
   linkId
 });
+
+export const RECEIVE_PROJECT_MODULES = 'RECEIVE_PROJECT_MODULES';
+const receiveProjectModules = (projectKey, modules) => ({
+  type: RECEIVE_PROJECT_MODULES,
+  projectKey,
+  modules
+});
+
+export const fetchProjectModules = projectKey => dispatch => {
+  const options = { qualifiers: 'BRC', s: 'name', ps: 500 };
+  getTree(projectKey, options).then(r => {
+    dispatch(receiveProjectModules(projectKey, r.components));
+  });
+};
+
+export const CHANGE_KEY = 'CHANGE_KEY';
+const changeKeyAction = (key, newKey) => ({
+  type: CHANGE_KEY,
+  key,
+  newKey
+});
+
+export const changeKey = (key, newKey) => dispatch => {
+  return changeKeyApi(key, newKey)
+      .then(() => dispatch(changeKeyAction(key, newKey)));
+};
diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/components.js b/server/sonar-web/src/main/js/apps/project-admin/store/components.js
new file mode 100644 (file)
index 0000000..bc36900
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import keyBy from 'lodash/keyBy';
+import omit from 'lodash/omit';
+import { RECEIVE_PROJECT_MODULES, CHANGE_KEY } from './actions';
+
+const components = (state = {}, action = {}) => {
+  if (action.type === RECEIVE_PROJECT_MODULES) {
+    const newComponentsByKey = keyBy(action.modules, 'key');
+    return { ...state, ...newComponentsByKey };
+  }
+
+  if (action.type === CHANGE_KEY) {
+    const oldComponent = state[action.key];
+    if (oldComponent != null) {
+      const newComponent = { ...oldComponent, key: action.newKey };
+      return {
+        ...omit(state, action.key),
+        [action.newKey]: newComponent
+      };
+    }
+  }
+
+  return state;
+};
+
+export default components;
+
+export const getComponentByKey = (state, key) =>
+    state[key];
diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/modulesByProject.js b/server/sonar-web/src/main/js/apps/project-admin/store/modulesByProject.js
new file mode 100644 (file)
index 0000000..0b55882
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import { RECEIVE_PROJECT_MODULES, CHANGE_KEY } from './actions';
+
+const modulesByProject = (state = {}, action = {}) => {
+  if (action.type === RECEIVE_PROJECT_MODULES) {
+    const moduleKeys = action.modules.map(module => module.key);
+    return { ...state, [action.projectKey]: moduleKeys };
+  }
+
+  if (action.type === CHANGE_KEY) {
+    const newState = {};
+    Object.keys(state).forEach(projectKey => {
+      const moduleKeys = state[projectKey];
+      const changedKeyIndex = moduleKeys.indexOf(action.key);
+      if (changedKeyIndex !== -1) {
+        const newModuleKeys = [...moduleKeys];
+        newModuleKeys.splice(changedKeyIndex, 1, action.newKey);
+        newState[projectKey] = newModuleKeys;
+      } else {
+        newState[projectKey] = moduleKeys;
+      }
+    });
+    return newState;
+  }
+
+  return state;
+};
+
+export default modulesByProject;
+
+export const getProjectModules = (state, projectKey) =>
+    state[projectKey];
index 513e39dc67dddb9ad3cbc855cc402e3ff092c5d4..a5c56087ce91c06bb09c04babf098333b4a78879 100644 (file)
@@ -27,6 +27,12 @@ import gates, { getAllGates as nextGetAllGates, getGate } from './gates';
 import gateByProject, { getProjectGate as nextGetProjectGate } from './gateByProject';
 import links, { getLink } from './links';
 import linksByProject, { getLinks } from './linksByProject';
+import components, {
+    getComponentByKey as nextGetComponentByKey
+} from './components';
+import modulesByProject, {
+    getProjectModules as nextGetProjectModules
+} from './modulesByProject';
 import globalMessages, {
     getGlobalMessages as nextGetGlobalMessages
 } from '../../../components/store/globalMessages';
@@ -38,6 +44,8 @@ const rootReducer = combineReducers({
   gateByProject,
   links,
   linksByProject,
+  components,
+  modulesByProject,
   globalMessages
 });
 
@@ -69,5 +77,16 @@ export const getProjectLinks = (state, projectKey) =>
     getLinks(state.linksByProject, projectKey)
         .map(linkId => getLinkById(state, linkId));
 
+export const getComponentByKey = (state, componentKey) =>
+    nextGetComponentByKey(state.components, componentKey);
+
+export const getProjectModules = (state, projectKey) => {
+  const moduleKeys = nextGetProjectModules(state.modulesByProject, projectKey);
+  if (moduleKeys == null) {
+    return null;
+  }
+  return moduleKeys.map(moduleKey => getComponentByKey(state, moduleKey));
+};
+
 export const getGlobalMessages = state =>
     nextGetGlobalMessages(state.globalMessages);
index 26ca85df14c41f188e92a1b230c5863a940ff46a..5743af7c82eab0a360fda5ab95be7689512f8db1 100644 (file)
@@ -394,6 +394,7 @@ ul.bullet li {
   margin: 0 1px 0 0;
   padding: 1px 5px;
   .link-no-underline;
+  transition: none;
 }
 
 .tabs2 li a.selected, .tabs li a.selected, .tabs .ui-tabs-active a {
index d4d40889d91ddaf97e2af6ec3c487605f5a1122d..a3dad4b490a01b53b1139f05d46fff14ac156435 100644 (file)
@@ -64,68 +64,6 @@ class ProjectController < ApplicationController
 
   def key
     @project = get_current_project(params[:id])
-    @snapshot = @project.last_snapshot
-  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
-      call_backend do
-        Internal.component_api.updateKey(project.key, new_key)
-        flash[:notice] = message('update_key.key_updated')
-      end
-    end
-
-    redirect_to :action => 'key', :id => project.root_project.id
-  end
-
-  def prepare_key_bulk_update
-    @project = get_current_project(params[:id])
-
-    @string_to_replace = params[:string_to_replace].strip
-    @replacement_string = params[:replacement_string].strip
-    if @string_to_replace.blank? || @replacement_string.blank?
-      flash[:error] = message('update_key.fieds_cant_be_blank_for_bulk_update')
-      redirect_to :action => 'key', :id => @project.id
-    else
-      call_backend do
-        @key_check_results = Internal.component_api.checkModuleKeysBeforeRenaming(@project.key, @string_to_replace, @replacement_string)
-        @can_update = false
-        @duplicate_key_found = false
-        @key_check_results.each do |key, value|
-          if value=="#duplicate_key#"
-            @duplicate_key_found = true
-          else
-            @can_update = true
-          end
-        end
-        @can_update = false if @duplicate_key_found
-      end
-    end
-  end
-
-  def perform_key_bulk_update
-    project = get_current_project(params[:id])
-
-    string_to_replace = params[:string_to_replace].strip
-    replacement_string = params[:replacement_string].strip
-
-    unless string_to_replace.blank? || replacement_string.blank?
-      call_backend do
-        Internal.component_api.bulkUpdateKey(project.key, string_to_replace, replacement_string)
-        flash[:notice] = message('update_key.key_updated')
-      end
-    end
-
-    redirect_to :action => 'key', :id => project.id
   end
 
   def history
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/_key_modules.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/_key_modules.html.erb
deleted file mode 100644 (file)
index 471065c..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-    <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 }, :onsubmit => "update_launched();$j('#loading_#{id_prefix}').show();") do -%>
-          <input type="text" value="<%= h(current_module.key) -%>" name="new_key" id="key_<%= id_prefix -%>" size="80" maxlength="400">
-          <%= submit_tag message('update_key.rename'), :id => 'update_key_' + id_prefix, :class => 'action',
-                         :confirm => message('update_key.are_you_sure_to_rename_x', :params => current_module.key) %>
-          <a href="#" onclick="$j('#key_<%= id_prefix -%>').val('<%= 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 %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/_prepare_keys.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/_prepare_keys.html.erb
deleted file mode 100644 (file)
index 1624108..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<%
-  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
index 0ae01cfeea0c904e32d6ce74f8c8c36292d70124..e9dd9ae341023b9b8daca7528d9cb6c8edd3ea46 100644 (file)
@@ -1,71 +1,3 @@
-<%
-  if controller.java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'updatable_key')
-    has_modules = !@project.modules.empty?
-    reset_cycle 'modules_tree'
-%>
-
-<script type="text/javascript">
-  function update_launched() {
-    $j('input.action').each(function(index,input) {
-      input.disabled=true;
-    });
-  }
-</script>
-
-<div class="page">
-  <header class="page-header">
-    <h1 class="page-title"><%= message('update_key.page') -%></h1>
-    <p class="page-description"><%= message('update_key.page.description') -%></p>
-  </header>
-
-  <% if has_modules %>
-    <h2><%= message('update_key.bulk_update') -%></h2>
-    <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 }, :onsubmit => "update_launched();$j('#loading_bulk_update').show();") do -%>
-      <table>
-        <tr>
-          <td style="padding-right: 20px"><%= message('update_key.replace') -%>:</td>
-          <td><input type="text" value="" name="string_to_replace" id="string_to_replace" size="40" maxlength="400"></td>
-          <td class="form-val-note" style="padding-left: 10px;"><%= message('update_key.replace_example') -%></td>
-        </tr>
-        <tr>
-          <td style="padding-right: 20px"><%= message('update_key.by') -%>:</td>
-          <td><input type="text" value="" name="replacement_string" id="replacement_string" size="40" maxlength="400"></td>
-          <td class="form-val-note" style="padding-left: 10px;"><%= message('update_key.by_example') -%></td>
-        </tr>
-        <tr>
-          <td></td>
-          <td style="padding-top: 5px">
-            <%= submit_tag message('update_key.rename'), :id => 'bulk_update_button', :class => 'action' -%>
-            <span class="loading" id="loading_bulk_update" style="display: none; padding: 3px 10px;"></span>
-          </td>
-          <td></td>
-        </tr>
-      </table>
-    <% end %>
-    <br/>
-    <br/>
-    <h2><%= message('update_key.fine_grained_key_update') -%></h2>
-    <br/>
-  <% end %>
-
-    <table class="data" style="width:1%">
-      <thead>
-        <tr>
-          <th class="nowrap"><%= 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 %>
-</div>
+<% content_for :extra_script do %>
+  <script src="<%= ApplicationController.root_context -%>/js/bundles/project-admin.js?v=<%= sonar_version -%>"></script>
+<% end %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/prepare_key_bulk_update.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/prepare_key_bulk_update.html.erb
deleted file mode 100644 (file)
index 7c5bcc6..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-<%
-    if @string_to_replace && @replacement_string
-      # validation screen for bulk update
-      reset_cycle 'modules_tree'
-%>
-
-  <script type="text/javascript">
-    function update_launched() {
-      $j('input.action').each(function(index,input) {
-        input.disabled=true;
-      });
-    }
-  </script>
-
-  <div class="page">
-    <header class="page-header">
-      <h1 class="page-title">
-        <%= @can_update ? message('update_key.bulk_update_confirmation_page') : message('update_key.bulk_update_impossible') -%>
-      </h1>
-    </header>
-    <p>
-      <% if @can_update %>
-        <%= message('update_key.keys_will_be_updated_as_follows') -%>
-      <% else %>
-        <% if @duplicate_key_found %>
-          <%= message('update_key.cant_update_because_duplicate_keys', :params => [@string_to_replace, @replacement_string]) -%>
-        <% else %>
-          <%= message('update_key.no_key_to_update', :params => @string_to_replace) -%>
-        <% end %>
-      <% end %>
-    </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 }, :onsubmit => "update_launched();$j('#loading_bulk_update').show();") do -%>
-        <input type="hidden" value="<%= @project.id -%>" name="id" id="project_id">
-        <input type="hidden" value="<%= @string_to_replace -%>" name="string_to_replace" id="string_to_replace">
-        <input type="hidden" value="<%= @replacement_string -%>" name="replacement_string" id="replacement_string">
-        <br/>
-        <%= submit_tag message('update_key.rename'), :id => 'bulk_update_button', :class => 'action' -%>
-        &nbsp;<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 %>
-  </div>
-
-<% end %>
index 7bfc12f147d804ec15be4812ecdacafcea30b0fd..81080336c9a1e93073aafd88e1863771c00d68e3 100644 (file)
@@ -213,6 +213,7 @@ are_you_sure=Are you sure?
 assigned_to=Assigned to
 bulk_change=Bulk Change
 bulleted_point=Bulleted point
+check_project=Check project
 coding_rules=Rules
 click_to_add_to_favorites=Click to add to favorites
 click_to_remove_from_favorites=Click to remove from favorites
@@ -1602,29 +1603,21 @@ project_history.event_already_exists=Event "{0}" already exists.
 #------------------------------------------------------------------------------
 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.old_key=Old Key
+update_key.new_key=New Key
 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.bulk_change_description=The bulk update allows to replace a part of the current key(s) 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.current_key_for_project_x_is_x=The key of the "{0}" project is currently "{1}".
 update_key.replace=Replace
 update_key.by=By
-update_key.replace_example=Ex.: "org.myCompany"
-update_key.by_example=Ex.: "com.myNewCompany"
+update_key.replace_example=org.myCompany
+update_key.by_example=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
-update_key.bulk_update_confirmation_page=Do you really want to perform the bulk update on project keys?
-update_key.bulk_update_impossible=Bulk update can not be performed
+update_key.keys_will_be_updated_as_follows=The resources will be updated as follows:
+update_key.duplicate_key=Duplicate Key
 update_key.no_key_to_update=No key contains the string to replace ("{0}").
+update_key.are_you_sure_to_change_key=Are you sure you want to change key of "{0}", as well as all its modules and resources?
+update_key.see_results=See Results →
 
 
 #------------------------------------------------------------------------------