From c12f3d56cb99478c36242fd122258ebf0d26f740 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Wed, 10 Aug 2016 18:16:54 +0200 Subject: [PATCH] SONAR-7919 Rewrite "Update Key" project page (#1140) --- .../ProjectAdministrationTest.java | 33 ---- .../ProjectKeyPageTest.java | 184 ++++++++++++++++++ .../src/test/java/pageobjects/Navigation.java | 6 + .../test/java/pageobjects/ProjectKeyPage.java | 103 ++++++++++ ...ate-impossible-because-duplicate-keys.html | 104 ---------- ...lk-update-impossible-because-no-match.html | 104 ---------- .../bulk-update-success.html | 114 ----------- .../fine-grained-update-impossible.html | 84 -------- .../fine-grained-update-success.html | 89 --------- .../sonar-web/src/main/js/api/components.js | 26 +++ .../src/main/js/apps/project-admin/app.js | 4 + .../js/apps/project-admin/key/BulkUpdate.js | 127 ++++++++++++ .../apps/project-admin/key/BulkUpdateForm.js | 75 +++++++ .../project-admin/key/BulkUpdateResults.js | 111 +++++++++++ .../project-admin/key/FineGrainedUpdate.js | 53 +++++ .../main/js/apps/project-admin/key/Header.js | 36 ++++ .../src/main/js/apps/project-admin/key/Key.js | 134 +++++++++++++ .../js/apps/project-admin/key/UpdateForm.js | 76 ++++++++ .../apps/project-admin/key/UpdateKeyForm.js | 92 +++++++++ .../key/views/UpdateKeyConfirmation.hbs | 30 +++ .../key/views/UpdateKeyConfirmation.js | 48 +++++ .../js/apps/project-admin/store/actions.js | 28 +++ .../js/apps/project-admin/store/components.js | 47 +++++ .../project-admin/store/modulesByProject.js | 50 +++++ .../apps/project-admin/store/rootReducer.js | 19 ++ server/sonar-web/src/main/less/style.less | 1 + .../app/controllers/project_controller.rb | 62 ------ .../app/views/project/_key_modules.html.erb | 18 -- .../app/views/project/_prepare_keys.html.erb | 19 -- .../WEB-INF/app/views/project/key.html.erb | 74 +------ .../project/prepare_key_bulk_update.html.erb | 61 ------ .../resources/org/sonar/l10n/core.properties | 27 +-- 32 files changed, 1263 insertions(+), 776 deletions(-) create mode 100644 it/it-tests/src/test/java/it/projectAdministration/ProjectKeyPageTest.java create mode 100644 it/it-tests/src/test/java/pageobjects/ProjectKeyPage.java delete mode 100644 it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-impossible-because-duplicate-keys.html delete mode 100644 it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-impossible-because-no-match.html delete mode 100644 it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-success.html delete mode 100644 it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/fine-grained-update-impossible.html delete mode 100644 it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/fine-grained-update-success.html create mode 100644 server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdate.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateForm.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateResults.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/key/FineGrainedUpdate.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/key/Header.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/key/Key.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.hbs create mode 100644 server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/store/components.js create mode 100644 server/sonar-web/src/main/js/apps/project-admin/store/modulesByProject.js delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/project/_key_modules.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/project/_prepare_keys.html.erb delete mode 100644 server/sonar-web/src/main/webapp/WEB-INF/app/views/project/prepare_key_bulk_update.html.erb diff --git a/it/it-tests/src/test/java/it/projectAdministration/ProjectAdministrationTest.java b/it/it-tests/src/test/java/it/projectAdministration/ProjectAdministrationTest.java index 001d25538c0..f9863aa85b4 100644 --- a/it/it-tests/src/test/java/it/projectAdministration/ProjectAdministrationTest.java +++ b/it/it-tests/src/test/java/it/projectAdministration/ProjectAdministrationTest.java @@ -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 index 00000000000..29ea28a15b5 --- /dev/null +++ b/it/it-tests/src/test/java/it/projectAdministration/ProjectKeyPageTest.java @@ -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)); + } +} diff --git a/it/it-tests/src/test/java/pageobjects/Navigation.java b/it/it-tests/src/test/java/pageobjects/Navigation.java index d7d4a794614..a4b3f9af62d 100644 --- a/it/it-tests/src/test/java/pageobjects/Navigation.java +++ b/it/it-tests/src/test/java/pageobjects/Navigation.java @@ -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 index 00000000000..43f85c1ba43 --- /dev/null +++ b/it/it-tests/src/test/java/pageobjects/ProjectKeyPage.java @@ -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 index dcffc75a5a2..00000000000 --- a/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-impossible-because-duplicate-keys.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - bulk-update-impossible-because-duplicate-keys - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
open/sessions/logout
open/sessions/login
typeloginadmin
typepasswordadmin
clickAndWaitcommit
waitForElementPresentcss=.js-user-authenticated
open/dashboard/index/com.sonarsource.it.samples:multi-modules-sample
clickcss=#context-navigation .navbar-admin-link
clickAndWaitlink=Update Key
typeid=string_to_replacecom.sonarsource.it.samples:multi-modules-sample:module_a
typeid=replacement_stringcom.sonarsource.it.samples:multi-modules-sample:module_b
clickAndWaitid=bulk_update_button
waitForTextcss=#content h1*Bulk update can not be performed*
assertTextcss=#content p*The replacement of "com.sonarsource.it.samples:multi-modules-sample:module_a" by "com.sonarsource.it.samples:multi-modules-sample:module_b" is impossible as it would result in duplicate keys (in red below):*
assertTextcss=#content .data*Duplicate key*
assertElementNotPresentid=bulk_update_button
clickAndWaitLink=Back
waitForTextcontent*Update Key*com.sonarsource.it.samples:multi-modules-sample*
- - 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 index f4e01d07dba..00000000000 --- a/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-impossible-because-no-match.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - bulk-update-impossible-because-no-match - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
open/sessions/logout
open/sessions/login
typeloginadmin
typepasswordadmin
clickAndWaitcommit
waitForElementPresentcss=.js-user-authenticated
open/dashboard/index/com.sonarsource.it.samples:multi-modules-sample
clickcss=#context-navigation .navbar-admin-link
waitForElementPresentlink=Update Key
clickAndWaitlink=Update Key
typeid=string_to_replacefoo
typeid=replacement_stringorg.sonar
clickAndWaitid=bulk_update_button
waitForTextcss=#content h1*Bulk update can not be performed*
assertTextcss=#content*Bulk update can not be performed*No key contains the string to replace ("foo").*
assertElementNotPresentid=bulk_update_button
clickAndWaitLink=Back
waitForTextcontent*Update Key*com.sonarsource.it.samples:multi-modules-sample*
- - 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 index e75663989ca..00000000000 --- a/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/bulk-update-success.html +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - bulk-update-success - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
open/sessions/logout
open/sessions/login
typeloginadmin
typepasswordadmin
clickAndWaitcommit
waitForElementPresentcss=.js-user-authenticated
open/dashboard/index/com.sonarsource.it.samples:multi-modules-sample
clickcss=#context-navigation .navbar-admin-link
waitForElementPresentlink=Update Key
clickAndWaitlink=Update Key
typeid=string_to_replacecom.sonarsource
typeid=replacement_stringorg.sonar
clickAndWaitid=bulk_update_button
waitForTextcontent*Do you really want to perform the bulk update on project keys?*
waitForTextcontent*com.sonarsource.it.samples:multi-modules-sample*org.sonar.it.samples:multi-modules-sample*
waitForTextcontent*com.sonarsource.it.samples:multi-modules-sample:module_a*org.sonar.it.samples:multi-modules-sample:module_a*
clickAndWaitid=bulk_update_button
waitForTextcontent*The key has successfully been updated for all required resources.*
waitForTextcontent*org.sonar.it.samples:multi-modules-sample*
assertTextNotPresentcontent*com.sonarsource.it.samples:multi-modules-sample*
- - 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 index d074667d223..00000000000 --- a/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/fine-grained-update-impossible.html +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - fine-grained-update-impossible - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
open/sessions/logout
open/sessions/login
typeloginadmin
typepasswordadmin
clickAndWaitcommit
waitForElementPresentcss=.js-user-authenticated
open/dashboard/index/com.sonarsource.it.samples:multi-modules-sample
clickcss=#context-navigation .navbar-admin-link
waitForElementPresentlink=Update Key
clickAndWaitlink=Update Key
typeid=key_05com.sonarsource.it.samples:multi-modules-sample:module_b
clickAndWaitid=update_key_05
assertConfirmation*Are you sure you want to rename "com.sonarsource.it.samples:multi-modules-sample:module_*", as well as all its modules and resources?*
waitForTextid=error*"com.sonarsource.it.samples:multi-modules-sample:module_*" can not be renamed because "com.sonarsource.it.samples:multi-modules-sample:module_b" is the key of an existing resource. The update has been canceled.*
- - 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 index 4f2d9d597eb..00000000000 --- a/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-update-keys/fine-grained-update-success.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - fine-grained-update-success - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
open/sessions/logout
open/sessions/login
typeloginadmin
typepasswordadmin
clickAndWaitcommit
waitForElementPresentcss=.js-user-authenticated
open/dashboard/index/com.sonarsource.it.samples:multi-modules-sample
clickcss=#context-navigation .navbar-admin-link
waitForElementPresentlink=Update Key
clickAndWaitlink=Update Key
typeid=key_02com.sonarsource.it.samples:module_c1
clickAndWaitid=update_key_02
assertConfirmation*Are you sure you want to rename "com.sonarsource.it.samples:multi-modules-sample:module_*", as well as all its modules and resources?*
waitForTextcontent*The key has successfully been updated for all required resources.*
waitForTextcontent*com.sonarsource.it.samples:module_c1*
- - diff --git a/server/sonar-web/src/main/js/api/components.js b/server/sonar-web/src/main/js/api/components.js index f0f49a636b4..6b3238022fb 100644 --- a/server/sonar-web/src/main/js/api/components.js +++ b/server/sonar-web/src/main/js/api/components.js @@ -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); +} diff --git a/server/sonar-web/src/main/js/apps/project-admin/app.js b/server/sonar-web/src/main/js/apps/project-admin/app.js index 7981c936e87..903ed046cb9 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/app.js +++ b/server/sonar-web/src/main/js/apps/project-admin/app.js @@ -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 => { + ), 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 index 00000000000..1624faddc3f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdate.js @@ -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 ( +
+ +
+ ); + } + + renderUpdated () { + return ( +
+
+ {translate('update_key.key_updated')} + {' '} + + {translate('check_project')} + +
+
+ ); + } + + 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 ( +
+
+
+ {translate('update_key.bulk_change_description')} +
+
+ {translateWithParameters( + 'update_key.current_key_for_project_x_is_x', + component.name, + component.key + )} +
+
+ + + + {results != null && ( + + )} +
+ ); + } +} 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 index 00000000000..db31d98063d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateForm.js @@ -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 ( +
+
+ + +
+ +
+ + + +
+
+ ); + } +} 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 index 00000000000..56f5531875b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/BulkUpdateResults.js @@ -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 ( +
+ {isEmpty && ( +
+ {translateWithParameters( + 'update_key.no_key_to_update', + replace + )} +
+ )} + + {hasDuplications && ( +
+ {translateWithParameters( + 'update_key.cant_update_because_duplicate_keys', + replace, + by + )} +
+ )} + + {canUpdate && ( +
+ {translate('update_key.keys_will_be_updated_as_follows')} +
+ )} + + {!isEmpty && ( + + + + + + + + + {results.map(result => ( + + + + + ))} + +
{translate('update_key.old_key')}{translate('update_key.new_key')}
+ {result.key} + + {result.duplicate && ( + + {translate('update_key.duplicate_key')} + + )} + {result.newKey} +
+ )} + +
+ {canUpdate && ( + + )} +
+
+ ); + } +} 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 index 00000000000..6f7deacf0ed --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/FineGrainedUpdate.js @@ -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 ( +
+ + + {components.map(component => ( + + + + + ))} + +
+ + {' '} + {component.name} + + +
+
+ ); + } +} 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 index 00000000000..c730528a8a2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/Header.js @@ -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 ( +
+

+ {translate('update_key.page')} +

+
+ {translate('update_key.page.description')} +
+
+ ); + } +} 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 index 00000000000..47b61bc3e6e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/Key.js @@ -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 ( +
+
+ + {modules == null && ( + + )} + + {noModules && ( + + )} + + {hasModules && ( +
+ + + {tab === 'bulk' && ( + + )} + + {tab === 'fine' && ( + + )} +
+ )} +
+ ); + } +} + +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 index 00000000000..701d2070957 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateForm.js @@ -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 ( +
+ + +
+ +
+
+ ); + } +} 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 index 00000000000..f23d1f7069e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/UpdateKeyForm.js @@ -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 ( +
+ + +
+ + + +
+
+ ); + } +} 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 index 00000000000..bec83b7693e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.hbs @@ -0,0 +1,30 @@ +
+ + + +
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 index 00000000000..9b1c4abd26e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/key/views/UpdateKeyConfirmation.js @@ -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 + }; + } +}); + diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/actions.js b/server/sonar-web/src/main/js/apps/project-admin/store/actions.js index 29a627e9d54..ca20001b763 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/store/actions.js +++ b/server/sonar-web/src/main/js/apps/project-admin/store/actions.js @@ -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 index 00000000000..bc369003eee --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/store/components.js @@ -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 index 00000000000..0b55882cf2d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-admin/store/modulesByProject.js @@ -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]; diff --git a/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js b/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js index 513e39dc67d..a5c56087ce9 100644 --- a/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js +++ b/server/sonar-web/src/main/js/apps/project-admin/store/rootReducer.js @@ -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); diff --git a/server/sonar-web/src/main/less/style.less b/server/sonar-web/src/main/less/style.less index 26ca85df14c..5743af7c82e 100644 --- a/server/sonar-web/src/main/less/style.less +++ b/server/sonar-web/src/main/less/style.less @@ -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 { diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/project_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/project_controller.rb index d4d40889d91..a3dad4b490a 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/project_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/project_controller.rb @@ -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 index 471065cbd2e..00000000000 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/_key_modules.html.erb +++ /dev/null @@ -1,18 +0,0 @@ - - - <%= h(current_module.key) -%> - - - <% form_tag( {:action => 'update_key', :id => current_module.id }, :onsubmit => "update_launched();$j('#loading_#{id_prefix}').show();") do -%> - - <%= 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) %> - <%= message('update_key.reset') -%> - - <% end %> - - - <% 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 index 1624108b2d8..00000000000 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/_prepare_keys.html.erb +++ /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 -%> - - - <%= h(current_module.key) -%> - - - <%= future_key ? future_key : current_module.key -%> - - - <% current_module.modules.each_with_index do |sub_module, index| %> - <%= render :partial => 'prepare_keys', :locals => {:current_module => sub_module, :module_depth => module_depth+1, - :key_check_results => key_check_results} -%> - <% end %> \ No newline at end of file diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/key.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/key.html.erb index 0ae01cfeea0..e9dd9ae3410 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/key.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/key.html.erb @@ -1,71 +1,3 @@ -<% - if controller.java_facade.getResourceTypeBooleanProperty(@project.qualifier, 'updatable_key') - has_modules = !@project.modules.empty? - reset_cycle 'modules_tree' -%> - - - -
- - - <% if has_modules %> -

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

