import it.qualityGate.QualityGateUiTest;
import it.settings.PropertySetsTest;
import it.settings.SettingsTest;
-import it.settings.SubCategoriesTest;
import it.sourceCode.EncodingTest;
import it.sourceCode.HighlightingTest;
import it.sourceCode.ProjectCodeTest;
BackgroundTasksTest.class,
// settings
PropertySetsTest.class,
- SubCategoriesTest.class,
SettingsTest.class,
// i18n
I18nTest.class,
// SONAR-2073
"/componentDashboard/DashboardTest/global_dashboard/filter-widget-anonymous.html",
- // SONAR-3460
- "/componentDashboard/DashboardTest/global_dashboard/global-admin-dashboards.html",
-
// SONAR-3461
"/componentDashboard/DashboardTest/global_dashboard/default-dashboards.html",
import com.sonar.orchestrator.Orchestrator;
import com.sonar.orchestrator.locator.FileLocation;
-import com.sonar.orchestrator.selenium.Selenese;
import it.Category1Suite;
import java.util.Date;
import java.util.List;
import org.sonar.wsclient.services.Measure;
import org.sonar.wsclient.services.Resource;
import org.sonar.wsclient.services.ResourceQuery;
+import pageobjects.Navigation;
import util.ItUtils;
-import util.selenium.SeleneseTest;
import static org.apache.commons.lang.time.DateUtils.addDays;
import static org.assertj.core.api.Assertions.assertThat;
assertThat(measures.get(0).getVariation1()).isEqualTo(17);
// Check on ui that it's possible to define leak period on project
- new SeleneseTest(Selenese.builder().setHtmlTestsInClasspath("define-leak-period-on-project",
- "/measureHistory/DifferentialPeriodsTest/define-leak-period-on-project.html"
- ).build()).runOn(orchestrator);
+ Navigation.get(orchestrator).openHomepage().logIn().asAdmin().openSettings("sample")
+ .assertSettingDisplayed("sonar.timemachine.period1");
}
/**
import com.sonar.orchestrator.build.SonarScanner;
import com.sonar.orchestrator.selenium.Selenese;
import it.Category1Suite;
+import java.io.UnsupportedEncodingException;
import java.sql.SQLException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import org.junit.rules.ExpectedException;
import org.sonar.wsclient.SonarClient;
import org.sonar.wsclient.base.HttpException;
-import org.sonar.wsclient.services.PropertyQuery;
import org.sonar.wsclient.services.ResourceQuery;
import org.sonar.wsclient.user.UserParameters;
+import pageobjects.Navigation;
+import pageobjects.settings.SettingsPage;
import util.selenium.SeleneseTest;
import static org.assertj.core.api.Assertions.assertThat;
import static util.ItUtils.projectDir;
public class ProjectAdministrationTest {
-
private static final String DELETE_WS_ENDPOINT = "api/projects/bulk_delete";
+
@ClassRule
public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR;
+
@Rule
public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public Navigation nav = Navigation.get(orchestrator);
+
private static final String PROJECT_KEY = "sample";
private static final String FILE_KEY = "sample:src/main/xoo/sample/Sample.xoo";
new SeleneseTest(
Selenese.builder().setHtmlTestsInClasspath("project-deletion", "/projectAdministration/ProjectAdministrationTest/project-deletion/project-deletion.html").build())
- .runOn(orchestrator);
+ .runOn(orchestrator);
} finally {
wsClient.userClient().deactivate(projectAdminUser);
}
assertThat(count("events where category='Version'")).as("Different number of events").isEqualTo(1);
}
- /**
- * SONAR-3425
- */
@Test
- public void project_settings() {
- scanSampleWithDate("2012-01-01");
-
- Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("project-settings",
- // SONAR-3425
- "/projectAdministration/ProjectAdministrationTest/project-settings/override-global-settings.html",
-
- "/projectAdministration/ProjectAdministrationTest/project-settings/only-on-project-settings.html").build();
- new SeleneseTest(selenese).runOn(orchestrator);
-
- // GET /api/properties/sonar.exclusions?resource=sample
- assertThat(orchestrator.getServer().getAdminWsClient().find(PropertyQuery.createForResource("sonar.exclusions", "sample")).getValue())
- .isEqualTo("my-exclusions");
-
- // GET /api/properties?resource=sample
- // Note that this WS is used by SonarLint
- assertThat(orchestrator.getServer().getAdminWsClient().findAll(PropertyQuery.createForResource(null, "sample"))).isNotEmpty();
+ public void display_project_settings() throws UnsupportedEncodingException {
+ scanSample(null, null);
+
+ SettingsPage page = nav.logIn().asAdmin().openSettings("sample")
+ .assertMenuContains("Analysis Scope")
+ .assertMenuContains("Category 1")
+ .assertMenuContains("DEV")
+ .assertMenuContains("project-only")
+ .assertMenuContains("Xoo")
+ .assertSettingDisplayed("sonar.dbcleaner.daysBeforeDeletingClosedIssues");
+
+ page.openCategory("project-only")
+ .assertSettingDisplayed("prop_only_on_project");
+
+ page.openCategory("General")
+ .assertStringSettingValue("sonar.dbcleaner.daysBeforeDeletingClosedIssues", "30")
+ .assertStringSettingValue("sonar.timemachine.period1", "previous_version")
+ .assertBooleanSettingValue("sonar.dbcleaner.cleanDirectory", true)
+ .setStringValue("sonar.dbcleaner.daysBeforeDeletingClosedIssues", "1")
+ .assertStringSettingValue("sonar.dbcleaner.daysBeforeDeletingClosedIssues", "1");
}
- /**
- * SONAR-4060
- */
@Test
- public void display_module_settings() {
+ public void display_module_settings() throws UnsupportedEncodingException {
orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample")));
- Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("module-settings",
- // SONAR-3425
- "/projectAdministration/ProjectAdministrationTest/module-settings/display-module-settings.html").build();
- new SeleneseTest(selenese).runOn(orchestrator);
+ nav.logIn().asAdmin()
+ .openSettings("com.sonarsource.it.samples:multi-modules-sample:module_a")
+ .assertMenuContains("Analysis Scope")
+ .assertSettingDisplayed("sonar.coverage.exclusions");
}
private void scanSampleWithDate(String date) {
package it.settings;
import com.sonar.orchestrator.Orchestrator;
-import com.sonar.orchestrator.selenium.Selenese;
import it.Category1Suite;
+import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.Map;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import org.sonarqube.ws.Settings;
import org.sonarqube.ws.client.setting.ResetRequest;
import org.sonarqube.ws.client.setting.SetRequest;
import org.sonarqube.ws.client.setting.SettingsService;
import org.sonarqube.ws.client.setting.ValuesRequest;
-import util.selenium.SeleneseTest;
+import pageobjects.Navigation;
+import pageobjects.settings.SettingsPage;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Arrays.asList;
@ClassRule
public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR;
+ @Rule
+ public Navigation nav = Navigation.get(orchestrator);
+
static SettingsService SETTINGS;
@BeforeClass
}
@Test
- public void support_property_sets() {
- Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("property-sets",
- "/settings/PropertySetsTest/property-sets/create.html",
- "/settings/PropertySetsTest/property-sets/delete.html",
- "/settings/PropertySetsTest/property-sets/reference.html",
- "/settings/PropertySetsTest/property-sets/all_types.html").build();
- // Use the old runner because it fails with the new Selenium runner
- orchestrator.executeSelenese(selenese);
-
- // SSF-25 Check that the password has well be setted as now it does not appears in the html source code
- assertPropertySet("sonar.demo",
- asList(entry("password", "abcde"),
- entry("boolean", "true"),
- entry("float", "42.0"),
- entry("license", "abc"),
- entry("list", "AAA"),
- entry("metric", "overall_branch_coverage"),
- entry("regexp", ".*"),
- entry("text", "text")));
+ public void support_property_sets() throws UnsupportedEncodingException {
+ SettingsPage page = nav.logIn().asAdmin().openSettings(null).openCategory("DEV")
+ .assertSettingDisplayed("sonar.test.jira.servers");
+
+ page.getPropertySetInput("sonar.test.jira.servers")
+ .setFieldValue("key", "jira1")
+ .setFieldValue("url", "http://jira")
+ .setFieldValue("port", "12345")
+ .save();
+
+ assertPropertySet("sonar.test.jira.servers", asList(
+ entry("key", "jira1"),
+ entry("url", "http://jira"),
+ entry("port", "12345")));
}
@Test
- public void support_property_sets_with_auto_generated_keys() {
- new SeleneseTest(
- Selenese.builder().setHtmlTestsInClasspath("create-auto-generated",
- "/settings/PropertySetsTest/auto-generated/create.html").build()).runOn(orchestrator);
- assertPropertySet("sonar.autogenerated", asList(entry("value", "FIRST")), asList(entry("value", "SECOND")), asList(entry("value", "THIRD")));
-
- new SeleneseTest(Selenese.builder().setHtmlTestsInClasspath("update-auto-generated",
- "/settings/PropertySetsTest/auto-generated/update.html").build()).runOn(orchestrator);
- assertPropertySet("sonar.autogenerated", asList(entry("value", "FIRST")), asList(entry("value", "THIRD")));
+ public void support_property_sets_with_auto_generated_keys() throws UnsupportedEncodingException {
+ SettingsPage page = nav.logIn().asAdmin().openSettings(null).openCategory("DEV")
+ .assertSettingDisplayed("sonar.autogenerated");
+
+ page.getPropertySetInput("sonar.autogenerated")
+ .setFieldValue(0, "value", "FIRST")
+ .setFieldValue(1, "value", "SECOND")
+ .setFieldValue(2, "value", "THIRD")
+ .save();
+
+ assertPropertySet("sonar.autogenerated",
+ asList(entry("value", "FIRST")),
+ asList(entry("value", "SECOND")),
+ asList(entry("value", "THIRD")));
}
@Test
assertThat(setting.getFieldValues().getFieldValuesList()).hasSize(fieldsValues.length);
int index = 0;
for (Settings.FieldValues.Value fieldsValue : setting.getFieldValues().getFieldValuesList()) {
- assertThat(fieldsValue.getValue()).containsOnly(fieldsValues[index].toArray(new Map.Entry[] {}));
+ assertThat(fieldsValue.getValue()).containsOnly(fieldsValues[index].toArray(new Map.Entry[]{}));
index++;
}
}
import com.sonar.orchestrator.Orchestrator;
import com.sonar.orchestrator.build.SonarScanner;
import com.sonar.orchestrator.selenium.Selenese;
+import java.io.UnsupportedEncodingException;
import java.net.URL;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import pageobjects.Navigation;
import util.selenium.SeleneseTest;
import static util.ItUtils.pluginArtifact;
}
@Test
- public void test_settings() {
+ public void test_settings() throws UnsupportedEncodingException {
URL secretKeyUrl = getClass().getResource("/settings/SettingsTest/sonar-secret.txt");
orchestrator = Orchestrator.builderEnv()
.addPlugin(pluginArtifact("settings-plugin"))
.build();
orchestrator.start();
- Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("test_settings",
- "/settings/SettingsTest/general-settings.html",
-
- // SONAR-2869 the annotation @Properties can be used on extensions and not only on plugin entry points
- "/settings/SettingsTest/hidden-extension-property.html",
- "/settings/SettingsTest/global-extension-property.html",
+ Navigation.get(orchestrator).openHomepage().logIn().asAdmin().openSettings(null)
+ .assertMenuContains("General")
+ .assertSettingDisplayed("sonar.dbcleaner.cleanDirectory")
+ .assertSettingNotDisplayed("settings.extension.hidden")
+ .assertSettingNotDisplayed("settings.extension.global");
- // SONAR-3344 - licenses
- "/settings/SettingsTest/ignore-corrupted-license.html",
- "/settings/SettingsTest/display-license.html",
- "/settings/SettingsTest/display-untyped-license.html",
-
- // SONAR-2084 - encryption
+ Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("test_settings",
+ // test encryption
"/settings/SettingsTest/generate-secret-key.html",
- "/settings/SettingsTest/encrypt-text.html",
-
- // SONAR-1378 - property types
- "/settings/SettingsTest/validate-property-type.html",
-
- // SONAR-3127 - hide passwords
- "/settings/SettingsTest/hide-passwords.html"
- ).build();
+ "/settings/SettingsTest/encrypt-text.html"
+
+ // test licenses
+ // TODO enable when license page will be rewritten
+ // "/settings/SettingsTest/ignore-corrupted-license.html",
+ // "/settings/SettingsTest/display-license.html",
+ // "/settings/SettingsTest/display-untyped-license.html"
+ ).build();
new SeleneseTest(selenese).runOn(orchestrator);
}
@Test
- public void property_relocation() {
+ public void property_relocation() throws UnsupportedEncodingException {
orchestrator = Orchestrator.builderEnv()
.addPlugin(pluginArtifact("property-relocation-plugin"))
.addPlugin(xooPlugin())
// should not fail
orchestrator.executeBuilds(withDeprecatedKey, withNewKey);
- Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("property_relocation",
- "/settings/SettingsTest/property_relocation.html"
- ).build();
- new SeleneseTest(selenese).runOn(orchestrator);
+ Navigation.get(orchestrator).openHomepage().logIn().asAdmin().openSettings(null)
+ .assertMenuContains("General")
+ .assertSettingDisplayed("sonar.newKey")
+ .assertSettingNotDisplayed("sonar.deprecatedKey");
}
}
+++ /dev/null
-/*
- * 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.settings;
-
-import com.sonar.orchestrator.Orchestrator;
-import com.sonar.orchestrator.build.SonarScanner;
-import com.sonar.orchestrator.selenium.Selenese;
-import it.Category1Suite;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.sonar.wsclient.services.PropertyQuery;
-import util.selenium.SeleneseTest;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static util.ItUtils.projectDir;
-
-public class SubCategoriesTest {
-
- @ClassRule
- public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR;
-
- /**
- * SONAR-3159
- */
- @Test
- public void should_support_global_subcategories() {
- Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("subcategories",
- "/settings/subcategories/global-subcategories.html",
- // SONAR-4495
- "/settings/subcategories/global-subcategories-no-default.html"
- ).build();
- new SeleneseTest(selenese).runOn(orchestrator);
- assertThat(getProperty("prop3", null)).isEqualTo("myValue");
- }
-
- /**
- * SONAR-3159
- */
- @Test
- public void should_support_project_subcategories() {
- orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample")));
-
- Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("subcategories",
- "/settings/subcategories/project-subcategories.html",
- // SONAR-4495
- "/settings/subcategories/project-subcategories-no-default.html"
- ).build();
- new SeleneseTest(selenese).runOn(orchestrator);
- assertThat(getProperty("prop3", "sample")).isEqualTo("myValue2");
- }
-
- static String getProperty(String key, String resourceKeyOrId) {
- return orchestrator.getServer().getAdminWsClient().find(new PropertyQuery().setKey(key).setResourceKeyOrId(resourceKeyOrId)).getValue();
- }
-}
return submitCredentials(login, password, Navigation.class);
}
+ public Navigation asAdmin() {
+ return submitCredentials("admin", "admin");
+ }
+
public LoginPage submitWrongCredentials(String login, String password) {
$("#login").val(login);
$("#password").val(password);
import com.codeborne.selenide.SelenideElement;
import com.codeborne.selenide.WebDriverRunner;
import com.sonar.orchestrator.Orchestrator;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import javax.annotation.Nullable;
import org.junit.rules.ExternalResource;
import org.openqa.selenium.By;
+import pageobjects.settings.SettingsPage;
import static com.codeborne.selenide.Selenide.$;
import static com.codeborne.selenide.Selenide.page;
return open("/background_tasks", BackgroundTasksPage.class);
}
+ public SettingsPage openSettings(@Nullable String projectKey) throws UnsupportedEncodingException {
+ String url = projectKey != null ?
+ "/project/settings?id=" + URLEncoder.encode(projectKey, "UTF-8") :
+ "/settings";
+ return open(url, SettingsPage.class);
+ }
+
public void open(String relativeUrl) {
Selenide.open(relativeUrl);
}
--- /dev/null
+/*
+ * 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.settings;
+
+import com.codeborne.selenide.SelenideElement;
+
+import static com.codeborne.selenide.Condition.exist;
+
+public class PropertySetInput {
+
+ private final SelenideElement elt;
+
+ public PropertySetInput(SelenideElement elt) {
+ this.elt = elt;
+ }
+
+ public PropertySetInput setFieldValue(int index, String fieldKey, String value) {
+ elt.findAll("input[name$=\"[" + fieldKey + "]\"]").get(index).val(value);
+ return this;
+ }
+
+ public PropertySetInput setFieldValue(String fieldKey, String value) {
+ return setFieldValue(0, fieldKey, value);
+ }
+
+ public PropertySetInput save() {
+ elt.find(".js-save-changes").click();
+ elt.find(".js-save-changes").shouldNot(exist);
+ return this;
+ }
+}
--- /dev/null
+/*
+ * 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.settings;
+
+import com.codeborne.selenide.SelenideElement;
+import org.openqa.selenium.By;
+
+import static com.codeborne.selenide.Condition.cssClass;
+import static com.codeborne.selenide.Condition.exactValue;
+import static com.codeborne.selenide.Condition.exist;
+import static com.codeborne.selenide.Condition.visible;
+import static com.codeborne.selenide.Selenide.$;
+
+public class SettingsPage {
+
+ public SettingsPage() {
+ $("#settings-page").shouldBe(visible);
+ }
+
+ public SettingsPage assertMenuContains(String categoryName) {
+ $(".settings-menu").$(By.linkText(categoryName)).shouldBe(visible);
+ return this;
+ }
+
+ public SettingsPage assertSettingDisplayed(String settingKey) {
+ $(".settings-definition[data-key='" + settingKey + "']").shouldBe(visible);
+ return this;
+ }
+
+ public SettingsPage assertSettingNotDisplayed(String settingKey) {
+ $(".settings-definition[data-key='" + settingKey + "']").shouldNotBe(visible);
+ return this;
+ }
+
+ public SettingsPage openCategory(String categoryName) {
+ $(".settings-menu").$(By.linkText(categoryName)).click();
+ return this;
+ }
+
+ public SettingsPage assertStringSettingValue(String settingKey, String value) {
+ $("input[name=\"settings[" + settingKey + "]\"]").shouldHave(exactValue(value));
+ return this;
+ }
+
+ public SettingsPage assertBooleanSettingValue(String settingKey, boolean value) {
+ SelenideElement toggle = $("button[name=\"settings[" + settingKey + "]\"]");
+ if (value) {
+ toggle.shouldHave(cssClass("boolean-toggle-on"));
+ } else {
+ toggle.shouldNotHave(cssClass("boolean-toggle-on"));
+ }
+ return this;
+ }
+
+ public SettingsPage setStringValue(String settingKey, String value) {
+ SelenideElement setting = $(".settings-definition[data-key=\"" + settingKey + "\"]");
+ setting.find("input").val(value);
+ setting.find(".js-save-changes").click();
+ setting.find(".js-save-changes").shouldNot(exist);
+ return this;
+ }
+
+ public PropertySetInput getPropertySetInput(String settingKey) {
+ SelenideElement setting = $(".settings-definition[data-key=\"" + settingKey + "\"]");
+ return new PropertySetInput(setting);
+ }
+}
+++ /dev/null
-<?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>global-admin-dashboards</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">global-admin-dashboards</td>
- </tr>
- </thead>
- <tbody>
- <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>open</td>
- <td>/</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForElementPresent</td>
- <td>link=Home</td>
- <td></td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>link=Home</td>
- <td></td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>link=Configure widgets</td>
- <td></td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>link=Back to dashboard</td>
- <td></td>
- </tr>
- <tr>
- <td>open</td>
- <td>/dashboards</td>
- <td></td>
- </tr>
- <tr>
- <td>open</td>
- <td>/settings/index</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForElementPresent</td>
- <td>link=Configuration</td>
- <td></td>
- </tr>
- <tr>
- <td>click</td>
- <td>link=Configuration</td>
- <td></td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>link=Default Dashboards</td>
- <td></td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>display-added-files</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">should_display_added_files_in_differential_drilldown</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/logout</td>
- <td></td>
-</tr>
-<tr>
- <td>open</td>
- <td>/project/settings?id=sample&category=general&subcategory=differentialviews</td>
- <td></td>
-</tr>
-<tr>
- <td>type</td>
- <td>id=login</td>
- <td>admin</td>
-</tr>
-<tr>
- <td>type</td>
- <td>id=password</td>
- <td>admin</td>
-</tr>
-<tr>
- <td>clickAndWait</td>
- <td>name=commit</td>
- <td></td>
-</tr>
-<tr>
- <td>waitForText</td>
- <td>properties</td>
- <td>*Leak Period*</td>
-</tr>
-</tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>override-global-settings</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">override-global-settings</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/logout</td>
- <td></td>
-</tr>
-<tr>
- <td>open</td>
- <td>/project/settings/com.sonarsource.it.samples%3Amulti-modules-sample%3Amodule_a</td>
- <td></td>
-</tr>
-<tr>
- <td>type</td>
- <td>id=login</td>
- <td>admin</td>
-</tr>
-<tr>
- <td>type</td>
- <td>id=password</td>
- <td>admin</td>
-</tr>
-<tr>
- <td>clickAndWait</td>
- <td>name=commit</td>
- <td></td>
-</tr>
-<tr>
- <td>waitForText</td>
- <td>plugins</td>
- <td>*Settings*</td>
-</tr>
-</tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>override-global-settings</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">override-global-settings</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/logout</td>
- <td></td>
- </tr>
- <tr>
- <td>open</td>
- <td>/project/settings/sample?category=project-only</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=login</td>
- <td>admin</td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=password</td>
- <td>admin</td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>name=commit</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForElementPresent</td>
- <td>css=.js-user-authenticated</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=input_prop_only_on_project</td>
- <td>foo</td>
- </tr>
- <tr>
- <td>click</td>
- <td>id=submit_settings</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>id=input_prop_only_on_project</td>
- <td>foo</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>override-global-settings</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">override-global-settings</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/logout</td>
- <td></td>
-</tr>
-<tr>
- <td>open</td>
- <td>/project/settings/sample?category=exclusions&subcategory=files</td>
- <td></td>
-</tr>
-<tr>
- <td>type</td>
- <td>id=login</td>
- <td>admin</td>
-</tr>
-<tr>
- <td>type</td>
- <td>id=password</td>
- <td>admin</td>
-</tr>
-<tr>
- <td>clickAndWait</td>
- <td>name=commit</td>
- <td></td>
-</tr>
-<tr>
- <td>waitForElementPresent</td>
- <td>css=.js-user-authenticated</td>
- <td></td>
-</tr>
-<tr>
- <td>type</td>
- <td>id=input_sonar.exclusions</td>
- <td>my-exclusions</td>
-</tr>
-<tr>
- <td>click</td>
- <td>id=submit_settings</td>
- <td></td>
-</tr>
-<tr>
- <td>waitForValue</td>
- <td>id=input_sonar.exclusions</td>
- <td>my-exclusions</td>
-</tr>
-</tbody>
-</table>
-</body>
-</html>
</tr>
<tr>
<td>open</td>
- <td>/settings/index</td>
- <td></td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>link=Licenses</td>
- <td></td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>link=Server ID</td>
- <td></td>
- </tr>
- <tr>
- <td>selectFrame</td>
- <td>settings_iframe</td>
+ <td>/server_id_configuration</td>
<td></td>
</tr>
<tr>
</tr>
<tr>
<td>open</td>
- <td>/settings/index</td>
- <td></td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>link=Licenses</td>
- <td></td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>link=Server ID</td>
- <td></td>
- </tr>
- <tr>
- <td>selectFrame</td>
- <td>settings_iframe</td>
+ <td>/server_id_configuration</td>
<td></td>
</tr>
<tr>
</tr>
<tr>
<td>open</td>
- <td>/settings/index</td>
- <td></td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>link=Licenses</td>
- <td></td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>link=Server ID</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForElementPresent</td>
- <td>settings_iframe</td>
- <td></td>
- </tr>
- <tr>
- <td>selectFrame</td>
- <td>settings_iframe</td>
+ <td>/server_id_configuration</td>
<td></td>
</tr>
<tr>
+++ /dev/null
-<?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>create</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</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>/settings?category=DEV</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>1</td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=input_value</td>
- <td>FIRST</td>
- </tr>
- <tr>
- <td>click</td>
- <td>css=button.add_value</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>xpath=(//input[@id='input_value'])[2]</td>
- <td>SECOND</td>
- </tr>
- <tr>
- <td>click</td>
- <td>css=button.add_value</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>xpath=(//input[@id='input_value'])[3]</td>
- <td>THIRD</td>
- </tr>
- <tr>
- <td>click</td>
- <td>id=submit_settings</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>2</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>update</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</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>/settings?category=DEV</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>1</td>
- </tr>
- <tr>
- <td>click</td>
- <td>link=Delete</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForVisible</td>
- <td>id=submit_settings</td>
- <td></td>
- </tr>
- <tr>
- <td>click</td>
- <td>id=submit_settings</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>2</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>all_types</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</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>/settings?category=DEV</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>1</td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=input_text</td>
- <td>text</td>
- </tr>
- <tr>
- <td>select</td>
- <td>id=input_boolean</td>
- <td>label=True</td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=input_float</td>
- <td>42.0</td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=input_license</td>
- <td>abc</td>
- </tr>
- <tr>
- <td>select</td>
- <td>id=input_metric</td>
- <td>label=Overall Condition Coverage</td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=input_password</td>
- <td>abcde</td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=input_regexp</td>
- <td>.*</td>
- </tr>
- <tr>
- <td>select</td>
- <td>id=input_list</td>
- <td>label=AAA</td>
- </tr>
- <tr>
- <td>click</td>
- <td>id=submit_settings</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>2</td>
- </tr>
- <tr>
- <td>assertValue</td>
- <td>id=input_text</td>
- <td>text</td>
- </tr>
- <tr>
- <td>assertValue</td>
- <td>id=input_boolean</td>
- <td>true</td>
- </tr>
- <tr>
- <td>assertValue</td>
- <td>id=input_float</td>
- <td>42.0</td>
- </tr>
- <tr>
- <td>assertValue</td>
- <td>id=input_license</td>
- <td>abc</td>
- </tr>
- <tr>
- <td>assertValue</td>
- <td>id=input_metric</td>
- <td>overall_branch_coverage</td>
- </tr>
- <tr>
- <td>assertValue</td>
- <td>id=input_password</td>
- <td>{{*******************}}</td>
- </tr>
- <tr>
- <td>assertValue</td>
- <td>id=input_regexp</td>
- <td>exact:.*</td>
- </tr>
- <tr>
- <td>assertValue</td>
- <td>id=input_list</td>
- <td>AAA</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>create</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</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>/settings?category=DEV</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>1</td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=input_key</td>
- <td>jira1</td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=input_url</td>
- <td>http://jira</td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=input_port</td>
- <td>12345</td>
- </tr>
- <tr>
- <td>click</td>
- <td>id=submit_settings</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>2</td>
- </tr>
- <tr>
- <td>assertValue</td>
- <td>id=input_key</td>
- <td>jira1</td>
- </tr>
- <tr>
- <td>assertValue</td>
- <td>id=input_url</td>
- <td>exact:http://jira</td>
- </tr>
- <tr>
- <td>assertValue</td>
- <td>id=input_port</td>
- <td>12345</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>delete</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</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>/settings?category=DEV</td>
- <td></td>
-</tr>
-<tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>1</td>
-</tr>
-<tr>
- <td>type</td>
- <td>id=input_key</td>
- <td>jira1</td>
-</tr>
-<tr>
- <td>type</td>
- <td>id=input_url</td>
- <td>http://jira1</td>
-</tr>
-<tr>
- <td>click</td>
- <td>css=#block_sonar\.test\.jira\.servers .add_value</td>
- <td></td>
-</tr>
-<tr>
- <td>type</td>
- <td>xpath=(//input[@id='input_key'])[2]</td>
- <td>jira2</td>
-</tr>
-<tr>
- <td>type</td>
- <td>xpath=(//input[@id='input_url'])[2]</td>
- <td>http://jira2</td>
-</tr>
-<tr>
- <td>click</td>
- <td>id=submit_settings</td>
- <td></td>
-</tr>
-<tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>2</td>
-</tr>
-<tr>
- <td>assertValue</td>
- <td>xpath=(//input[@id='input_key'])[1]</td>
- <td>jira1</td>
-</tr>
-<tr>
- <td>assertValue</td>
- <td>xpath=(//input[@id='input_key'])[2]</td>
- <td>jira2</td>
-</tr>
-<tr>
- <td>click</td>
- <td>xpath=(//a[contains(text(),'Delete')])[3]</td>
- <td></td>
-</tr>
-<tr>
- <td>click</td>
- <td>id=submit_settings</td>
- <td></td>
-</tr>
-<tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>3</td>
-</tr>
-<tr>
- <td>assertValue</td>
- <td>xpath=(//input[@id='input_key'])[1]</td>
- <td>jira1</td>
-</tr>
-<tr>
- <td>waitForNotText</td>
- <td>xpath=(//input[@id='input_key'])</td>
- <td>*jira2*</td>
-</tr>
-</tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>reference</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</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>/settings?category=DEV</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>1</td>
- </tr>
- <tr>
- <td>type</td>
- <td>xpath=(//input[@id='input_key'])[1]</td>
- <td>jira1</td>
- </tr>
- <tr>
- <td>click</td>
- <td>css=#block_sonar\.test\.jira\.servers .add_value</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>xpath=(//input[@id='input_key'])[2]</td>
- <td>jira2</td>
- </tr>
- <tr>
- <td>click</td>
- <td>id=submit_settings</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>2</td>
- </tr>
- <tr>
- <td>assertSelectOptions</td>
- <td>id=input_sonar.test.jira</td>
- <td>Default,jira1,jira2</td>
- </tr>
- <tr>
- <td>click</td>
- <td>xpath=(//a[contains(text(),'Delete')])[2]</td>
- <td></td>
- </tr>
- <tr>
- <td>click</td>
- <td>id=submit_settings</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>3</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
</tr>
<tr>
<td>open</td>
- <td>/settings?category=security&subcategory=encryption</td>
+ <td>/encryption_configuration</td>
<td></td>
</tr>
<tr>
</tr>
<tr>
<td>waitForElementPresent</td>
- <td>css=.js-user-authenticated</td>
- <td></td>
- </tr>
- <tr>
- <td>selectFrame</td>
- <td>settings_iframe</td>
+ <td>css=#submit_encrypt</td>
<td></td>
</tr>
<tr>
+++ /dev/null
-<?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>settings_on_core_plugins</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">settings_on_core_plugins</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/logout</td>
- <td></td>
-</tr>
-<tr>
- <td>open</td>
- <td>/sessions/new</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>/settings/index</td>
- <td></td>
-</tr>
-<tr>
- <td>waitForText</td>
- <td>plugins</td>
- <td>*General*</td>
-</tr>
-</tbody>
-</table>
-</body>
-</html>
</tr>
<tr>
<td>open</td>
- <td>/settings?category=security&subcategory=encryption</td>
+ <td>/encryption_configuration</td>
<td></td>
</tr>
<tr>
</tr>
<tr>
<td>waitForElementPresent</td>
- <td>css=.js-user-authenticated</td>
- <td></td>
- </tr>
- <tr>
- <td>selectFrame</td>
- <td>settings_iframe</td>
+ <td>css=#submit_encrypt</td>
<td></td>
</tr>
<tr>
+++ /dev/null
-<?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>global-extension-property</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">global-extension-property</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</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>/settings?category=Settings</td>
- <td></td>
- </tr>
- <tr>
- <td>assertNotText</td>
- <td>plugins</td>
- <td>glob:*Hidden*</td>
- </tr>
-
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>hidden-extension-property</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <thead>
- <tr>
- <td rowspan="1" colspan="3">hidden-extension-property</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</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>/settings?category=Settings</td>
- <td></td>
- </tr>
- <tr>
- <td>assertNotText</td>
- <td>plugins</td>
- <td>glob:*Hidden*</td>
- </tr>
-
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>hide-passwords</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>/settings?category=Settings</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=login</td>
- <td>admin</td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=password</td>
- <td>admin</td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>name=commit</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForElementPresent</td>
- <td>css=.js-user-authenticated</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>block_password</td>
- <td>*Default*</td>
- </tr>
- <tr>
- <td>assertNotText</td>
- <td>block_password</td>
- <td>*Default*sonar*</td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>property-relocation</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>/settings/index?category=general</td>
- <td></td>
-</tr>
-<tr>
- <td>type</td>
- <td>id=login</td>
- <td>admin</td>
-</tr>
-<tr>
- <td>type</td>
- <td>id=password</td>
- <td>admin</td>
-</tr>
-<tr>
- <td>clickAndWait</td>
- <td>name=commit</td>
- <td></td>
-</tr>
-<tr>
- <td>waitForElementPresent</td>
- <td>css=.js-user-authenticated</td>
- <td></td>
-</tr>
-<tr>
- <td>waitForText</td>
- <td>properties</td>
- <td>*sonar.newKey*</td>
-</tr>
-<tr>
- <td>assertNotText</td>
- <td>properties</td>
- <td>*sonar.deprecatedKey*</td>
-</tr>
-</tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>validate-property-type</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>/settings?category=Settings</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=login</td>
- <td>admin</td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=password</td>
- <td>admin</td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>name=commit</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForElementPresent</td>
- <td>css=.js-user-authenticated</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=input_float</td>
- <td>abc</td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=input_integer</td>
- <td>123</td>
- </tr>
- <tr>
- <td>click</td>
- <td>id=submit_settings</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>properties</td>
- <td>*Not a floating point number*</td>
- </tr>
- <tr>
- <td>assertValue</td>
- <td>input_integer</td>
- <td>123</td>
- </tr>
- <tr>
- <td>open</td>
- <td>/settings?category=Settings</td>
- <td></td>
- </tr>
- <tr>
- <td>assertValue</td>
- <td>input_float</td>
- <td></td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>global-subcategories</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</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>/settings?category=Category 2</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>1</td>
- </tr>
- <!-- First subcategory should be selected by default -->
- <tr>
- <td>assertElementPresent</td>
- <td>id=input_prop2_1</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop2_2</td>
- <td></td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>global-subcategories</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</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>/settings?category=Category 1</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>1</td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop1</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop2</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop3</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementPresent</td>
- <td>id=input_prop4</td>
- <td></td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>link=Sub category 1</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementPresent</td>
- <td>id=input_prop1</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementPresent</td>
- <td>id=input_prop2</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop3</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop4</td>
- <td></td>
- </tr>
- <!-- Verify index attribute is taken into account -->
- <tr>
- <td>assertElementPresent</td>
- <td>xpath=//.[@id='input_prop2']/following::input[@id='input_prop1']</td>
- <td></td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>link=Sub category 2</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=input_prop3</td>
- <td>myValue</td>
- </tr>
- <tr>
- <td>click</td>
- <td>id=submit_settings</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>2</td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop1</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop2</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementPresent</td>
- <td>id=input_prop3</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop4</td>
- <td></td>
- </tr>
- <tr>
- <td>assertValue</td>
- <td>id=input_prop3</td>
- <td>myValue</td>
- </tr>
- <!-- SONAR-4473 -->
- <tr>
- <td>clickAndWait</td>
- <td>link=Sub category 1</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementPresent</td>
- <td>id=input_prop1</td>
- <td></td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>create</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</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>/project/settings/sample?category=Category 2</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>1</td>
- </tr>
- <!-- First subcategory should be selected by default -->
- <tr>
- <td>assertElementPresent</td>
- <td>id=input_prop2_1</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop2_2</td>
- <td></td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
+++ /dev/null
-<?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>create</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
- <tbody>
- <tr>
- <td>open</td>
- <td>/sessions/new</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>/project/settings/sample?category=Category 1</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>1</td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop1</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop2</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop3</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementPresent</td>
- <td>id=input_prop4</td>
- <td></td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>link=Sub category 1</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementPresent</td>
- <td>id=input_prop1</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementPresent</td>
- <td>id=input_prop2</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop3</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop4</td>
- <td></td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>link=Sub category 2</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=input_prop3</td>
- <td>myValue2</td>
- </tr>
- <tr>
- <td>click</td>
- <td>id=submit_settings</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForValue</td>
- <td>name=page_version</td>
- <td>2</td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop1</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop2</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementPresent</td>
- <td>id=input_prop3</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementNotPresent</td>
- <td>id=input_prop4</td>
- <td></td>
- </tr>
- <tr>
- <td>assertValue</td>
- <td>id=input_prop3</td>
- <td>myValue2</td>
- </tr>
- <!-- SONAR-4473 -->
- <tr>
- <td>clickAndWait</td>
- <td>link=Sub category 1</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementPresent</td>
- <td>id=input_prop1</td>
- <td></td>
- </tr>
- </tbody>
-</table>
-</body>
-</html>
</tr>
<tr>
<td>open</td>
- <td>/settings</td>
+ <td>/updatecenter</td>
<td></td>
</tr>
<tr>
<td>css=.js-user-authenticated</td>
<td></td>
</tr>
- <tr>
- <td>open</td>
- <td>/updatecenter</td>
- <td></td>
- </tr>
<tr>
<td>waitForText</td>
<td>content</td>
</tr>
<tr>
<td>open</td>
- <td>/settings/index</td>
+ <td>/settings</td>
<td></td>
</tr>
<tr>
</tr>
<tr>
<td>assertLocation</td>
- <td>*/settings/index</td>
- <td></td>
- </tr>
- <tr>
- <td>assertElementPresent</td>
- <td>plugins</td>
+ <td>*/settings</td>
<td></td>
</tr>
</tbody>
'projects': './src/main/js/apps/projects/app.js',
'quality-gates': './src/main/js/apps/quality-gates/app.js',
'quality-profiles': './src/main/js/apps/quality-profiles/app.js',
+ 'settings': './src/main/js/apps/settings/app.js',
'source-viewer': './src/main/js/apps/source-viewer/app.js',
'system': './src/main/js/apps/system/app.js',
'update-center': './src/main/js/apps/update-center/app.js',
function runDevServer (port) {
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true,
- quiet: true,
publicPath: config.output.publicPath
}));
- app.use(require('webpack-hot-middleware')(compiler, {
- noInfo: true,
- quiet: true
- }));
+ app.use(require('webpack-hot-middleware')(compiler));
app.all('*', proxy(API_HOST, {
forwardPath: function (req) {
--- /dev/null
+/*
+ * 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 omitBy from 'lodash/omitBy';
+import { getJSON, post } from '../helpers/request';
+import { TYPE_PROPERTY_SET } from '../apps/settings/constants';
+
+export function getDefinitions (componentKey) {
+ const url = '/api/settings/list_definitions';
+ const data = componentKey ? { componentKey } : {};
+ return getJSON(url, data).then(r => r.definitions);
+}
+
+export function getValues (keys, componentKey) {
+ const url = '/api/settings/values';
+ const data = { keys };
+ if (componentKey) {
+ data.componentKey = componentKey;
+ }
+ return getJSON(url, data).then(r => r.settings);
+}
+
+export function setSettingValue (definition, value, componentKey) {
+ const url = '/api/settings/set';
+
+ const { key } = definition;
+ const data = { key };
+
+ if (definition.multiValues) {
+ data.values = value;
+ } else if (definition.type === TYPE_PROPERTY_SET) {
+ data.fieldValues = value
+ .map(fields => omitBy(fields, value => value == null))
+ .map(JSON.stringify);
+ } else {
+ data.value = value;
+ }
+
+ if (componentKey) {
+ data.componentKey = componentKey;
+ }
+
+ return post(url, data);
+}
+
+export function resetSettingValue (key, componentKey) {
+ const url = '/api/settings/reset';
+ const data = { key };
+ if (componentKey) {
+ data.componentKey = componentKey;
+ }
+ return post(url, data);
+}
const url = '/api/users/identity_providers';
return getJSON(url);
}
+
+export function searchUsers (query) {
+ const url = '/api/users/search';
+ const data = { q: query };
+ return getJSON(url, data);
+}
--- /dev/null
+/*
+ * 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 { expect } from 'chai';
+import { getEmptyValue } from '../utils';
+import { TYPE_PROPERTY_SET, TYPE_STRING, TYPE_SINGLE_SELECT_LIST, TYPE_BOOLEAN } from '../constants';
+
+const fields = [
+ { key: 'foo', type: TYPE_STRING },
+ { key: 'bar', type: TYPE_SINGLE_SELECT_LIST }
+];
+
+describe('Settings :: Utils', () => {
+ describe('#getEmptyValue()', () => {
+ it('should work for property sets', () => {
+ const setting = { type: TYPE_PROPERTY_SET, fields };
+ expect(getEmptyValue(setting)).to.deep.equal([{ foo: '', bar: null }]);
+ });
+
+ it('should work for multi values string', () => {
+ const setting = { type: TYPE_STRING, multiValues: true };
+ expect(getEmptyValue(setting)).to.deep.equal(['']);
+ });
+
+ it('should work for multi values boolean', () => {
+ const setting = { type: TYPE_BOOLEAN, multiValues: true };
+ expect(getEmptyValue(setting)).to.deep.equal([null]);
+ });
+ });
+});
--- /dev/null
+/*
+ * 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 { render } from 'react-dom';
+import { Provider } from 'react-redux';
+import { Router, Route, Redirect, useRouterHistory } from 'react-router';
+import { createHistory } from 'history';
+import App from './components/App';
+import rootReducer from './store/rootReducer';
+import configureStore from '../../components/store/configureStore';
+
+window.sonarqube.appStarted.then(options => {
+ const el = document.querySelector(options.el);
+
+ const controller = options.component ? '/project/settings' : '/settings';
+ const history = useRouterHistory(createHistory)({
+ basename: window.baseUrl + controller
+ });
+
+ const store = configureStore(rootReducer);
+
+ const withComponent = ComposedComponent => props =>
+ <ComposedComponent {...props} component={options.component}/>;
+
+ render((
+ <Provider store={store}>
+ <Router history={history}>
+ <Redirect from="/index" to="/"/>
+ <Route path="/" component={withComponent(App)}/>
+ </Router>
+ </Provider>
+ ), el);
+});
--- /dev/null
+/*
+ * 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 { connect } from 'react-redux';
+import CategoriesList from './CategoriesList';
+import { getAllCategories } from '../store/rootReducer';
+
+class AllCategoriesList extends React.Component {
+ render () {
+ return <CategoriesList {...this.props}/>;
+ }
+}
+
+const mapStateToProps = state => ({
+ categories: getAllCategories(state)
+});
+
+export default connect(
+ mapStateToProps
+)(AllCategoriesList);
--- /dev/null
+/*
+ * 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 PageHeader from './PageHeader';
+import CategoryDefinitionsList from './CategoryDefinitionsList';
+import AllCategoriesList from './AllCategoriesList';
+import GlobalMessagesContainer from './GlobalMessagesContainer';
+import { fetchSettings } from '../store/actions';
+import { getDefaultCategory } from '../store/rootReducer';
+import '../styles.css';
+
+class App extends React.Component {
+ static propTypes = {
+ component: React.PropTypes.object,
+ fetchSettings: React.PropTypes.func.isRequired,
+ defaultCategory: React.PropTypes.string
+ };
+
+ state = { loaded: false };
+
+ componentDidMount () {
+ document.querySelector('html').classList.add('dashboard-page');
+ const componentKey = this.props.component ? this.props.component.key : null;
+ this.props.fetchSettings(componentKey).then(() => {
+ this.setState({ loaded: true });
+ });
+ }
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ componentDidUpdate (prevProps) {
+ if (prevProps.component !== this.props.component) {
+ const componentKey = this.props.component ? this.props.component.key : null;
+ this.props.fetchSettings(componentKey);
+ }
+ }
+
+ componentWillUnmount () {
+ document.querySelector('html').classList.remove('dashboard-page');
+ }
+
+ render () {
+ if (!this.state.loaded) {
+ return null;
+ }
+
+ const { query } = this.props.location;
+ const selectedCategory = query.category || this.props.defaultCategory;
+
+ return (
+ <div id="settings-page" className="page page-limited">
+ <PageHeader component={this.props.component}/>
+ <GlobalMessagesContainer/>
+ <div className="settings-layout">
+ <div className="settings-side">
+ <AllCategoriesList
+ component={this.props.component}
+ selectedCategory={selectedCategory}
+ defaultCategory={this.props.defaultCategory}/>
+ </div>
+ <div className="settings-main">
+ <CategoryDefinitionsList
+ component={this.props.component}
+ category={selectedCategory}/>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ defaultCategory: getDefaultCategory(state)
+});
+
+export default connect(
+ mapStateToProps,
+ { fetchSettings }
+)(App);
+
--- /dev/null
+/*
+ * 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 sortBy from 'lodash/sortBy';
+import { IndexLink } from 'react-router';
+import { getCategoryName } from '../utils';
+
+export default class CategoriesList extends React.Component {
+ static propTypes = {
+ categories: React.PropTypes.array.isRequired,
+ selectedCategory: React.PropTypes.string.isRequired,
+ defaultCategory: React.PropTypes.string.isRequired
+ };
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ renderLink (category) {
+ const query = {};
+
+ if (category.key !== this.props.defaultCategory) {
+ query.category = category.key.toLowerCase();
+ }
+
+ if (this.props.component) {
+ query.id = this.props.component.key;
+ }
+
+ const className = category.key.toLowerCase() === this.props.selectedCategory.toLowerCase() ? 'active' : '';
+
+ return (
+ <IndexLink to={{ pathname: '/', query }} className={className} title={category.name}>
+ {category.name}
+ </IndexLink>
+ );
+ }
+
+ render () {
+ const categoriesWithName = this.props.categories.map(key => ({ key, name: getCategoryName(key) }));
+ const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase());
+
+ return (
+ <ul className="settings-menu">
+ {sortedCategories.map(category => (
+ <li key={category.key}>
+ {this.renderLink(category)}
+ </li>
+ ))}
+ </ul>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 { connect } from 'react-redux';
+import SubCategoryDefinitionsList from './SubCategoryDefinitionsList';
+import { getSettingsForCategory } from '../store/rootReducer';
+
+class CategoryDefinitionsList extends React.Component {
+ render () {
+ return <SubCategoryDefinitionsList {...this.props}/>;
+ }
+}
+
+const mapStateToProps = (state, ownProps) => ({
+ settings: getSettingsForCategory(state, ownProps.category)
+});
+
+export default connect(
+ mapStateToProps
+)(CategoryDefinitionsList);
--- /dev/null
+/*
+ * 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 { connect } from 'react-redux';
+import shallowCompare from 'react-addons-shallow-compare';
+import classNames from 'classnames';
+import Input from './inputs/Input';
+import DefinitionDefaults from './DefinitionDefaults';
+import DefinitionChanges from './DefinitionChanges';
+import { getPropertyName, getPropertyDescription, isEmptyValue, getSettingValue, isDefaultOrInherited } from '../utils';
+import { translateWithParameters, translate } from '../../../helpers/l10n';
+import { resetValue, saveValue } from '../store/actions';
+import { isLoading, getValidationMessage, getChangedValue } from '../store/rootReducer';
+import { failValidation, passValidation } from '../store/settingsPage/validationMessages/actions';
+import { cancelChange, changeValue } from '../store/settingsPage/changedValues/actions';
+
+class Definition extends React.Component {
+ static propTypes = {
+ component: React.PropTypes.object,
+ setting: React.PropTypes.object.isRequired,
+ changedValue: React.PropTypes.any,
+ loading: React.PropTypes.bool.isRequired,
+ validationMessage: React.PropTypes.string,
+
+ changeValue: React.PropTypes.func.isRequired,
+ cancelChange: React.PropTypes.func.isRequired,
+ saveValue: React.PropTypes.func.isRequired,
+ resetValue: React.PropTypes.func.isRequired,
+ failValidation: React.PropTypes.func.isRequired,
+ passValidation: React.PropTypes.func.isRequired
+ };
+
+ state = {
+ success: false
+ };
+
+ componentDidMount () {
+ this.mounted = true;
+ }
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ safeSetState (changes) {
+ if (this.mounted) {
+ this.setState(changes);
+ }
+ }
+
+ handleChange (value) {
+ clearTimeout(this.timeout);
+ return this.props.changeValue(this.props.setting.definition.key, value);
+ }
+
+ handleReset () {
+ const componentKey = this.props.component ? this.props.component.key : null;
+ const { definition } = this.props.setting;
+ return this.props.resetValue(definition.key, componentKey).then(() => {
+ this.safeSetState({ success: true });
+ this.timeout = setTimeout(() => this.safeSetState({ success: false }), 3000);
+ }).catch(() => { /* do nothing */ });
+ }
+
+ handleCancel () {
+ this.props.cancelChange(this.props.setting.definition.key);
+ this.props.passValidation(this.props.setting.definition.key);
+ }
+
+ handleSave () {
+ this.safeSetState({ success: false });
+ const { definition } = this.props.setting;
+ if (isEmptyValue(definition, this.props.changedValue)) {
+ this.props.failValidation(definition.key, translate('settings.state.value_cant_be_empty'));
+ return;
+ }
+
+ const componentKey = this.props.component ? this.props.component.key : null;
+ this.props.saveValue(this.props.setting.definition.key, componentKey).then(() => {
+ this.safeSetState({ success: true });
+ this.timeout = setTimeout(() => this.safeSetState({ success: false }), 3000);
+ }).catch(() => { /* do nothing */ });
+ }
+
+ render () {
+ const { setting, changedValue, loading } = this.props;
+ const { definition } = setting;
+ const propertyName = getPropertyName(definition);
+
+ const hasValueChanged = changedValue != null;
+
+ const className = classNames('settings-definition', {
+ 'settings-definition-changed': hasValueChanged
+ });
+
+ const effectiveValue = hasValueChanged ? changedValue : getSettingValue(setting);
+
+ const isDefault = isDefaultOrInherited(setting) && !hasValueChanged;
+
+ return (
+ <div className={className} data-key={definition.key}>
+ <div className="settings-definition-left">
+ <h3 className="settings-definition-name" title={propertyName}>
+ {propertyName}
+ </h3>
+
+ <div className="settings-definition-description markdown small spacer-top"
+ dangerouslySetInnerHTML={{ __html: getPropertyDescription(definition) }}/>
+
+ <div className="settings-definition-key note little-spacer-top">
+ {translateWithParameters('settings.key_x', definition.key)}
+ </div>
+ </div>
+
+ <div className="settings-definition-right">
+ <Input setting={setting} value={effectiveValue} onChange={this.handleChange.bind(this)}/>
+
+ {!hasValueChanged && (
+ <DefinitionDefaults
+ setting={setting}
+ isDefault={isDefault}
+ onReset={() => this.handleReset()}/>
+ )}
+
+ {hasValueChanged && (
+ <DefinitionChanges
+ onSave={this.handleSave.bind(this)}
+ onCancel={this.handleCancel.bind(this)}/>
+ )}
+
+ <div className="settings-definition-state">
+ {loading && (
+ <span className="text-info">
+ <span className="settings-definition-state-icon">
+ <i className="spinner"/>
+ </span>
+ {translate('settings.state.saving')}
+ </span>
+ )}
+
+ {!loading && (this.props.validationMessage != null) && (
+ <span className="text-danger">
+ <span className="settings-definition-state-icon">
+ <i className="icon-alert-error"/>
+ </span>
+ {translateWithParameters('settings.state.validation_failed', this.props.validationMessage)}
+ </span>
+ )}
+
+ {!loading && this.state.success && (
+ <span className="text-success">
+ <span className="settings-definition-state-icon">
+ <i className="icon-check"/>
+ </span>
+ {translate('settings.state.saved')}
+ </span>
+ )}
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+const mapStateToProps = (state, ownProps) => ({
+ changedValue: getChangedValue(state, ownProps.setting.definition.key),
+ loading: isLoading(state, ownProps.setting.definition.key),
+ validationMessage: getValidationMessage(state, ownProps.setting.definition.key)
+});
+
+export default connect(
+ mapStateToProps,
+ { changeValue, saveValue, resetValue, failValidation, passValidation, cancelChange }
+)(Definition);
--- /dev/null
+/*
+ * 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 { translate } from '../../../helpers/l10n';
+
+export default class DefinitionChanges extends React.Component {
+ static propTypes = {
+ onSave: React.PropTypes.func.isRequired,
+ onCancel: React.PropTypes.func.isRequired
+ };
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ handleSaveClick (e) {
+ e.preventDefault();
+ e.target.blur();
+ this.props.onSave();
+ }
+
+ handleCancelChange (e) {
+ e.preventDefault();
+ e.target.blur();
+ this.props.onCancel();
+ }
+
+ render () {
+ return (
+ <div className="settings-definition-changes">
+ <button className="js-save-changes button-success" onClick={e => this.handleSaveClick(e)}>
+ {translate('save')}
+ </button>
+
+ <button className="js-cancel-changes big-spacer-left button-link" onClick={e => this.handleCancelChange(e)}>
+ {translate('cancel')}
+ </button>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 { getSettingValue, isEmptyValue, getDefaultValue } from '../utils';
+import { translate } from '../../../helpers/l10n';
+
+export default class DefinitionDefaults extends React.Component {
+ static propTypes = {
+ setting: React.PropTypes.object.isRequired,
+ isDefault: React.PropTypes.bool.isRequired,
+ onReset: React.PropTypes.func.isRequired
+ };
+
+ handleReset (e) {
+ e.preventDefault();
+ e.target.blur();
+ this.props.onReset();
+ }
+
+ render () {
+ const { setting, isDefault } = this.props;
+ const { definition } = setting;
+
+ const isExplicitlySet = !isDefault && !isEmptyValue(definition, getSettingValue(setting));
+
+ return (
+ <div>
+ {isDefault && (
+ <div className="spacer-top note" style={{ lineHeight: '24px' }}>
+ {translate('settings._default')}
+ </div>
+ )}
+
+ {isExplicitlySet && (
+ <div className="spacer-top nowrap">
+ <button onClick={e => this.handleReset(e)}>{translate('reset_verb')}</button>
+ <span className="spacer-left note">{translate('default')}{': '}{getDefaultValue(setting)}</span>
+ </div>
+ )}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 Definition from './Definition';
+
+export default class DefinitionsList extends React.Component {
+ static propTypes = {
+ component: React.PropTypes.object,
+ settings: React.PropTypes.array.isRequired
+ };
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ render () {
+ return (
+ <ul className="settings-definitions-list">
+ {this.props.settings.map(setting => (
+ <li key={setting.definition.key}>
+ <Definition component={this.props.component} setting={setting}/>
+ </li>
+ ))}
+ </ul>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 { connect } from 'react-redux';
+import GlobalMessages from '../../../components/controls/GlobalMessages';
+import { getGlobalMessages } from '../store/rootReducer';
+
+const mapStateToProps = state => ({
+ messages: getGlobalMessages(state)
+});
+
+export default connect(mapStateToProps)(GlobalMessages);
--- /dev/null
+/*
+ * 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 PageHeader extends React.Component {
+ static propTypes = {
+ component: React.PropTypes.object
+ };
+
+ render () {
+ const title = this.props.component != null ?
+ translate('project_settings.page') :
+ translate('settings.page');
+
+ const description = this.props.component != null ?
+ translate('project_settings.page.description') :
+ translate('settings.page.description');
+
+ return (
+ <header className="page-header">
+ <h1 className="page-title">{title}</h1>
+ <div className="page-description">{description}</div>
+ </header>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 groupBy from 'lodash/groupBy';
+import sortBy from 'lodash/sortBy';
+import DefinitionsList from './DefinitionsList';
+import { getSubCategoryName, getSubCategoryDescription } from '../utils';
+
+export default class SubCategoryDefinitionsList extends React.Component {
+ static propTypes = {
+ component: React.PropTypes.object,
+ settings: React.PropTypes.array.isRequired
+ };
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ render () {
+ const bySubCategory = groupBy(this.props.settings, setting => setting.definition.subCategory);
+ const subCategories = Object.keys(bySubCategory).map(key => ({
+ key,
+ name: getSubCategoryName(bySubCategory[key][0].definition.category, key),
+ description: getSubCategoryDescription(bySubCategory[key][0].definition.category, key)
+ }));
+ const sortedSubCategories = sortBy(subCategories, subCategory => subCategory.name.toLowerCase());
+
+ return (
+ <ul className="settings-sub-categories-list">
+ {sortedSubCategories.map(subCategory => (
+ <li key={subCategory.key}>
+ <h2 className="settings-sub-category-name">{subCategory.name}</h2>
+ {subCategory.description != null && (
+ <div className="settings-sub-category-description markdown">
+ {subCategory.description}
+ </div>
+ )}
+ <DefinitionsList settings={bySubCategory[subCategory.key]} component={this.props.component}/>
+ </li>
+ ))}
+ </ul>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 PropertySetInput from './PropertySetInput';
+import MultiValueInput from './MultiValueInput';
+import PrimitiveInput from './PrimitiveInput';
+import { TYPE_PROPERTY_SET } from '../../constants';
+
+export default class Input extends React.Component {
+ static propTypes = {
+ setting: React.PropTypes.object.isRequired,
+ value: React.PropTypes.any,
+ onChange: React.PropTypes.func.isRequired
+ };
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ render () {
+ const { definition } = this.props.setting;
+
+ if (definition.multiValues) {
+ return <MultiValueInput {...this.props}/>;
+ }
+
+ if (definition.type === TYPE_PROPERTY_SET) {
+ return <PropertySetInput {...this.props}/>;
+ }
+
+ return <PrimitiveInput {...this.props}/>;
+ }
+}
--- /dev/null
+/*
+ * 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 Toggle from '../../../../components/controls/Toggle';
+import { defaultInputPropTypes } from '../../propTypes';
+import { translate } from '../../../../helpers/l10n';
+
+export default class InputForBoolean extends React.Component {
+ static propTypes = {
+ ...defaultInputPropTypes,
+ value: React.PropTypes.oneOfType([React.PropTypes.bool, React.PropTypes.string])
+ };
+
+ render () {
+ const hasValue = this.props.value != null;
+ const displayedValue = hasValue ? this.props.value : false;
+
+ return (
+ <div className="display-inline-block text-top">
+ <Toggle
+ name={this.props.name}
+ value={displayedValue}
+ onChange={this.props.onChange}/>
+
+ {!hasValue && (
+ <span className="spacer-left note">{translate('settings.not_set')}</span>
+ )}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 SimpleInput from './SimpleInput';
+
+export default class InputForNumber extends React.Component {
+ render () {
+ return (
+ <SimpleInput {...this.props} className="input-small" type="text"/>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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';
+import { defaultInputPropTypes } from '../../propTypes';
+
+export default class InputForPassword extends React.Component {
+ static propTypes = defaultInputPropTypes;
+
+ state = {
+ changing: false
+ };
+
+ handleChangeClick (e) {
+ e.preventDefault();
+ e.target.blur();
+ this.setState({ changing: true });
+ }
+
+ handleCancelChangeClick (e) {
+ e.preventDefault();
+ e.target.blur();
+ this.setState({ changing: false });
+ }
+
+ handleFormSubmit (e) {
+ e.preventDefault();
+ this.props.onChange(this.refs.input.value);
+ this.setState({ changing: false });
+ }
+
+ renderInput () {
+ return (
+ <div>
+ <form onSubmit={e => this.handleFormSubmit(e)}>
+ <input className="hidden" type="password"/>
+ <input
+ ref="input"
+ name={this.props.name}
+ className="input-large text-top"
+ type="password"
+ autoFocus={true}
+ autoComplete={false}/>
+ <button className="spacer-left">{translate('set')}</button>
+ <a className="spacer-left" href="#" onClick={e => this.handleCancelChangeClick(e)}>
+ {translate('cancel')}
+ </a>
+ </form>
+ </div>
+ );
+ }
+
+ render () {
+ if (this.state.changing) {
+ return this.renderInput();
+ }
+
+ const hasValue = !!this.props.value;
+
+ return (
+ <div>
+ {hasValue && (
+ <i className="big-spacer-right icon-lock icon-gray"/>
+ )}
+
+ <button onClick={e => this.handleChangeClick(e)}>
+ {hasValue ? translate('change_verb') : translate('set')}
+ </button>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 Select from 'react-select';
+import { defaultInputPropTypes } from '../../propTypes';
+
+export default class InputForSingleSelectList extends React.Component {
+ static propTypes = {
+ ...defaultInputPropTypes,
+ options: React.PropTypes.arrayOf(React.PropTypes.string).isRequired
+ };
+
+ handleInputChange (option) {
+ this.props.onChange(option.value);
+ }
+
+ render () {
+ const options = this.props.options.map(option => ({
+ label: option,
+ value: option
+ }));
+
+ return (
+ <Select
+ name={this.props.name}
+ className="input-large"
+ options={options}
+ clearable={false}
+ value={this.props.value}
+ onChange={option => this.handleInputChange(option)}/>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 SimpleInput from './SimpleInput';
+
+export default class InputForString extends React.Component {
+ render () {
+ return (
+ <SimpleInput {...this.props} className="input-large" type="text"/>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 { defaultInputPropTypes } from '../../propTypes';
+
+export default class InputForText extends React.Component {
+ static propTypes = defaultInputPropTypes;
+
+ handleInputChange (e) {
+ this.props.onChange(e.target.value);
+ }
+
+ render () {
+ return (
+ <textarea
+ name={this.props.name}
+ className="input-super-large text-top"
+ rows="5"
+ value={this.props.value}
+ onChange={e => this.handleInputChange(e)}/>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 PrimitiveInput from './PrimitiveInput';
+import { getEmptyValue } from '../../utils';
+
+export default class MultiValueInput extends React.Component {
+ static propTypes = {
+ setting: React.PropTypes.object.isRequired,
+ value: React.PropTypes.array,
+ onChange: React.PropTypes.func.isRequired
+ };
+
+ ensureValue () {
+ return this.props.value || [];
+ }
+
+ handleSingleInputChange (index, value) {
+ const newValue = [...this.ensureValue()];
+ newValue.splice(index, 1, value);
+ this.props.onChange(newValue);
+ }
+
+ handleDeleteValue (e, index) {
+ e.preventDefault();
+ e.target.blur();
+
+ const newValue = [...this.ensureValue()];
+ newValue.splice(index, 1);
+ this.props.onChange(newValue);
+ }
+
+ prepareSetting () {
+ const { setting } = this.props;
+ const newDefinition = { ...setting.definition, multiValues: false };
+ return {
+ ...setting,
+ definition: newDefinition,
+ values: undefined
+ };
+ }
+
+ renderInput (value, index, isLast) {
+ return (
+ <li key={index} className="spacer-bottom">
+ <PrimitiveInput
+ setting={this.prepareSetting()}
+ value={value}
+ onChange={this.handleSingleInputChange.bind(this, index)}/>
+
+ {!isLast && (
+ <div className="display-inline-block spacer-left">
+ <button className="js-remove-value button-clean" onClick={e => this.handleDeleteValue(e, index)}>
+ <i className="icon-delete"/>
+ </button>
+ </div>
+ )}
+ </li>
+ );
+ }
+
+ render () {
+ const displayedValue = [...this.ensureValue(), ...getEmptyValue(this.props.setting.definition)];
+
+ return (
+ <div>
+ <ul>
+ {displayedValue.map((value, index) => this.renderInput(value, index, index === displayedValue.length - 1))}
+ </ul>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 InputForString from './InputForString';
+import InputForText from './InputForText';
+import InputForPassword from './InputForPassword';
+import InputForBoolean from './InputForBoolean';
+import InputForNumber from './InputForNumber';
+import InputForSingleSelectList from './InputForSingleSelectList';
+import { getUniqueName, isDefaultOrInherited } from '../../utils';
+import * as types from '../../constants';
+
+const typeMapping = {
+ [types.TYPE_STRING]: InputForString,
+ [types.TYPE_TEXT]: InputForText,
+ [types.TYPE_PASSWORD]: InputForPassword,
+ [types.TYPE_BOOLEAN]: InputForBoolean,
+ [types.TYPE_INTEGER]: InputForNumber,
+ [types.TYPE_LONG]: InputForNumber,
+ [types.TYPE_FLOAT]: InputForNumber
+};
+
+export default class PrimitiveInput extends React.Component {
+ static propTypes = {
+ setting: React.PropTypes.object.isRequired,
+ value: React.PropTypes.any,
+ onChange: React.PropTypes.func.isRequired
+ };
+
+ render () {
+ const { setting, value, onChange, ...other } = this.props;
+ const { definition } = setting;
+
+ const name = getUniqueName(definition);
+
+ if (definition.type === types.TYPE_SINGLE_SELECT_LIST) {
+ return (
+ <InputForSingleSelectList
+ name={name}
+ value={value}
+ isDefault={isDefaultOrInherited(setting)}
+ options={definition.options}
+ onChange={onChange}
+ {...other}/>
+ );
+ }
+
+ const InputComponent = typeMapping[definition.type] || InputForString;
+ return (
+ <InputComponent
+ name={name}
+ value={value}
+ isDefault={isDefaultOrInherited(setting)}
+ onChange={onChange}
+ {...other}/>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 PrimitiveInput from './PrimitiveInput';
+import { getEmptyValue, getUniqueName } from '../../utils';
+
+export default class PropertySetInput extends React.Component {
+ static propTypes = {
+ setting: React.PropTypes.object.isRequired,
+ value: React.PropTypes.array,
+ onChange: React.PropTypes.func.isRequired
+ };
+
+ ensureValue () {
+ return this.props.value || [];
+ }
+
+ getFieldName (field) {
+ return getUniqueName(this.props.setting.definition, field.key);
+ }
+
+ handleDeleteValue (e, index) {
+ e.preventDefault();
+ e.target.blur();
+
+ const newValue = [...this.ensureValue()];
+ newValue.splice(index, 1);
+ this.props.onChange(newValue);
+ }
+
+ handleInputChange (index, fieldKey, value) {
+ const emptyValue = getEmptyValue(this.props.setting.definition)[0];
+ const newValue = [...this.ensureValue()];
+ const newFields = { ...emptyValue, ...newValue[index], [fieldKey]: value };
+ newValue.splice(index, 1, newFields);
+ return this.props.onChange(newValue);
+ }
+
+ renderFields (fieldValues, index, isLast) {
+ const { setting } = this.props;
+
+ return (
+ <tr key={index}>
+ {setting.definition.fields.map(field => (
+ <td key={field.key}>
+ <PrimitiveInput
+ name={this.getFieldName(field)}
+ setting={{ definition: field, value: fieldValues[field.key] }}
+ value={fieldValues[field.key]}
+ onChange={this.handleInputChange.bind(this, index, field.key)}/>
+ </td>
+ ))}
+ <td className="thin nowrap">
+ {!isLast && (
+ <button className="js-remove-value button-link" onClick={e => this.handleDeleteValue(e, index)}>
+ <i className="icon-delete"/>
+ </button>
+ )}
+ </td>
+ </tr>
+ );
+ }
+
+ render () {
+ const { setting } = this.props;
+
+ const displayedValue = [...this.ensureValue(), ...getEmptyValue(this.props.setting.definition)];
+
+ return (
+ <div>
+ <table className="data zebra-hover no-outer-padding" style={{ width: 'auto', minWidth: 480, marginTop: -12 }}>
+ <thead>
+ <tr>
+ {setting.definition.fields.map(field => (
+ <th key={field.key}>
+ {field.name}
+ {field.description != null && (
+ <span className="spacer-top small">{field.description}</span>
+ )}
+ </th>
+ ))}
+ <th> </th>
+ </tr>
+ </thead>
+ <tbody>
+ {displayedValue.map((fieldValues, index) =>
+ this.renderFields(fieldValues, index, index === displayedValue.length - 1))}
+ </tbody>
+ </table>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 { defaultInputPropTypes } from '../../propTypes';
+
+export default class SimpleInput extends React.Component {
+ static propTypes = {
+ ...defaultInputPropTypes,
+ value: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]),
+ type: React.PropTypes.string.isRequired,
+ className: React.PropTypes.string.isRequired
+ };
+
+ handleInputChange (e) {
+ this.props.onChange(e.target.value);
+ }
+
+ render () {
+ return (
+ <input
+ name={this.props.name}
+ className={this.props.className + ' text-top'}
+ type={this.props.type}
+ value={this.props.value}
+ onChange={e => this.handleInputChange(e)}/>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 { expect } from 'chai';
+import { shallow } from 'enzyme';
+import sinon from 'sinon';
+import Input from '../Input';
+import PrimitiveInput from '../PrimitiveInput';
+import MultiValueInput from '../MultiValueInput';
+import PropertySetInput from '../PropertySetInput';
+import { TYPE_STRING, TYPE_PROPERTY_SET } from '../../../constants';
+
+describe('Settings :: Inputs :: Input', () => {
+ it('should render PrimitiveInput', () => {
+ const setting = { definition: { key: 'example', type: TYPE_STRING } };
+ const onChange = sinon.spy();
+ const input = shallow(<Input setting={setting} value="foo" onChange={onChange}/>).find(PrimitiveInput);
+ expect(input).to.have.length(1);
+ expect(input.prop('setting')).to.equal(setting);
+ expect(input.prop('value')).to.equal('foo');
+ expect(input.prop('onChange')).to.equal(onChange);
+ });
+
+ it('should render MultiValueInput', () => {
+ const setting = { definition: { key: 'example', type: TYPE_STRING, multiValues: true } };
+ const onChange = sinon.spy();
+ const input = shallow(<Input setting={setting} value="foo" onChange={onChange}/>).find(MultiValueInput);
+ expect(input).to.have.length(1);
+ expect(input.prop('setting')).to.equal(setting);
+ expect(input.prop('value')).to.equal('foo');
+ expect(input.prop('onChange')).to.equal(onChange);
+ });
+
+ it('should render PropertySetInput', () => {
+ const setting = { definition: { key: 'example', type: TYPE_PROPERTY_SET, fields: [] } };
+ const onChange = sinon.spy();
+ const input = shallow(<Input setting={setting} value="foo" onChange={onChange}/>).find(PropertySetInput);
+ expect(input).to.have.length(1);
+ expect(input.prop('setting')).to.equal(setting);
+ expect(input.prop('value')).to.equal('foo');
+ expect(input.prop('onChange')).to.equal(onChange);
+ });
+});
--- /dev/null
+/*
+ * 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 { expect } from 'chai';
+import { shallow } from 'enzyme';
+import sinon from 'sinon';
+import InputForBoolean from '../InputForBoolean';
+import Toggle from '../../../../../components/controls/Toggle';
+
+describe('Settings :: Inputs :: InputForBoolean', () => {
+ it('should render Toggle', () => {
+ const onChange = sinon.spy();
+ const toggle = shallow(
+ <InputForBoolean
+ name="foo"
+ value={true}
+ isDefault={false}
+ onChange={onChange}/>
+ ).find(Toggle);
+ expect(toggle).to.have.length(1);
+ expect(toggle.prop('name')).to.equal('foo');
+ expect(toggle.prop('value')).to.equal(true);
+ expect(toggle.prop('onChange')).to.be.a('function');
+ });
+
+ it('should render Toggle without value', () => {
+ const onChange = sinon.spy();
+ const input = shallow(
+ <InputForBoolean
+ name="foo"
+ isDefault={false}
+ onChange={onChange}/>
+ );
+ const toggle = input.find(Toggle);
+ expect(toggle).to.have.length(1);
+ expect(toggle.prop('name')).to.equal('foo');
+ expect(toggle.prop('value')).to.equal(false);
+ expect(toggle.prop('onChange')).to.be.a('function');
+ expect(input.find('.note')).to.have.length(1);
+ });
+
+ it('should call onChange', () => {
+ const onChange = sinon.spy();
+ const input = shallow(
+ <InputForBoolean
+ name="foo"
+ value={true}
+ isDefault={false}
+ onChange={onChange}/>
+ );
+ const toggle = input.find(Toggle);
+ expect(toggle).to.have.length(1);
+ expect(toggle.prop('onChange')).to.be.a('function');
+
+ toggle.prop('onChange')(false);
+
+ expect(onChange.called).to.equal(true);
+ expect(onChange.lastCall.args).to.deep.equal([false]);
+ });
+});
--- /dev/null
+/*
+ * 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 { expect } from 'chai';
+import { shallow } from 'enzyme';
+import sinon from 'sinon';
+import InputForNumber from '../InputForNumber';
+import SimpleInput from '../SimpleInput';
+
+describe('Settings :: Inputs :: InputForNumber', () => {
+ it('should render SimpleInput', () => {
+ const onChange = sinon.spy();
+ const simpleInput = shallow(
+ <InputForNumber
+ name="foo"
+ value={17}
+ isDefault={false}
+ onChange={onChange}/>
+ ).find(SimpleInput);
+ expect(simpleInput).to.have.length(1);
+ expect(simpleInput.prop('name')).to.equal('foo');
+ expect(simpleInput.prop('value')).to.equal(17);
+ expect(simpleInput.prop('type')).to.equal('text');
+ expect(simpleInput.prop('onChange')).to.be.a('function');
+ });
+});
--- /dev/null
+/*
+ * 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 { expect } from 'chai';
+import { shallow, mount } from 'enzyme';
+import sinon from 'sinon';
+import InputForPassword from '../InputForPassword';
+import { click, submit } from '../../../../../../../../tests/utils';
+
+describe('Settings :: Inputs :: InputForPassword', () => {
+ it('should render lock icon, but no form', () => {
+ const onChange = sinon.spy();
+ const input = shallow(
+ <InputForPassword
+ name="foo"
+ value="bar"
+ isDefault={false}
+ onChange={onChange}/>
+ );
+ expect(input.find('.icon-lock')).to.have.length(1);
+ expect(input.find('form')).to.have.length(0);
+ });
+
+ it('should open form', () => {
+ const onChange = sinon.spy();
+ const input = shallow(
+ <InputForPassword
+ name="foo"
+ value="bar"
+ isDefault={false}
+ onChange={onChange}/>
+ );
+ const button = input.find('button');
+ expect(button).to.have.length(1);
+
+ click(button);
+ expect(input.find('form')).to.have.length(1);
+ });
+
+ it('should close form', () => {
+ const onChange = sinon.spy();
+ const input = shallow(
+ <InputForPassword
+ name="foo"
+ value="bar"
+ isDefault={false}
+ onChange={onChange}/>
+ );
+ const button = input.find('button');
+ expect(button).to.have.length(1);
+
+ click(button);
+ expect(input.find('form')).to.have.length(1);
+
+ click(input.find('form').find('a'));
+ expect(input.find('form')).to.have.length(0);
+ });
+
+ it('should set value', () => {
+ const onChange = sinon.stub().returns(Promise.resolve());
+ const input = mount(
+ <InputForPassword
+ name="foo"
+ value="bar"
+ isDefault={false}
+ onChange={onChange}/>
+ );
+ const button = input.find('button');
+ expect(button).to.have.length(1);
+
+ click(button);
+ const form = input.find('form');
+ expect(form).to.have.length(1);
+
+ input.ref('input').value = 'secret';
+ submit(form);
+
+ expect(onChange.called).to.equal(true);
+ });
+});
--- /dev/null
+/*
+ * 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 { expect } from 'chai';
+import { shallow } from 'enzyme';
+import sinon from 'sinon';
+import Select from 'react-select';
+import InputForSingleSelectList from '../InputForSingleSelectList';
+
+describe('Settings :: Inputs :: InputForSingleSelectList', () => {
+ it('should render Select', () => {
+ const onChange = sinon.spy();
+ const select = shallow(
+ <InputForSingleSelectList
+ name="foo"
+ value="bar"
+ options={['foo', 'bar', 'baz']}
+ isDefault={false}
+ onChange={onChange}/>
+ ).find(Select);
+ expect(select).to.have.length(1);
+ expect(select.prop('name')).to.equal('foo');
+ expect(select.prop('value')).to.equal('bar');
+ expect(select.prop('options')).to.deep.equal([
+ { value: 'foo', label: 'foo' },
+ { value: 'bar', label: 'bar' },
+ { value: 'baz', label: 'baz' }
+ ]);
+ expect(select.prop('onChange')).to.be.a('function');
+ });
+
+ it('should call onChange', () => {
+ const onChange = sinon.spy();
+ const select = shallow(
+ <InputForSingleSelectList
+ name="foo"
+ value="bar"
+ options={['foo', 'bar', 'baz']}
+ isDefault={false}
+ onChange={onChange}/>
+ ).find(Select);
+ expect(select).to.have.length(1);
+ expect(select.prop('onChange')).to.be.a('function');
+
+ select.prop('onChange')({ value: 'baz', label: 'baz' });
+ expect(onChange.called).to.equal(true);
+ expect(onChange.lastCall.args).to.deep.equal(['baz']);
+ });
+});
--- /dev/null
+/*
+ * 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 { expect } from 'chai';
+import { shallow } from 'enzyme';
+import sinon from 'sinon';
+import InputForString from '../InputForString';
+import SimpleInput from '../SimpleInput';
+
+describe('Settings :: Inputs :: InputForString', () => {
+ it('should render SimpleInput', () => {
+ const onChange = sinon.spy();
+ const simpleInput = shallow(
+ <InputForString
+ name="foo"
+ value="bar"
+ isDefault={false}
+ onChange={onChange}/>
+ ).find(SimpleInput);
+ expect(simpleInput).to.have.length(1);
+ expect(simpleInput.prop('name')).to.equal('foo');
+ expect(simpleInput.prop('value')).to.equal('bar');
+ expect(simpleInput.prop('type')).to.equal('text');
+ expect(simpleInput.prop('onChange')).to.be.a('function');
+ });
+});
--- /dev/null
+/*
+ * 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 { expect } from 'chai';
+import { shallow } from 'enzyme';
+import sinon from 'sinon';
+import InputForText from '../InputForText';
+import { change } from '../../../../../../../../tests/utils';
+
+describe('Settings :: Inputs :: InputForText', () => {
+ it('should render textarea', () => {
+ const onChange = sinon.spy();
+ const textarea = shallow(
+ <InputForText
+ name="foo"
+ value="bar"
+ isDefault={false}
+ onChange={onChange}/>
+ ).find('textarea');
+ expect(textarea).to.have.length(1);
+ expect(textarea.prop('name')).to.equal('foo');
+ expect(textarea.prop('value')).to.equal('bar');
+ expect(textarea.prop('onChange')).to.be.a('function');
+ });
+
+ it('should call onChange', () => {
+ const onChange = sinon.spy();
+ const textarea = shallow(
+ <InputForText
+ name="foo"
+ value="bar"
+ isDefault={false}
+ onChange={onChange}/>
+ ).find('textarea');
+ expect(textarea).to.have.length(1);
+ expect(textarea.prop('onChange')).to.be.a('function');
+
+ change(textarea, 'qux');
+
+ expect(onChange.called).to.equal(true);
+ expect(onChange.lastCall.args).to.deep.equal(['qux']);
+ });
+});
--- /dev/null
+/*
+ * 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 { expect } from 'chai';
+import { shallow, mount } from 'enzyme';
+import sinon from 'sinon';
+import MultiValueInput from '../MultiValueInput';
+import InputForString from '../InputForString';
+import { click, change } from '../../../../../../../../tests/utils';
+
+const definition = { multiValues: true };
+
+const assertValues = (inputs, values) => {
+ values.forEach((value, index) => {
+ const input = inputs.at(index);
+ expect(input.prop('value')).to.equal(value);
+ });
+};
+
+describe('Settings :: Inputs :: MultiValueInput', () => {
+ it('should render one value', () => {
+ const multiValueInput = mount(
+ <MultiValueInput
+ setting={{ definition }}
+ value={['foo']}
+ onChange={sinon.stub()}/>
+ );
+ const stringInputs = multiValueInput.find(InputForString);
+ expect(stringInputs).to.have.length(1 + 1);
+ assertValues(stringInputs, ['foo', '']);
+ });
+
+ it('should render several values', () => {
+ const multiValueInput = mount(
+ <MultiValueInput
+ setting={{ definition }}
+ value={['foo', 'bar', 'baz']}
+ onChange={sinon.stub()}/>
+ );
+ const stringInputs = multiValueInput.find(InputForString);
+ expect(stringInputs).to.have.length(3 + 1);
+ assertValues(stringInputs, ['foo', 'bar', 'baz', '']);
+ });
+
+ it('should remove value', () => {
+ const onChange = sinon.spy();
+ const multiValueInput = mount(
+ <MultiValueInput
+ setting={{ definition }}
+ value={['foo', 'bar', 'baz']}
+ onChange={onChange}/>
+ );
+
+ click(multiValueInput.find('.js-remove-value').at(1));
+ expect(onChange.called).to.equal(true);
+ expect(onChange.lastCall.args).to.deep.equal([['foo', 'baz']]);
+ });
+
+ it('should change existing value', () => {
+ const onChange = sinon.spy();
+ const multiValueInput = mount(
+ <MultiValueInput
+ setting={{ definition }}
+ value={['foo', 'bar', 'baz']}
+ onChange={onChange}/>
+ );
+
+ change(multiValueInput.find(InputForString).at(1).find('input'), 'qux');
+ expect(onChange.called).to.equal(true);
+ expect(onChange.lastCall.args).to.deep.equal([['foo', 'qux', 'baz']]);
+ });
+
+ it('should add new value', () => {
+ const onChange = sinon.spy();
+ const multiValueInput = mount(
+ <MultiValueInput
+ setting={{ definition }}
+ value={['foo']}
+ onChange={onChange}/>
+ );
+
+ change(multiValueInput.find(InputForString).at(1).find('input'), 'bar');
+ expect(onChange.called).to.equal(true);
+ expect(onChange.lastCall.args).to.deep.equal([['foo', 'bar']]);
+ });
+});
--- /dev/null
+/*
+ * 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 { expect } from 'chai';
+import { shallow } from 'enzyme';
+import sinon from 'sinon';
+import SimpleInput from '../SimpleInput';
+import { change } from '../../../../../../../../tests/utils';
+
+describe('Settings :: Inputs :: SimpleInput', () => {
+ it('should render input', () => {
+ const onChange = sinon.spy();
+ const input = shallow(
+ <SimpleInput
+ type="text"
+ className="input-large"
+ name="foo"
+ value="bar"
+ isDefault={false}
+ onChange={onChange}/>
+ ).find('input');
+ expect(input).to.have.length(1);
+ expect(input.prop('type')).to.equal('text');
+ expect(input.prop('className')).to.include('input-large');
+ expect(input.prop('name')).to.equal('foo');
+ expect(input.prop('value')).to.equal('bar');
+ expect(input.prop('onChange')).to.be.a('function');
+ });
+
+ it('should call onChange', () => {
+ const onChange = sinon.spy();
+ const input = shallow(
+ <SimpleInput
+ type="text"
+ className="input-large"
+ name="foo"
+ value="bar"
+ isDefault={false}
+ onChange={onChange}/>
+ ).find('input');
+ expect(input).to.have.length(1);
+ expect(input.prop('onChange')).to.be.a('function');
+
+ change(input, 'qux');
+
+ expect(onChange.called).to.equal(true);
+ expect(onChange.lastCall.args).to.deep.equal(['qux']);
+ });
+});
--- /dev/null
+/*
+ * 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.
+ */
+export const TYPE_STRING = 'STRING';
+export const TYPE_TEXT = 'TEXT';
+export const TYPE_PASSWORD = 'PASSWORD';
+export const TYPE_BOOLEAN = 'BOOLEAN';
+export const TYPE_FLOAT = 'FLOAT';
+export const TYPE_INTEGER = 'INTEGER';
+export const TYPE_LONG = 'LONG';
+export const TYPE_SINGLE_SELECT_LIST = 'SINGLE_SELECT_LIST';
+export const TYPE_PROPERTY_SET = 'PROPERTY_SET';
--- /dev/null
+/*
+ * 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 { PropTypes } from 'react';
+
+export const defaultInputPropTypes = {
+ name: PropTypes.string.isRequired,
+ value: PropTypes.any,
+ isDefault: PropTypes.bool.isRequired,
+ onChange: PropTypes.func.isRequired
+};
--- /dev/null
+/*
+ * 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 { getDefinitions, getValues, setSettingValue, resetSettingValue } from '../../../api/settings';
+import { receiveValues } from './values/actions';
+import { receiveDefinitions } from './definitions/actions';
+import { startLoading, stopLoading } from './settingsPage/loading/actions';
+import { parseError } from '../../code/utils';
+import { addGlobalErrorMessage, closeAllGlobalMessages } from '../../../components/store/globalMessages';
+import { passValidation, failValidation } from './settingsPage/validationMessages/actions';
+import { cancelChange } from './settingsPage/changedValues/actions';
+import { getDefinition, getChangedValue } from './rootReducer';
+
+export const fetchSettings = componentKey => dispatch => {
+ return getDefinitions(componentKey)
+ .then(definitions => {
+ dispatch(receiveDefinitions(definitions));
+ const keys = definitions.map(definition => definition.key).join();
+ return getValues(keys, componentKey);
+ })
+ .then(settings => {
+ dispatch(receiveValues(settings));
+ dispatch(closeAllGlobalMessages());
+ })
+ .catch(e => parseError(e).then(message => dispatch(addGlobalErrorMessage(message))));
+};
+
+export const saveValue = (key, componentKey) => (dispatch, getState) => {
+ dispatch(startLoading(key));
+
+ const state = getState();
+ const definition = getDefinition(state, key);
+ const value = getChangedValue(state, key);
+
+ return setSettingValue(definition, value, componentKey)
+ .then(() => getValues(key, componentKey))
+ .then(values => {
+ dispatch(receiveValues(values));
+ dispatch(cancelChange(key));
+ dispatch(passValidation(key));
+ dispatch(stopLoading(key));
+ })
+ .catch(e => {
+ dispatch(stopLoading(key));
+ parseError(e).then(message => dispatch(failValidation(key, message)));
+ return Promise.reject();
+ });
+};
+
+export const resetValue = (key, componentKey) => dispatch => {
+ dispatch(startLoading(key));
+
+ return resetSettingValue(key, componentKey)
+ .then(() => getValues(key, componentKey))
+ .then(values => {
+ if (values.length > 0) {
+ dispatch(receiveValues(values));
+ } else {
+ dispatch(receiveValues([{ key }]));
+ }
+ dispatch(passValidation(key));
+ dispatch(stopLoading(key));
+ })
+ .catch(e => {
+ dispatch(stopLoading(key));
+ parseError(e).then(message => dispatch(failValidation(key, message)));
+ return Promise.reject();
+ });
+};
--- /dev/null
+/*
+ * 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.
+ */
+export const RECEIVE_DEFINITIONS = 'RECEIVE_DEFINITIONS';
+
+/**
+ * Receive definitions action creator
+ * @param {Array} definitions
+ * @returns {Object}
+ */
+export const receiveDefinitions = definitions => ({
+ type: RECEIVE_DEFINITIONS,
+ definitions
+});
--- /dev/null
+/*
+ * 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 sortBy from 'lodash/sortBy';
+import uniqBy from 'lodash/uniqBy';
+import { RECEIVE_DEFINITIONS } from './actions';
+import { DEFAULT_CATEGORY, getCategoryName } from '../../utils';
+
+const reducer = (state = {}, action = {}) => {
+ if (action.type === RECEIVE_DEFINITIONS) {
+ const definitionsByKey = keyBy(action.definitions, 'key');
+ return { ...state, ...definitionsByKey };
+ }
+
+ return state;
+};
+
+export default reducer;
+
+export const getDefinition = (state, key) => state[key];
+
+export const getAllDefinitions = state => Object.values(state);
+
+export const getDefinitionsForCategory = (state, category) =>
+ getAllDefinitions(state).filter(definition => definition.category.toLowerCase() === category.toLowerCase());
+
+export const getAllCategories = state => uniqBy(
+ getAllDefinitions(state).map(definition => definition.category),
+ category => category.toLowerCase());
+
+export const getDefaultCategory = state => {
+ const categories = getAllCategories(state);
+ if (categories.includes(DEFAULT_CATEGORY)) {
+ return DEFAULT_CATEGORY;
+ } else {
+ const sortedCategories = sortBy(categories, category => getCategoryName(category).toLowerCase());
+ return sortedCategories[0];
+ }
+};
--- /dev/null
+/*
+ * 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 { combineReducers } from 'redux';
+import definitions, * as fromDefinitions from './definitions/reducer';
+import values, * as fromValues from './values/reducer';
+import settingsPage, * as fromSettingsPage from './settingsPage/reducer';
+import globalMessages, * as fromGlobalMessages from '../../../components/store/globalMessages';
+
+const rootReducer = combineReducers({
+ definitions,
+ values,
+ settingsPage,
+ globalMessages
+});
+
+export default rootReducer;
+
+export const getDefinition = (state, key) => fromDefinitions.getDefinition(state.definitions, key);
+
+export const getAllCategories = state => fromDefinitions.getAllCategories(state.definitions);
+
+export const getDefaultCategory = state => fromDefinitions.getDefaultCategory(state.definitions);
+
+export const getValue = (state, key) => fromValues.getValue(state.values, key);
+
+export const getSettingsForCategory = (state, category) =>
+ fromDefinitions.getDefinitionsForCategory(state.definitions, category).map(definition => ({
+ ...getValue(state, definition.key),
+ definition
+ }));
+
+export const getChangedValue = (state, key) => fromSettingsPage.getChangedValue(state.settingsPage, key);
+
+export const isLoading = (state, key) => fromSettingsPage.isLoading(state.settingsPage, key);
+
+export const getValidationMessage = (state, key) => fromSettingsPage.getValidationMessage(state.settingsPage, key);
+
+export const getGlobalMessages = state => fromGlobalMessages.getGlobalMessages(state.globalMessages);
--- /dev/null
+/*
+ * 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.
+ */
+export const CHANGE_VALUE = 'settingsPage/CHANGE_VALUE';
+
+export const changeValue = (key, value) => ({
+ type: CHANGE_VALUE,
+ key,
+ value
+});
+
+export const CANCEL_CHANGE = 'settingsPage/CANCEL_CHANGE';
+
+export const cancelChange = key => ({
+ type: CANCEL_CHANGE,
+ key
+});
--- /dev/null
+/*
+ * 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 omit from 'lodash/omit';
+import { CHANGE_VALUE, CANCEL_CHANGE } from './actions';
+
+const reducer = (state = {}, action = {}) => {
+ if (action.type === CHANGE_VALUE) {
+ return { ...state, [action.key]: action.value };
+ }
+
+ if (action.type === CANCEL_CHANGE) {
+ return omit(state, action.key);
+ }
+
+ return state;
+};
+
+export default reducer;
+
+export const getChangedValue = (state, key) => state[key];
--- /dev/null
+/*
+ * 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.
+ */
+export const START_LOADING = 'settingsPage/START_LOADING';
+
+export const startLoading = key => ({
+ type: START_LOADING,
+ key
+});
+export const STOP_LOADING = 'settingsPage/STOP_LOADING';
+
+export const stopLoading = key => ({
+ type: STOP_LOADING,
+ key
+});
--- /dev/null
+/*
+ * 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 { START_LOADING, STOP_LOADING } from './actions';
+
+const reducer = (state = {}, action = {}) => {
+ if (action.type === START_LOADING) {
+ return { ...state, [action.key]: true };
+ }
+
+ if (action.type === STOP_LOADING) {
+ return { ...state, [action.key]: false };
+ }
+
+ return state;
+};
+
+export default reducer;
+
+export const isLoading = (state, key) => !!state[key];
--- /dev/null
+/*
+ * 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 { combineReducers } from 'redux';
+import changedValues, * as fromChangedValues from './changedValues/reducer';
+import validationMessages, * as fromValidationMessages from './validationMessages/reducer';
+import loading, * as fromLoading from './loading/reducer';
+
+export default combineReducers({
+ changedValues,
+ validationMessages,
+ loading
+});
+
+export const getChangedValue = (state, key) =>
+ fromChangedValues.getChangedValue(state.changedValues, key);
+
+export const getValidationMessage = (state, key) =>
+ fromValidationMessages.getValidationMessage(state.validationMessages, key);
+
+export const isLoading = (state, key) =>
+ fromLoading.isLoading(state.loading, key);
--- /dev/null
+/*
+ * 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.
+ */
+export const FAIL_VALIDATION = 'settingsPage/FAIL_VALIDATION';
+
+export const failValidation = (key, message) => ({
+ type: FAIL_VALIDATION,
+ key,
+ message
+});
+
+export const PASS_VALIDATION = 'settingsPage/PASS_VALIDATION';
+
+export const passValidation = key => ({
+ type: PASS_VALIDATION,
+ key
+});
--- /dev/null
+/*
+ * 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 { FAIL_VALIDATION, PASS_VALIDATION } from './actions';
+
+const reducer = (state = {}, action = {}) => {
+ if (action.type === FAIL_VALIDATION) {
+ return { ...state, [action.key]: action.message };
+ }
+
+ if (action.type === PASS_VALIDATION) {
+ return { ...state, [action.key]: null };
+ }
+
+ return state;
+};
+
+export default reducer;
+
+export const getValidationMessage = (state, key) => state[key];
--- /dev/null
+/*
+ * 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.
+ */
+export const RECEIVE_VALUES = 'RECEIVE_VALUES';
+
+/**
+ * Receive settings action creator
+ * @param {Array} settings
+ * @returns {Object}
+ */
+export const receiveValues = settings => ({
+ type: RECEIVE_VALUES,
+ settings
+});
--- /dev/null
+/*
+ * 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 { RECEIVE_VALUES } from './actions';
+
+const reducer = (state = {}, action = {}) => {
+ if (action.type === RECEIVE_VALUES) {
+ const settingsByKey = keyBy(action.settings, 'key');
+ return { ...state, ...settingsByKey };
+ }
+
+ return state;
+};
+
+export default reducer;
+
+export const getValue = (state, key) => state[key];
--- /dev/null
+.settings-layout {
+ display: flex;
+ justify-content: space-between;
+ align-items: stretch;
+ margin-bottom: 60px;
+}
+
+.settings-main {
+ position: relative;
+ z-index: 2;
+ flex-grow: 1;
+ padding: 15px 20px;
+ border: 1px solid #e6e6e6;
+ box-sizing: border-box;
+ background-color: #fff;
+}
+
+.settings-side {
+ position: relative;
+ z-index: 3;
+ width: 160px;
+ flex-shrink: 0;
+ padding: 10px 0;
+ box-sizing: border-box;
+ transform: translateX(1px);
+}
+
+.settings-menu {
+}
+
+.settings-menu > li {
+ margin-bottom: 4px;
+}
+
+.settings-menu > li > a {
+ display: block;
+ padding: 10px 10px;
+ line-height: 1.5;
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+ border: 1px solid #e6e6e6;
+ border-right: none;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ transition: color 0.3s ease, background-color 0.3s ease;
+}
+
+.settings-menu > li > a:hover,
+.settings-menu > li > a:focus,
+.settings-menu > li > a.active {
+ background-color: #fff;
+}
+
+.settings-menu > li > a.active {
+ color: #444;
+ cursor: default;
+}
+
+.settings-definitions-list > li + li {
+ margin-top: 30px;
+}
+
+.settings-definition {
+ display: flex;
+ align-items: stretch;
+}
+
+.settings-definition-changed {
+ margin: -10px -20px;
+ padding: 9px 20px;
+ border-top: 1px solid #faebcc;
+ border-bottom: 1px solid #faebcc;
+ background-color: #fcf8e3;
+}
+
+.settings-definition-left {
+ width: 330px;
+ padding-right: 30px;
+ box-sizing: border-box;
+}
+
+.settings-definition-right {
+ position: relative;
+ width: calc(100% - 330px);
+ padding-top: 35px;
+ box-sizing: border-box;
+}
+
+.settings-definition-name {
+ text-overflow: ellipsis;
+}
+
+.settings-definition-key {
+ line-height: 1.5;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.settings-definition-key:hover {
+ overflow: visible;
+}
+
+.settings-definition-state {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ line-height: 24px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.settings-definition-state-icon {
+ display: inline-block;
+ vertical-align: middle;
+ width: 24px;
+ height: 24px;
+ margin-right: 8px;
+ text-align: center;
+}
+
+.settings-definition-state-icon > .icon-alert-error,
+.settings-definition-state-icon > .spinner {
+ position: relative;
+ top: -2px;
+}
+
+.settings-definition-changes {
+ margin-top: 20px;
+ padding-top: 20px;
+ border-top: 1px dotted #e6e6e6;
+}
+
+.settings-sub-categories-list {
+}
+
+.settings-sub-categories-list > li {
+}
+
+.settings-sub-categories-list > li + li {
+ margin: 30px -20px 0;
+ padding: 30px 20px;
+ border-top: 1px solid #e6e6e6;
+}
+
+.settings-sub-category-name {
+ margin-bottom: 20px;
+ font-size: 16px;
+}
+
+.settings-sub-category-description {
+ margin-top: -15px;
+ margin-bottom: 20px;
+ color: #777;
+}
--- /dev/null
+/*
+ * 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 { translate, hasMessage } from '../../helpers/l10n';
+import { TYPE_PROPERTY_SET, TYPE_BOOLEAN, TYPE_SINGLE_SELECT_LIST, TYPE_PASSWORD } from './constants';
+
+export const DEFAULT_CATEGORY = 'general';
+
+export function getPropertyName (definition) {
+ const key = `property.${definition.key}.name`;
+ return hasMessage(key) ? translate(key) : definition.name;
+}
+
+export function getPropertyDescription (definition) {
+ const key = `property.${definition.key}.description`;
+ return hasMessage(key) ? translate(key) : definition.description;
+}
+
+export function getCategoryName (category) {
+ const key = `property.category.${category}`;
+ return hasMessage(key) ? translate(key) : category;
+}
+
+export function getSubCategoryName (category, subCategory) {
+ const key = `property.category.${category}.${subCategory}`;
+ return hasMessage(key) ? translate(key) : getCategoryName(subCategory);
+}
+
+export function getSubCategoryDescription (category, subCategory) {
+ const key = `property.category.${category}.${subCategory}.description`;
+ return hasMessage(key) ? translate(key) : null;
+}
+
+export function getUniqueName (definition, index = null) {
+ const indexSuffix = index != null ? `[${index}]` : '';
+ return `settings[${definition.key}]${indexSuffix}`;
+}
+
+export function getSettingValue (setting) {
+ if (setting.definition.multiValues) {
+ return setting.values;
+ } else if (setting.definition.type === TYPE_PROPERTY_SET) {
+ return setting.fieldValues;
+ } else {
+ return setting.value;
+ }
+}
+
+export function isEmptyValue (definition, value) {
+ if (value == null) {
+ return true;
+ } else if (definition.type === TYPE_BOOLEAN) {
+ return false;
+ } else {
+ return value.length === 0;
+ }
+}
+
+export function getEmptyValue (definition) {
+ if (definition.multiValues) {
+ return [getEmptyValue({ ...definition, multiValues: false })];
+ }
+
+ if (definition.type === TYPE_PROPERTY_SET) {
+ const value = {};
+ definition.fields.forEach(field => value[field.key] = getEmptyValue(field));
+ return [value];
+ }
+
+ if (definition.type === TYPE_BOOLEAN || definition.type === TYPE_SINGLE_SELECT_LIST) {
+ return null;
+ }
+
+ return '';
+}
+
+export function isDefaultOrInherited (setting) {
+ return !!setting.default || !!setting.inherited;
+}
+
+function getParentValue (setting) {
+ if (setting.definition.multiValues) {
+ return setting.parentValues;
+ } else if (setting.definition.type === TYPE_PROPERTY_SET) {
+ return setting.parentFieldValues;
+ } else {
+ return setting.parentValue;
+ }
+}
+
+/**
+ * Get and format the default value
+ * @param setting
+ * @returns {string}
+ */
+export function getDefaultValue (setting) {
+ const parentValue = getParentValue(setting);
+
+ if (parentValue == null) {
+ return translate('settings.default.no_value');
+ }
+
+ if (setting.definition.multiValues) {
+ return parentValue.length > 0 ?
+ parentValue.join(', ') :
+ translate('settings.default.no_value');
+ }
+
+ if (setting.definition.type === TYPE_PROPERTY_SET) {
+ return parentValue.length > 0 ?
+ translate('settings.default.complex_value') :
+ translate('settings.default.no_value');
+ }
+
+ if (setting.definition.type === TYPE_PASSWORD) {
+ return translate('settings.default.password');
+ }
+
+ if (setting.definition.type === TYPE_BOOLEAN) {
+ return parentValue ? translate('settings.boolean.true') : translate('settings.boolean.false');
+ }
+
+ return parentValue;
+}
--- /dev/null
+/*
+ * 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 classNames from 'classnames';
+import './styles.css';
+
+export default class Toggle extends React.Component {
+ static propTypes = {
+ value: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.bool]).isRequired,
+ name: React.PropTypes.string,
+ onChange: React.PropTypes.func
+ };
+
+ handleClick (e, value) {
+ e.preventDefault();
+ e.currentTarget.blur();
+ if (this.props.onChange) {
+ this.props.onChange(!value);
+ }
+ }
+
+ render () {
+ const { value } = this.props;
+ const booleanValue = typeof value === 'string' ? value === 'true' : value;
+
+ const className = classNames('boolean-toggle', { 'boolean-toggle-on': booleanValue });
+
+ return (
+ <button className={className} name={this.props.name} onClick={e => this.handleClick(e, booleanValue)}>
+ <div className="boolean-toggle-handle"/>
+ </button>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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 chai, { expect } from 'chai';
+import { shallow } from 'enzyme';
+import sinon from 'sinon';
+import sinonChai from 'sinon-chai';
+import React from 'react';
+import Toggle from '../Toggle';
+
+chai.use(sinonChai);
+
+function getSample (props) {
+ return (
+ <Toggle value={true} onChange={() => true} {...props}/>);
+}
+
+function click (element) {
+ return element.simulate('click', {
+ currentTarget: { blur () {} },
+ preventDefault () {}
+ });
+}
+
+describe('Components :: Controls :: Toggle', () => {
+ it('should render', () => {
+ const Toggle = shallow(getSample());
+ expect(Toggle.is('button')).to.equal(true);
+ });
+
+ it('should call onChange', () => {
+ const onChange = sinon.spy();
+ const Toggle = shallow(getSample({ onChange }));
+ click(Toggle);
+ expect(onChange).to.have.been.calledWith(false);
+ });
+});
.date-input-control-input:focus + .date-input-control-icon path {
fill: #4b9fd5;
}
+
+.boolean-toggle {
+ display: inline-block;
+ vertical-align: middle;
+ width: 48px;
+ height: 24px;
+ padding: 1px;
+ border: 1px solid #cdcdcd;
+ border-radius: 24px;
+ box-sizing: border-box;
+ background-color: #fff;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.boolean-toggle:hover {
+ background-color: #fff;
+}
+
+.boolean-toggle:focus {
+ border-color: #4b9fd5;
+ background-color: #f6f6f6;
+}
+
+.boolean-toggle-handle {
+ width: 20px;
+ height: 20px;
+ border: 1px solid #cdcdcd;
+ border-radius: 22px;
+ box-sizing: border-box;
+ background-color: #f6f6f6;
+ transition: transform 0.3s cubic-bezier(.87,-.41,.19,1.44), border 0.3s ease;
+}
+
+.boolean-toggle-on {
+ border-color: #236a97;
+ background-color: #236a97;
+}
+
+.boolean-toggle-on:hover {
+ background-color: #236a97;
+}
+
+.boolean-toggle-on:focus {
+ background-color: #236a97;
+}
+
+.boolean-toggle-on .boolean-toggle-handle {
+ border-color: #f6f6f6;
+ transform: translateX(24px);
+}
}
}
+export function hasMessage (...keys) {
+ const messageKey = keys.join('.');
+ return messages[messageKey] != null;
+}
+
function getCurrentLocale () {
return window.navigator.languages ? window.navigator.languages[0] : window.navigator.language;
}
* @returns {Promise}
*/
export function delay (response) {
- return new Promise(resolve => setTimeout(() => resolve(response), 3000));
+ return new Promise(resolve => setTimeout(() => resolve(response), 500));
}
left: 0;
line-height: @formControlHeight;
padding-left: 8px;
- padding-right: 8px;
+ padding-right: 24px;
position: absolute;
right: 0;
top: 0;
input[type=email],
input[type=search],
input[type=date],
+input[type=number],
textarea,
select {
border: 1px solid @darkGrey;
input[type=password],
input[type=email],
input[type=search],
-input[type=date] {
+input[type=date],
+input[type=number] {
height: @formControlHeight;
padding: 0 6px;
}
}
}
+.button-success,
+input[type="submit"].button-success {
+ border-color: @green;
+ color: @green;
+
+ &:hover, &:focus, &.active {
+ background: @green;
+ color: #fff;
+ }
+}
+
.button-clean,
.button-clean:hover,
.button-clean:focus {
.input-super-large {
width: 100%;
max-width: 300px;
+ min-width: 200px;
+}
+
+textarea.input-super-large {
+ max-width: 600px;
+ min-width: 300px;
}
em.mandatory {
display: inline-block;
vertical-align: middle;
font-size: 0;
+ white-space: nowrap;
& > li {
display: inline-block;
padding-bottom: 5px;
}
+table.data.no-outer-padding > thead > tr {
+ > th:first-child {
+ padding-left: 0
+ }
+
+ > th:last-child {
+ padding-right: 0
+ }
+}
+
+table.data.no-outer-padding > tbody > tr {
+ > td:first-child {
+ padding-left: 0
+ }
+
+ > td:last-child {
+ padding-right: 0
+ }
+}
+
.data thead tr.total {
background-color: #EFEFEF;
font-weight: normal;
redirect_to :overwrite_params => {:controller => :dashboard, :action => 'index'}
end
+ def settings
+ @project = get_current_project(params[:id])
+ end
+
def deletion
@project = get_current_project(params[:id])
end
:include => 'events', :order => 'snapshots.created_at DESC')
end
- def settings
- @resource = get_current_project(params[:id])
-
- if !java_facade.getResourceTypeBooleanProperty(@resource.qualifier, 'configurable')
- redirect_to :action => 'index', :id => params[:id]
- end
-
- @snapshot = @resource.last_snapshot
- definitions_per_category = java_facade.propertyDefinitions.propertiesByCategory(@resource.qualifier)
- processProperties(definitions_per_category)
- end
-
def update_version
snapshot=Snapshot.find(params[:sid], :include => 'project')
not_found("Snapshot not found") unless snapshot
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
class SettingsController < ApplicationController
-
SECTION=Navigation::SECTION_CONFIGURATION
-
- verify :method => :post, :only => %w(update), :redirect_to => {:action => :index}
- before_filter :admin_required, :only => %w(index)
+ before_filter :admin_required
def index
- load_properties()
- end
-
- def update
- resource_id = params[:resource_id]
- @resource = Project.by_key(resource_id) if resource_id
-
- access_denied if (@resource && !is_admin?(@resource))
- access_denied if (@resource.nil? && !is_admin?)
-
- load_properties()
-
- @updated_properties = {}
- update_properties(resource_id)
- update_property_sets(resource_id)
-
- render :partial => 'settings/properties'
- end
-
- private
-
- def update_properties(resource_id)
- (params[:settings] || []).each do |key, value|
- update_property(key, value, resource_id)
- end
- end
- def update_property_sets(resource_id)
- (params[:property_sets] || []).each do |key, set_keys|
- update_property_set(key, set_keys, params[key], resource_id, params[:auto_generate] && params[:auto_generate][key])
- end
end
-
- def update_property_set(key, set_keys, fields_hash, resource_id, auto_generate)
- if auto_generate
- max = (Time.now.to_f * 100000).to_i
- set_keys.each_with_index do |v, index|
- if v.blank?
- max += 1;
- set_keys[index] = max.to_s
- end
- end
- end
-
- set_key_values = {}
- fields_hash.each do |field_key, field_values|
- field_values.zip(set_keys).each do |field_value, set_key|
- set_key_values[set_key] ||= {}
- set_key_values[set_key][field_key] = field_value
- end
- end
-
- set_keys.reject! { |set_key| set_key.blank? || (auto_generate && set_key_values[set_key].values.all?(&:blank?)) }
-
- Property.transaction do
- # Delete only property sets that are no more existing
- condition = "prop_key LIKE '" + key + ".%' AND "
- set_keys.each {|set_key| condition += "prop_key NOT LIKE ('#{key + '.' + set_key + '.%'}') AND "}
- if resource_id
- condition += 'resource_id=' + resource_id
- else
- condition += 'resource_id IS NULL'
- end
- Property.delete_all(condition)
-
- update_property(key, set_keys, resource_id)
- set_keys.each do |set_key|
- update_property("#{key}.#{set_key}.key", set_key, resource_id) unless auto_generate
-
- set_key_values[set_key].each do |field, value|
- update_property("#{key}.#{set_key}.#{field}", value, resource_id)
- end
- end
- end
- end
-
- def update_property(key, value, resource_id)
- @updated_properties[key] = Property.set(key, value, resource_id)
- end
-
- def load_properties
- definitions_per_category = java_facade.propertyDefinitions.propertiesByCategory(@resource.nil? ? nil : @resource.qualifier)
- processProperties(definitions_per_category)
- end
-
end
-<div class="page" name="settings">
- <div class="yui-g widget" id="widget_plugins">
- <%= render :partial => 'settings/settings' %>
- </div>
-</div>
+<% content_for :extra_script do %>
+<script src="<%= ApplicationController.root_context -%>/js/bundles/settings.js?v=<%= sonar_version -%>"></script>
+<% end %>
+++ /dev/null
-<% updated_property = @updated_properties[key] if @updated_properties -%>
-
-<% if updated_property && !updated_property.valid? -%>
- <div class="error"><%= updated_property.validation_error_message -%></div>
-<% end -%>
+++ /dev/null
-<div class="multi_value marginbottom5">
- <%= render "settings/single_value", :property => property, :value => value -%>
- <a href="#" class="delete link-action" style="<%= 'display:none;' if hide_delete -%>"><%= message('delete') -%></a>
- <br/>
-</div>
+++ /dev/null
-<% if @category.isSpecial -%>
- <%= render 'special', :url => url_for(:controller => "#{@category.key}_configuration") -%>
-<% else -%>
- <form onsubmit="$j('#submit_settings').hide();
- $j('#loading_settings').show();
- $j.ajax({ url:'<%= url_for :controller => 'settings', :action => 'update', :category => @category.key, :subcategory => @subcategory.key, :resource_id => (@resource && @resource.id) -%>',
- type:'post',
- success:function(responseHTML){$j('#properties').html($j(responseHTML));$j('#loading_settings').hide();$j('#submit_settings').show()},
- data:$j(this).serialize()});
- return false;"
- method='post'
- action='#'
- autocomplete="off"
- >
- <% subcategories = @subcategories_per_categories[@category] || [] -%>
- <ul class="tabs">
- <% subcategories.each do |subcategory| -%>
- <li>
- <% if @resource %>
- <a <% if @subcategory==subcategory %>class="selected"<% end -%> href="<%= url_for(:controller => 'project', :action => 'settings', :id => nil) -%>?id=<%= url_encode(@resource.key) -%>&category=<%= url_encode(@category.key) -%>&subcategory=<%= url_encode(subcategory.key) -%>"><%= h subcategory_name(@category, subcategory) -%></a>
- <% else %>
- <%= link_to subcategory_name(@category, subcategory), {:controller => 'settings', :action =>'index', :category => @category.key, :subcategory => subcategory.key}, :class => @subcategory==subcategory ? 'selected' : nil -%>
- <% end %>
- </li>
- <% end -%>
- </ul>
- <% if @subcategory.isSpecial %>
- <%= render 'special', :url => url_for(:controller => "#{@subcategory.key}_configuration") -%>
- <% else %>
- <% if @subcategory.key == @category.key && !category_desc(@category).blank? -%>
- <p class="categoryDescription"><%= category_desc(@category) -%> </p>
- <% end -%>
- <% if @subcategory.key != @category.key && !subcategory_desc(@category, @subcategory).blank? -%>
- <p class="categoryDescription" colspan="2"><%= subcategory_desc(@category, @subcategory) -%> </p>
- <% end -%>
-
- <table class="marginbottom10">
- <tbody>
- <% by_property_index_or_name(@definitions).each do |property| -%>
- <tr class="property" id="block_<%= property.key -%>">
- <th>
- <h3><%= property_name(property) -%></h3>
- </th>
- <td>
- <% value = property_value(property) -%>
- <% if property.multi_values -%>
- <% value.each_with_index do |sub_value, index| -%>
- <%= render "settings/multi_value", :property => property, :value => sub_value, :hide_delete => index == 0 -%>
- <% end -%>
- <div class="template" style="display:none;">
- <%= render "settings/multi_value", :property => property, :value => nil, :hide_delete => false -%>
- </div>
- <button class="add_value"><%= message('settings.add') -%></button>
- <br/>
- <% else -%>
- <%= render "settings/single_value", :property => property, :value => value -%>
- <% end -%>
-
- <%= render "settings/error", :key => property.key -%>
-
- <!-- SONAR-4707 Don't display default value for property sets -->
- <% if property.fields.blank? %>
- <% default_prop_value = (@resource ? Property.value(property.key, nil, property.defaultValue) : property.defaultValue) -%>
- <% unless default_prop_value.blank? -%>
- <div class="note"><%= message('default') %>: <%= property.type.to_s=='PASSWORD' ? '********' : h(default_prop_value) -%></div>
- <% else -%>
- <!-- SONAR-5162 When no default value, leave a space to add a separation with the description or the key -->
- <p class="marginbottom10"></p>
- <% end -%>
- <% end -%>
-
- <% desc=property_description(property) -%>
- <% unless desc.blank? %>
- <p class="marginbottom10"><%= desc -%></p>
- <% end -%>
- <div class="note"><%= message('key') -%>: <%= property.key -%></div>
- </td>
- </tr>
- <% end -%>
- </tbody>
-
- </table>
-
- <% unless @definitions.empty? %>
- <div class="marginbottom10" style="padding-left: 5px;">
- <%= hidden_field_tag('page_version', (params[:page_version] || 0).to_i + 1) -%>
- <%= submit_tag(message('settings.save_category', :params => [subcategory_name(@category, @subcategory)]), :id => 'submit_settings') -%>
- <img src="<%= ApplicationController.root_context -%>/images/loading.gif" id="loading_settings" style="display:none;">
- </div>
- <% end %>
-
- <% if @category.key() == 'exclusions' -%>
- <div class="help marginbottom10" style="margin-left: -1px">
- <h2>Wildcards</h2>
- <p>Following rules are applied:</p>
- <table class="data">
- <thead><tr><th colspan="2"></th></tr></thead>
- <tr>
- <td>*</td>
- <td>Match zero or more characters</td>
- </tr>
- <tr>
- <td>**</td>
- <td>Match zero or more directories</td>
- </tr>
- <tr>
- <td>?</td>
- <td>Match a single character</td>
- </tr>
- </table>
- <br>
- <table class="data">
- <thead><tr><th>Example</th><th>Matches</th><th>Does not match</th></tr></thead>
- <tbody>
- <tr>
- <td>**/foo/*.js</td>
- <td>
- <ul>
- <li>src/foo/bar.js</li>
- <li>lib/ui/foo/bar.js</li>
- </ul>
- </td>
- <td>
- <ul>
- <li>src/bar.js</li>
- <li>src/foo2/bar.js</li>
- </ul>
- </td>
- </tr>
- <tr>
- <td>src/foo/*bar*.js</td>
- <td>
- <ul>
- <li>src/foo/bar.js</li>
- <li>src/foo/bar1.js</li>
- <li>src/foo/bar123.js</li>
- <li>src/foo/123bar123.js</li>
- </ul>
- </td>
- <td>
- <ul>
- <li>src/foo/ui/bar.js</li>
- <li>src/bar.js</li>
- </ul>
- </td>
- </tr>
- <tr>
- <td>src/foo/**</td>
- <td>
- <ul>
- <li>src/foo/bar.js</li>
- <li>src/foo/ui/bar.js</li>
- </ul>
- </td>
- <td>
- <ul>
- <li>src/bar/foo/bar.js</li>
- <li>src/bar.js</li>
- </ul>
- </td>
- </tr>
- <tr>
- <td>**/foo?.js</td>
- <td>
- <ul>
- <li>src/foo1.js</li>
- <li>src/bar/foo1.js</li>
- </ul>
- </td>
- <td>
- <ul>
- <li>src/foo.js</li>
- <li>src/foo12.js</li>
- <li>src/12foo3.js</li>
- </ul>
- </td>
- </tr>
- </tbody>
- </table>
- </div>
- <% else
- help = category_help(@category)
- unless help.blank?
- -%>
- <div class="help marginbottom10" style="margin-left: -1px">
- <%= help -%>
- </div>
- <% end
- end
- -%>
-
- <% end %>
- </form>
- <% end -%>
-
-
-
-<script>
- $j('#properties > form')
- .on('click', '.delete', function () {
- $j(this).parents('.multi_value').remove();
- return false;
- })
- .on('click', '.add_value', function () {
- var template = $j(this).parents('.property').find('.template').last();
- template.clone().insertBefore(template).show();
- return false;
- })
- .on('keypress', 'form', function (e) {
- if (e.which == 13 && e.target.nodeName != "TEXTAREA") {
- /* See https://jira.sonarsource.com/browse/SONAR-4363 */
- submit_settings.click();
- return false;
- }
- });
-</script>
+++ /dev/null
-<% errors = [] -%>
-<% key_field = key_field(property) -%>
-
-<tr class="text-top multi_value <%= 'template' unless set_key -%> odd" style="<%= 'display:none' unless set_key -%>">
- <% unless key_field -%>
- <%= hidden_field_tag "property_sets[#{property.key}][]", set_key -%>
- <% end -%>
-
- <% property.fields.each do |field| -%>
- <% if set_key -%>
- <% key = "#{property.key}.#{set_key}.#{field.key}" -%>
- <% value = Property.value(key, resource_id) -%>
- <% errors << (render "settings/error", :key => key) -%>
- <% end -%>
-
- <% if field == key_field -%>
- <td><%= render "settings/type_#{field.type}", :property => field, :field => field, :value => value, :name => "property_sets[#{property.key}][]", :id => "input_#{h field.key}", :size => field.indicativeSize -%></td>
- <% else -%>
- <td><%= render "settings/type_#{field.type}", :property => field, :field => field, :value => value, :name => "#{property.key}[#{field.key}][]", :id => "input_#{h field.key}", :size => field.indicativeSize -%></td>
- <% end -%>
- <% end -%>
-
- <td style="width: 60px;">
- <% unless hide_delete -%>
- <a href="#" class="delete link-action"><%= message('delete') -%></a>
- <% end -%>
- </td>
-</tr>
-
-<% unless errors.all?(&:blank?) -%>
- <tr>
- <% if key_field -%>
- <td></td>
- <% end -%>
-
- <% errors.each do |error| -%>
- <td><%= error -%></td>
- <% end -%>
-
- <td></td>
- </tr>
-<% end -%>
+++ /dev/null
-<div id="plugins">
- <header class="page-header">
- <h1 class="page-title"><%= message(@resource ? 'project_settings.page' : 'settings.page') -%></h1>
- <p class="page-description"><%= message(@resource ? 'project_settings.page.description' : 'settings.page.description') -%> </p>
- </header>
-
- <table width="100%">
- <tr>
- <td width="1%" nowrap class="column first">
- <table class="data">
- <thead>
- <tr>
- <th><%= message('category') -%></th>
- </tr>
- </thead>
- <tbody>
- <% @categories.each do |category| -%>
- <% if !category.key.blank?-%>
- <tr id="select_<%= category.key -%>" class="select <%= cycle('even', 'odd', :name => 'category') -%> <%= 'selected' if @category.key==category.key -%>">
- <td class="category">
- <% if @resource %>
- <a href="<%= url_for(:controller => 'project', :action => 'settings') -%>?id=<%= url_encode(@resource.key) -%>&category=<%= url_encode(category.key) -%>"><%= h category_name(category) -%></a>
- <% else %>
- <%= link_to category_name(category), :category => category.key -%>
- <% end %>
- </td>
- </tr>
- <% end -%>
- <% end -%>
- </tbody>
- </table>
- <br/>
- </td>
-
- <td class="column">
- <div id="properties">
- <%= render 'settings/properties' -%>
- </div>
- </td>
- </tr>
- </table>
-</div>
+++ /dev/null
-<%= render "settings/type_#{property_type(property, value)}", :property => property, :field => nil, :value => value, :name => input_name(property), :id => "input_#{h property.key}" -%>
+++ /dev/null
-<iframe src="<%= url -%>" width="100%" height="900" frameborder="0" style="overflow-y: auto;" name="settings_iframe"></iframe>
+++ /dev/null
-<%= property_input_field(name, PropertyType::TYPE_BOOLEAN, value, PropertiesHelper::SCREEN_SETTINGS, {:id => id, :default => (defined? property.defaultValue) ? property.defaultValue : nil }) %>
\ No newline at end of file
+++ /dev/null
-<%
- options = {:id => id}
- options[:size] = (defined? size) ? size : nil
-%>
-<%= property_input_field(name, PropertyType::TYPE_FLOAT, value, PropertiesHelper::SCREEN_SETTINGS, options) %>
\ No newline at end of file
+++ /dev/null
-<%
- options = {:id => id}
- options[:size] = (defined? size) ? size : nil
-%>
-<%= property_input_field(name, PropertyType::TYPE_INTEGER, value, PropertiesHelper::SCREEN_SETTINGS, options) %>
\ No newline at end of file
+++ /dev/null
-<% if !value || value.blank? %>
- <textarea rows="5" cols="80" class="width100" name="<%= name -%>" id="<%= id -%>"></textarea>
-<%
- else
- license = controller.java_facade.parseLicense(value)
- product = license.getProduct() || '-'
- # super-hack here
- # should be avoided in the future
- does_product_match = property.key.include? product
-%>
- <div class="width100">
- <textarea rows="6" name="<%= name -%>" id="<%= id -%>" style="float: left;width: 390px"><%= h value -%></textarea>
-
- <div style="margin-left: 400px">
- <table>
- <tr>
- <td class="form-key-cell <% if !does_product_match -%>bg-danger<% end -%>">Product:</td>
- <td class="form-val-cell <% if !does_product_match -%>bg-danger<% end -%>"><%= product -%></td>
- </tr>
- <tr>
- <td class="form-key-cell">Organization:</td>
- <td><%= license.getOrganization() || '-' -%></td>
- </tr>
- <tr>
- <td class="form-key-cell">Expiration:</td>
- <td>
- <% if license.getExpirationDate()
- formatted_date = l(Date.parse(license.getExpirationDateAsString()))
- %>
- <%= license.isExpired() ? "<span class='error'>#{formatted_date}</span>" : formatted_date -%>
- <% else %>
- -
- <% end %>
-
- </td>
- </tr>
- <tr>
- <td class="form-key-cell">Type:</td>
- <td><%= license.getType() || '-' -%></td>
- </tr>
- <tr>
- <td class="form-key-cell">Server:</td>
- <td>
- <% if license.getServer() &&
- license.getServer() != "*" &&
- controller.java_facade.getConfigurationValue("sonar.server_id") != license.getServer() %>
- <span class='error'><%= license.getServer() -%></span>
- <% else %>
- <%= license.getServer() || '-' -%>
- <% end %>
- </td>
- </tr>
- <% license.additionalProperties().each do |k,v| -%>
- <tr>
- <td class="form-key-cell"><%= k -%>:</td>
- <td><%= v || '-' -%></td>
- </tr>
- <% end %>
- </table>
- </div>
- </div>
-<% end %>
+++ /dev/null
-<%
- defaultValue = (defined? property.defaultValue) ? property.defaultValue : nil
-%>
-<select name="<%= name -%>" id="<%= id -%>">
- <option value=""><%= !defaultValue.blank? ? message('default') : nil -%></option>
- <%
- metrics_per_domain={}
- metrics_filtered_by(property.options).each do |metric|
- domain=metric.domain || ''
- metrics_per_domain[domain]||=[]
- metrics_per_domain[domain]<<metric
- end
-
- metrics_per_domain.keys.sort.each do |domain|
- %>
- <optgroup label="<%= h domain -%>">
- <% metrics_per_domain[domain].each do |m|
- selected_attr = (m.key==value || m.id==value) ? " selected='selected'" : ''
- %>
- <option value="<%= m.key -%>" <%= selected_attr -%>><%= m.short_name -%></option>
- <% end -%>
- </optgroup>
- <% end -%>
-</select>
\ No newline at end of file
+++ /dev/null
-<%
- options = {:id => id}
- options[:size] = (defined? size) ? size : nil
-%>
-
-<% value = Property::EXISTING_PASSWORD unless value.blank? %>
-<%= property_input_field(name, PropertyType::TYPE_PASSWORD, value, PropertiesHelper::SCREEN_SETTINGS, options) %>
+++ /dev/null
-<% choices = Property.values(property.propertySetKey).reject(&:blank?) -%>
-<% prompt = [[message('default'), '']] -%>
-
-<% if !value.blank? && (choices.exclude? value) -%>
- <%= image_tag 'exclamation.png' -%>
- <% missing = [[h(value + ' <' + message('deleted') + '>'), value]] -%>
-<% else -%>
- <% missing = [] -%>
-<% end -%>
-
-<%= select_tag name, options_for_select(prompt + choices + missing, value), :id => id -%>
\ No newline at end of file
+++ /dev/null
-<% resource_id = @resource.id if @resource -%>
-
-<table class="data">
- <thead>
- <tr>
- <% unless key_field(property) -%>
- <%= hidden_field_tag "auto_generate[#{property.key}]", true -%>
- <% end -%>
- <% property.fields.each do |field| -%>
- <th>
- <%= field_name(property, field) -%>
- <% desc = field_description(property, field) -%>
- <% unless desc.blank? %>
- <p class="note"><%= desc -%></p>
- <% end -%>
- </th>
- <% end -%>
- <th></th>
- </tr>
- </thead>
-
- <tbody>
- <% set_keys = Property.values(property.key, resource_id) -%>
- <% set_keys = [''] if set_keys.all?(&:blank?) -%>
- <% set_keys.each_with_index do |set_key, index| -%>
- <%= render 'settings/set_instance', :property => property, :set_key => set_key, :resource_id => resource_id, :hide_delete => (index == 0) %>
- <% end -%>
- <%= render 'settings/set_instance', :property => property, :set_key => nil, :resource_id => resource_id, :hide_delete => false %>
- </tbody>
-
- <tfoot>
- <tr>
- <td colspan="<%= property.fields.size + 1 -%>">
- <button class="add_value"><%= message('settings.add') -%></button>
- </td>
- </tr>
- </tfoot>
-</table>
+++ /dev/null
-<%
- options = {:id => id}
- options[:size] = (defined? size) ? size : nil
-%>
-<%= property_input_field(name, PropertyType::TYPE_REGULAR_EXPRESSION, value, PropertiesHelper::SCREEN_SETTINGS, options) %>
\ No newline at end of file
+++ /dev/null
-<%= property_input_field(name, PropertyType::TYPE_SINGLE_SELECT_LIST, value, PropertiesHelper::SCREEN_SETTINGS,
- {:id => id, :default => (defined? property.defaultValue) ? property.defaultValue : nil, :values => property.options, :extra_values => {:property => property, :field => field}}) %>
\ No newline at end of file
+++ /dev/null
-<%
- options = {:id => id}
- options[:size] = (defined? size) ? size : nil
-%>
-<%= property_input_field(name, PropertyType::TYPE_STRING, value, PropertiesHelper::SCREEN_SETTINGS, options) %>
\ No newline at end of file
+++ /dev/null
-<%
- options = {:id => id}
- options[:size] = (defined? size) ? size : nil
-%>
-<%= property_input_field(name, PropertyType::TYPE_TEXT, value, PropertiesHelper::SCREEN_SETTINGS, options) %>
+++ /dev/null
-<%
- options = {:id => id}
- options[:size] = (defined? size) ? size : nil
-%>
-<%= property_input_field(name, PropertyType::TYPE_USER_LOGIN, value, PropertiesHelper::SCREEN_SETTINGS, options) %>
-<div class="page">
- <%= render 'settings', :project => nil %>
-</div>
+<% content_for :extra_script do %>
+ <script src="<%= ApplicationController.root_context -%>/js/bundles/settings.js?v=<%= sonar_version -%>"></script>
+<% end %>
--- /dev/null
+/*
+ * 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.
+ */
+export const click = element => {
+ return element.simulate('click', {
+ target: { blur () {} },
+ preventDefault () {}
+ });
+};
+
+export const submit = element => {
+ return element.simulate('submit', {
+ preventDefault () {}
+ });
+};
+
+export const change = (element, value) => {
+ return element.simulate('change', {
+ target: { value },
+ currentTarget: { value }
+ });
+};
<configuration>
<rules>
<requireFilesSize>
- <minsize>120000000</minsize>
- <maxsize>128000000</maxsize>
+ <minsize>125000000</minsize>
+ <maxsize>132000000</maxsize>
<files>
<file>${project.build.directory}/sonarqube-${project.version}.zip</file>
</files>
see_all=See All
select_all=Select all
select_verb=Select
+set=Set
severity=Severity
severity_abbreviated=Se.
shared=Shared
#------------------------------------------------------------------------------
settings.add=Add value
settings.save_category=Save {0} Settings
+settings.key_x=Key: {0}
+settings.default_x=Default: {0}
+settings.not_set=(not set)
+settings.state.saving=Saving...
+settings.state.saved=Saved!
+settings.state.validation_failed=Validation failed. {0}
+settings.state.value_cant_be_empty=Value can't be empty. Use "Reset" to set value to the default one.
+settings._default=(default)
+settings.boolean.true=True
+settings.boolean.false=False
+settings.default.no_value=<no value>
+settings.default.complex_value=<complex value>
+settings.default.password=<password>
+
property.category.general=General
property.category.general.email=Email
property.category.general.duplications=Duplications