@@ -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 | |||
*/ |
@@ -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)); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 "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):*</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> |
@@ -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 ("foo").*</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> |
@@ -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> |
@@ -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 "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>id=error</td> | |||
<td>*"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.*</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
</body> | |||
</html> |
@@ -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> |
@@ -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); | |||
} |
@@ -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); |
@@ -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> | |||
); | |||
} | |||
} |
@@ -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> | |||
); | |||
} | |||
} |
@@ -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> | |||
); | |||
} | |||
} |
@@ -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> | |||
); | |||
} | |||
} |
@@ -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> | |||
); | |||
} | |||
} |
@@ -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); |
@@ -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> | |||
); | |||
} | |||
} |
@@ -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> | |||
); | |||
} | |||
} |
@@ -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> |
@@ -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 | |||
}; | |||
} | |||
}); | |||
@@ -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))); | |||
}; |
@@ -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]; |
@@ -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]; |
@@ -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); |
@@ -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 { |
@@ -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 |
@@ -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 %> |
@@ -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 %> |
@@ -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 %> |
@@ -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' -%> | |||
<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 %> |
@@ -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 → | |||
#------------------------------------------------------------------------------ |