-
-

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

- <%= message('update_key.current_key_for_project_x_is_x', :params => [@project.name, @project.key]) -%> -

-
- <% form_tag( {:action => 'prepare_key_bulk_update', :id => @project.id }, :onsubmit => "update_launched();$j('#loading_bulk_update').show();") do -%> - - - - - - - - - - - - - - - - -
<%= message('update_key.replace') -%>:<%= message('update_key.replace_example') -%>
<%= message('update_key.by') -%>:<%= message('update_key.by_example') -%>
- <%= submit_tag message('update_key.rename'), :id => 'bulk_update_button', :class => 'action' -%> - -
- <% end %> -
-
-

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

-
- <% end %> - - - - - - - - - - <%= render :partial => 'key_modules', :locals => {:current_module => @project, :module_depth => 0, :id_prefix => "0"} -%> - -
<%= message('update_key.old_key') -%><%= message('update_key.new_key') -%>
- - <% end %> -
+<% content_for :extra_script do %> + +<% 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 index 7c5bcc61074..00000000000 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/project/prepare_key_bulk_update.html.erb +++ /dev/null @@ -1,61 +0,0 @@ -<% - if @string_to_replace && @replacement_string - # validation screen for bulk update - reset_cycle 'modules_tree' -%> - - - -
- -

- <% 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 %> -

- - - - - - - - - - <%= render :partial => 'prepare_keys', :locals => {:current_module => @project, :module_depth => 0, :key_check_results => @key_check_results} -%> - -
<%= message('update_key.old_key') -%><%= message('update_key.new_key') -%>
- - <% if @can_update %> - <% form_tag( {:action => 'perform_key_bulk_update', :id => @project.id }, :onsubmit => "update_launched();$j('#loading_bulk_update').show();") do -%> - - - -
- <%= submit_tag message('update_key.rename'), :id => 'bulk_update_button', :class => 'action' -%> -  <%= message('cancel') -%> - - <% end %> - <% else %> -
- <%= message('back') -%> - <% end %> -
- -<% end %> diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 7bfc12f147d..81080336c9a 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -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 "{1}". -update_key.are_you_sure_to_rename_x=Are you sure you want to rename "{0}", as well as all its modules and resources? +update_key.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 → #------------------------------------------------------------------------------ -- 2.39.5