]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5856 Rewrite Settings page (#1163)
authorStas Vilchik <vilchiks@gmail.com>
Thu, 1 Sep 2016 15:30:34 +0000 (17:30 +0200)
committerGitHub <noreply@github.com>
Thu, 1 Sep 2016 15:30:34 +0000 (17:30 +0200)
128 files changed:
it/it-tests/src/test/java/it/Category1Suite.java
it/it-tests/src/test/java/it/componentDashboard/DashboardTest.java
it/it-tests/src/test/java/it/measureHistory/DifferentialPeriodsTest.java
it/it-tests/src/test/java/it/projectAdministration/ProjectAdministrationTest.java
it/it-tests/src/test/java/it/settings/PropertySetsTest.java
it/it-tests/src/test/java/it/settings/SettingsTestRestartingOrchestrator.java
it/it-tests/src/test/java/it/settings/SubCategoriesTest.java [deleted file]
it/it-tests/src/test/java/pageobjects/LoginPage.java
it/it-tests/src/test/java/pageobjects/Navigation.java
it/it-tests/src/test/java/pageobjects/settings/PropertySetInput.java [new file with mode: 0644]
it/it-tests/src/test/java/pageobjects/settings/SettingsPage.java [new file with mode: 0644]
it/it-tests/src/test/resources/componentDashboard/DashboardTest/global_dashboard/global-admin-dashboards.html [deleted file]
it/it-tests/src/test/resources/measureHistory/DifferentialPeriodsTest/define-leak-period-on-project.html [deleted file]
it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/module-settings/display-module-settings.html [deleted file]
it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-settings/only-on-project-settings.html [deleted file]
it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-settings/override-global-settings.html [deleted file]
it/it-tests/src/test/resources/serverSystem/ServerSystemTest/missing_ip.html
it/it-tests/src/test/resources/serverSystem/ServerSystemTest/organisation_must_not_accept_special_chars.html
it/it-tests/src/test/resources/serverSystem/ServerSystemTest/valid_id.html
it/it-tests/src/test/resources/settings/PropertySetsTest/auto-generated/create.html [deleted file]
it/it-tests/src/test/resources/settings/PropertySetsTest/auto-generated/update.html [deleted file]
it/it-tests/src/test/resources/settings/PropertySetsTest/property-sets/all_types.html [deleted file]
it/it-tests/src/test/resources/settings/PropertySetsTest/property-sets/create.html [deleted file]
it/it-tests/src/test/resources/settings/PropertySetsTest/property-sets/delete.html [deleted file]
it/it-tests/src/test/resources/settings/PropertySetsTest/property-sets/reference.html [deleted file]
it/it-tests/src/test/resources/settings/SettingsTest/encrypt-text.html
it/it-tests/src/test/resources/settings/SettingsTest/general-settings.html [deleted file]
it/it-tests/src/test/resources/settings/SettingsTest/generate-secret-key.html
it/it-tests/src/test/resources/settings/SettingsTest/global-extension-property.html [deleted file]
it/it-tests/src/test/resources/settings/SettingsTest/hidden-extension-property.html [deleted file]
it/it-tests/src/test/resources/settings/SettingsTest/hide-passwords.html [deleted file]
it/it-tests/src/test/resources/settings/SettingsTest/property_relocation.html [deleted file]
it/it-tests/src/test/resources/settings/SettingsTest/validate-property-type.html [deleted file]
it/it-tests/src/test/resources/settings/subcategories/global-subcategories-no-default.html [deleted file]
it/it-tests/src/test/resources/settings/subcategories/global-subcategories.html [deleted file]
it/it-tests/src/test/resources/settings/subcategories/project-subcategories-no-default.html [deleted file]
it/it-tests/src/test/resources/settings/subcategories/project-subcategories.html [deleted file]
it/it-tests/src/test/resources/updateCenter/installed-plugins.html
it/it-tests/src/test/resources/user/LocalAuthenticationTest/redirect_to_original_url_after_indirect_login.html
server/sonar-web/config/webpack/webpack.config.base.js
server/sonar-web/scripts/start.js
server/sonar-web/src/main/js/api/settings.js [new file with mode: 0644]
server/sonar-web/src/main/js/api/users.js
server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/app.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/App.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/Definition.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/DefinitionChanges.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/DefinitionDefaults.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/GlobalMessagesContainer.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/PageHeader.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/Input.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForNumber.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForString.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForText.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/PropertySetInput.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/SimpleInput.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/Input-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForBoolean-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForNumber-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForString-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForText-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/MultiValueInput-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/SimpleInput-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/constants.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/propTypes.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/actions.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/definitions/actions.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/definitions/reducer.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/rootReducer.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/actions.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/reducer.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/actions.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/reducer.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/settingsPage/reducer.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/actions.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/reducer.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/values/actions.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/store/values/reducer.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/styles.css [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/utils.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/controls/Toggle.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/controls/__tests__/Toggle-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/components/controls/styles.css
server/sonar-web/src/main/js/helpers/l10n.js
server/sonar-web/src/main/js/helpers/request.js
server/sonar-web/src/main/less/components/react-select.less
server/sonar-web/src/main/less/init/forms.less
server/sonar-web/src/main/less/init/tables.less
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/project_controller.rb
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/settings_controller.rb
server/sonar-web/src/main/webapp/WEB-INF/app/views/project/settings.html.erb
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_error.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_multi_value.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_properties.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_set_instance.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_settings.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_single_value.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_special.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_BOOLEAN.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_FLOAT.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_INTEGER.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_LICENSE.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_METRIC.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PASSWORD.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PROPERTY_SET.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PROPERTY_SET_DEFINITION.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_REGULAR_EXPRESSION.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_STRING.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_TEXT.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_USER_LOGIN.html.erb [deleted file]
server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/index.html.erb
server/sonar-web/tests/utils.js [new file with mode: 0644]
sonar-application/pom.xml
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 18f2c01a35222952593bdff29a1f11c18ccfdbdf..8f1a4cbe3a01465f6af4c9aba4dc41edca7cf368 100644 (file)
@@ -49,7 +49,6 @@ import it.qualityGate.QualityGateTest;
 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;
@@ -71,7 +70,6 @@ import static util.ItUtils.xooPlugin;
   BackgroundTasksTest.class,
   // settings
   PropertySetsTest.class,
-  SubCategoriesTest.class,
   SettingsTest.class,
   // i18n
   I18nTest.class,
index 4ef6c3000e9df8d33796dd61cd6e12ccd31b926e..2f903b05fb6addb7752b99a2c8333b0fcc2b83cc 100644 (file)
@@ -70,9 +70,6 @@ public class DashboardTest {
       // 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",
 
index 0cd559954181ce84e81e5d2c92422c353e26dfb6..00a0236650269d8cdc3151b46cda19e886d6a88f 100644 (file)
@@ -21,7 +21,6 @@ package it.measureHistory;
 
 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;
@@ -32,8 +31,8 @@ import org.junit.Test;
 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;
@@ -133,9 +132,8 @@ public class DifferentialPeriodsTest {
     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");
   }
 
   /**
index 50a3f12b3b4beb03d8afca39daa40b3d861fe6dd..5026725cbc8aced909c3cd095e5fa3aa69aa05e2 100644 (file)
@@ -23,6 +23,7 @@ import com.sonar.orchestrator.Orchestrator;
 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;
@@ -34,22 +35,27 @@ import org.junit.Test;
 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";
 
@@ -116,7 +122,7 @@ public class ProjectAdministrationTest {
 
       new SeleneseTest(
         Selenese.builder().setHtmlTestsInClasspath("project-deletion", "/projectAdministration/ProjectAdministrationTest/project-deletion/project-deletion.html").build())
-          .runOn(orchestrator);
+        .runOn(orchestrator);
     } finally {
       wsClient.userClient().deactivate(projectAdminUser);
     }
@@ -156,40 +162,37 @@ public class ProjectAdministrationTest {
     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) {
index fb4858b6ff4373bd38c4030bf029a6839dbb0c45..0dca1cf94b5ce6545cfaa718f4e1eff0421adc77 100644 (file)
 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;
@@ -47,6 +49,9 @@ public class PropertySetsTest {
   @ClassRule
   public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR;
 
+  @Rule
+  public Navigation nav = Navigation.get(orchestrator);
+
   static SettingsService SETTINGS;
 
   @BeforeClass
@@ -60,37 +65,37 @@ public class PropertySetsTest {
   }
 
   @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
@@ -124,7 +129,7 @@ public class PropertySetsTest {
     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++;
     }
   }
index 055c79910262efd29e6cebea787a98d01d77f0f7..67b2eefa62f9fd6dbc67422e5bed4f9754da55a5 100644 (file)
@@ -22,11 +22,13 @@ package it.settings;
 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;
@@ -51,7 +53,7 @@ public class SettingsTestRestartingOrchestrator {
   }
 
   @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"))
@@ -60,33 +62,28 @@ public class SettingsTestRestartingOrchestrator {
       .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())
@@ -101,10 +98,10 @@ public class SettingsTestRestartingOrchestrator {
     // 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");
   }
 
 }
diff --git a/it/it-tests/src/test/java/it/settings/SubCategoriesTest.java b/it/it-tests/src/test/java/it/settings/SubCategoriesTest.java
deleted file mode 100644 (file)
index 5bca8b8..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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();
-  }
-}
index 2aea164f0cb451848b26489926290de3bcbbdb2f..98e95b01df82ae3bfe163bb106a1fca685d11c0e 100644 (file)
@@ -36,6 +36,10 @@ public class LoginPage {
     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);
index ecd9cc6bc67bac36acfa930c080b410e50bfaef4..28a99e4b3f8770500d4df5b345ac45e38333d7a4 100644 (file)
@@ -24,8 +24,12 @@ import com.codeborne.selenide.Selenide;
 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;
@@ -84,6 +88,13 @@ public class Navigation extends ExternalResource {
     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);
   }
diff --git a/it/it-tests/src/test/java/pageobjects/settings/PropertySetInput.java b/it/it-tests/src/test/java/pageobjects/settings/PropertySetInput.java
new file mode 100644 (file)
index 0000000..9da8501
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+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;
+  }
+}
diff --git a/it/it-tests/src/test/java/pageobjects/settings/SettingsPage.java b/it/it-tests/src/test/java/pageobjects/settings/SettingsPage.java
new file mode 100644 (file)
index 0000000..24676e8
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * 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);
+  }
+}
diff --git a/it/it-tests/src/test/resources/componentDashboard/DashboardTest/global_dashboard/global-admin-dashboards.html b/it/it-tests/src/test/resources/componentDashboard/DashboardTest/global_dashboard/global-admin-dashboards.html
deleted file mode 100644 (file)
index 67911a1..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
diff --git a/it/it-tests/src/test/resources/measureHistory/DifferentialPeriodsTest/define-leak-period-on-project.html b/it/it-tests/src/test/resources/measureHistory/DifferentialPeriodsTest/define-leak-period-on-project.html
deleted file mode 100644 (file)
index 0bba5f6..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-  <title>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&amp;category=general&amp;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>
diff --git a/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/module-settings/display-module-settings.html b/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/module-settings/display-module-settings.html
deleted file mode 100644 (file)
index 6d301d7..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
diff --git a/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-settings/only-on-project-settings.html b/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-settings/only-on-project-settings.html
deleted file mode 100644 (file)
index 9197fa9..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
diff --git a/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-settings/override-global-settings.html b/it/it-tests/src/test/resources/projectAdministration/ProjectAdministrationTest/project-settings/override-global-settings.html
deleted file mode 100644 (file)
index a73b2ca..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-       <title>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&amp;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>
index 1a9725f07a8b7e4849c584b262bc4842900c36f6..82e481ae7b925435825e51fe7489910d0f1da411 100644 (file)
   </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>
index 29823121244bec49cf3dcfe95e12e013fb16c75e..d9b98cf4ed7462f26b54a8fce9e19c8d09907d02 100644 (file)
     </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>
index 4f86397d6423208069d1892d59d19816666a913b..dabdb002dffcb0b207f6e0441c9874fd77a4b6a0 100644 (file)
     </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>
diff --git a/it/it-tests/src/test/resources/settings/PropertySetsTest/auto-generated/create.html b/it/it-tests/src/test/resources/settings/PropertySetsTest/auto-generated/create.html
deleted file mode 100644 (file)
index a28e642..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
diff --git a/it/it-tests/src/test/resources/settings/PropertySetsTest/auto-generated/update.html b/it/it-tests/src/test/resources/settings/PropertySetsTest/auto-generated/update.html
deleted file mode 100644 (file)
index 2db1709..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
diff --git a/it/it-tests/src/test/resources/settings/PropertySetsTest/property-sets/all_types.html b/it/it-tests/src/test/resources/settings/PropertySetsTest/property-sets/all_types.html
deleted file mode 100644 (file)
index 4dfffa1..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-  <title>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>
diff --git a/it/it-tests/src/test/resources/settings/PropertySetsTest/property-sets/create.html b/it/it-tests/src/test/resources/settings/PropertySetsTest/property-sets/create.html
deleted file mode 100644 (file)
index d85e447..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
diff --git a/it/it-tests/src/test/resources/settings/PropertySetsTest/property-sets/delete.html b/it/it-tests/src/test/resources/settings/PropertySetsTest/property-sets/delete.html
deleted file mode 100644 (file)
index aa0e9ca..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
diff --git a/it/it-tests/src/test/resources/settings/PropertySetsTest/property-sets/reference.html b/it/it-tests/src/test/resources/settings/PropertySetsTest/property-sets/reference.html
deleted file mode 100644 (file)
index c212340..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
index fe174478d54f783c2b4309b858b2819334be15b8..d1258d789ac886b1107197215ab06b52f191708d 100644 (file)
@@ -15,7 +15,7 @@
     </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>
diff --git a/it/it-tests/src/test/resources/settings/SettingsTest/general-settings.html b/it/it-tests/src/test/resources/settings/SettingsTest/general-settings.html
deleted file mode 100644 (file)
index 89061e1..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
index c667bf3ca0ba91a430aefb79eb156cf645bab60e..dafe8ccfe82f02245c81b49551fffe4796f1977f 100644 (file)
@@ -15,7 +15,7 @@
     </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>
diff --git a/it/it-tests/src/test/resources/settings/SettingsTest/global-extension-property.html b/it/it-tests/src/test/resources/settings/SettingsTest/global-extension-property.html
deleted file mode 100644 (file)
index ad3d976..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
diff --git a/it/it-tests/src/test/resources/settings/SettingsTest/hidden-extension-property.html b/it/it-tests/src/test/resources/settings/SettingsTest/hidden-extension-property.html
deleted file mode 100644 (file)
index 7aa9bd9..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
diff --git a/it/it-tests/src/test/resources/settings/SettingsTest/hide-passwords.html b/it/it-tests/src/test/resources/settings/SettingsTest/hide-passwords.html
deleted file mode 100644 (file)
index 531e11e..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
diff --git a/it/it-tests/src/test/resources/settings/SettingsTest/property_relocation.html b/it/it-tests/src/test/resources/settings/SettingsTest/property_relocation.html
deleted file mode 100644 (file)
index 37986cd..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
diff --git a/it/it-tests/src/test/resources/settings/SettingsTest/validate-property-type.html b/it/it-tests/src/test/resources/settings/SettingsTest/validate-property-type.html
deleted file mode 100644 (file)
index 96024f7..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
diff --git a/it/it-tests/src/test/resources/settings/subcategories/global-subcategories-no-default.html b/it/it-tests/src/test/resources/settings/subcategories/global-subcategories-no-default.html
deleted file mode 100644 (file)
index a4b6971..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
diff --git a/it/it-tests/src/test/resources/settings/subcategories/global-subcategories.html b/it/it-tests/src/test/resources/settings/subcategories/global-subcategories.html
deleted file mode 100644 (file)
index 3f57822..0000000
+++ /dev/null
@@ -1,156 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
diff --git a/it/it-tests/src/test/resources/settings/subcategories/project-subcategories-no-default.html b/it/it-tests/src/test/resources/settings/subcategories/project-subcategories-no-default.html
deleted file mode 100644 (file)
index f0efe3f..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
diff --git a/it/it-tests/src/test/resources/settings/subcategories/project-subcategories.html b/it/it-tests/src/test/resources/settings/subcategories/project-subcategories.html
deleted file mode 100644 (file)
index 1d6b7ef..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <title>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>
index 05c21066451be46bf19508f064c8d109c1110ce7..01c518c4f9f2aafbf798a106548301c23f8ffe69 100644 (file)
@@ -15,7 +15,7 @@
   </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>
index ef3618524db375ba9d4149a7c1a70fbeed90daae..9683c351923f67b7af4ecbb8d488e3e1b9cc9d1c 100644 (file)
@@ -14,7 +14,7 @@
   </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>
index 0533884f2450675fe02ab7ccc63bca89bfdff960..2e0dcc48236e67fd6e480f3fc1d4f0082b33042c 100644 (file)
@@ -45,6 +45,7 @@ module.exports = {
     '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',
index ceb9d61928a46cf89162a31f0218c71992155db7..905793318e2c67d787a18a14cd8ad4b0dcc80310 100644 (file)
@@ -121,14 +121,10 @@ function setupCompiler () {
 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) {
diff --git a/server/sonar-web/src/main/js/api/settings.js b/server/sonar-web/src/main/js/api/settings.js
new file mode 100644 (file)
index 0000000..1439624
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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);
+}
index 80865674d800d203399fd0571bbf8c8a7ad50cae..e5d5d15f7bc76bc0d16936af65c3f433aef5e28c 100644 (file)
@@ -39,3 +39,9 @@ export function getIdentityProviders () {
   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);
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.js b/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.js
new file mode 100644 (file)
index 0000000..16b709d
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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]);
+    });
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/app.js b/server/sonar-web/src/main/js/apps/settings/app.js
new file mode 100644 (file)
index 0000000..cf9dfcf
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import 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);
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.js
new file mode 100644 (file)
index 0000000..44d16ad
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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);
diff --git a/server/sonar-web/src/main/js/apps/settings/components/App.js b/server/sonar-web/src/main/js/apps/settings/components/App.js
new file mode 100644 (file)
index 0000000..424675b
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * 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);
+
diff --git a/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js b/server/sonar-web/src/main/js/apps/settings/components/CategoriesList.js
new file mode 100644 (file)
index 0000000..aa833d5
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * 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>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js b/server/sonar-web/src/main/js/apps/settings/components/CategoryDefinitionsList.js
new file mode 100644 (file)
index 0000000..cc2e6d8
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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);
diff --git a/server/sonar-web/src/main/js/apps/settings/components/Definition.js b/server/sonar-web/src/main/js/apps/settings/components/Definition.js
new file mode 100644 (file)
index 0000000..66b19fd
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * 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);
diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionChanges.js b/server/sonar-web/src/main/js/apps/settings/components/DefinitionChanges.js
new file mode 100644 (file)
index 0000000..edbeafe
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionDefaults.js b/server/sonar-web/src/main/js/apps/settings/components/DefinitionDefaults.js
new file mode 100644 (file)
index 0000000..452b24c
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js b/server/sonar-web/src/main/js/apps/settings/components/DefinitionsList.js
new file mode 100644 (file)
index 0000000..77d0d7f
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/GlobalMessagesContainer.js b/server/sonar-web/src/main/js/apps/settings/components/GlobalMessagesContainer.js
new file mode 100644 (file)
index 0000000..f64ef91
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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);
diff --git a/server/sonar-web/src/main/js/apps/settings/components/PageHeader.js b/server/sonar-web/src/main/js/apps/settings/components/PageHeader.js
new file mode 100644 (file)
index 0000000..34e4021
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.js b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.js
new file mode 100644 (file)
index 0000000..f61d6b2
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/Input.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/Input.js
new file mode 100644 (file)
index 0000000..02fcbfa
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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}/>;
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.js
new file mode 100644 (file)
index 0000000..9f64059
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import 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>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForNumber.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForNumber.js
new file mode 100644 (file)
index 0000000..6f741da
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * 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"/>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js
new file mode 100644 (file)
index 0000000..4e2a783
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.js
new file mode 100644 (file)
index 0000000..a0c7046
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import 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)}/>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForString.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForString.js
new file mode 100644 (file)
index 0000000..7eaef55
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * 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"/>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForText.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForText.js
new file mode 100644 (file)
index 0000000..13cbbfa
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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)}/>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.js
new file mode 100644 (file)
index 0000000..fa2facf
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * 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>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/PrimitiveInput.js
new file mode 100644 (file)
index 0000000..61e9db6
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import 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}/>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/PropertySetInput.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/PropertySetInput.js
new file mode 100644 (file)
index 0000000..636a838
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * 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>&nbsp;</th>
+              </tr>
+            </thead>
+            <tbody>
+              {displayedValue.map((fieldValues, index) =>
+                  this.renderFields(fieldValues, index, index === displayedValue.length - 1))}
+            </tbody>
+          </table>
+        </div>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/SimpleInput.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/SimpleInput.js
new file mode 100644 (file)
index 0000000..6a7e24a
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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)}/>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/Input-test.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/Input-test.js
new file mode 100644 (file)
index 0000000..dbe570e
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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);
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForBoolean-test.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForBoolean-test.js
new file mode 100644 (file)
index 0000000..2fc8b9b
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * 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]);
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForNumber-test.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForNumber-test.js
new file mode 100644 (file)
index 0000000..3544898
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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');
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.js
new file mode 100644 (file)
index 0000000..6e46364
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * 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);
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.js
new file mode 100644 (file)
index 0000000..2be66e0
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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']);
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForString-test.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForString-test.js
new file mode 100644 (file)
index 0000000..4fd3c7a
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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');
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForText-test.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForText-test.js
new file mode 100644 (file)
index 0000000..97ef6ec
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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']);
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/MultiValueInput-test.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/MultiValueInput-test.js
new file mode 100644 (file)
index 0000000..126a7a5
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+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']]);
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/SimpleInput-test.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/SimpleInput-test.js
new file mode 100644 (file)
index 0000000..679d530
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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']);
+  });
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/constants.js b/server/sonar-web/src/main/js/apps/settings/constants.js
new file mode 100644 (file)
index 0000000..249c996
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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';
diff --git a/server/sonar-web/src/main/js/apps/settings/propTypes.js b/server/sonar-web/src/main/js/apps/settings/propTypes.js
new file mode 100644 (file)
index 0000000..c7e64bf
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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
+};
diff --git a/server/sonar-web/src/main/js/apps/settings/store/actions.js b/server/sonar-web/src/main/js/apps/settings/store/actions.js
new file mode 100644 (file)
index 0000000..f2ba7d7
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * 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();
+      });
+};
diff --git a/server/sonar-web/src/main/js/apps/settings/store/definitions/actions.js b/server/sonar-web/src/main/js/apps/settings/store/definitions/actions.js
new file mode 100644 (file)
index 0000000..589a05d
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/store/definitions/reducer.js b/server/sonar-web/src/main/js/apps/settings/store/definitions/reducer.js
new file mode 100644 (file)
index 0000000..d80ffc4
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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];
+  }
+};
diff --git a/server/sonar-web/src/main/js/apps/settings/store/rootReducer.js b/server/sonar-web/src/main/js/apps/settings/store/rootReducer.js
new file mode 100644 (file)
index 0000000..603cb3b
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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);
diff --git a/server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/actions.js b/server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/actions.js
new file mode 100644 (file)
index 0000000..46a0bee
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/reducer.js b/server/sonar-web/src/main/js/apps/settings/store/settingsPage/changedValues/reducer.js
new file mode 100644 (file)
index 0000000..3783b92
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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];
diff --git a/server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/actions.js b/server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/actions.js
new file mode 100644 (file)
index 0000000..0a4363b
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/reducer.js b/server/sonar-web/src/main/js/apps/settings/store/settingsPage/loading/reducer.js
new file mode 100644 (file)
index 0000000..d0a6677
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import { 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];
diff --git a/server/sonar-web/src/main/js/apps/settings/store/settingsPage/reducer.js b/server/sonar-web/src/main/js/apps/settings/store/settingsPage/reducer.js
new file mode 100644 (file)
index 0000000..a3dc0c7
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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);
diff --git a/server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/actions.js b/server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/actions.js
new file mode 100644 (file)
index 0000000..eb7b33d
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/reducer.js b/server/sonar-web/src/main/js/apps/settings/store/settingsPage/validationMessages/reducer.js
new file mode 100644 (file)
index 0000000..b332de3
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import { 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];
diff --git a/server/sonar-web/src/main/js/apps/settings/store/values/actions.js b/server/sonar-web/src/main/js/apps/settings/store/values/actions.js
new file mode 100644 (file)
index 0000000..582b607
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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
+});
diff --git a/server/sonar-web/src/main/js/apps/settings/store/values/reducer.js b/server/sonar-web/src/main/js/apps/settings/store/values/reducer.js
new file mode 100644 (file)
index 0000000..5ed6709
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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];
diff --git a/server/sonar-web/src/main/js/apps/settings/styles.css b/server/sonar-web/src/main/js/apps/settings/styles.css
new file mode 100644 (file)
index 0000000..a5c4c61
--- /dev/null
@@ -0,0 +1,157 @@
+.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;
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/utils.js b/server/sonar-web/src/main/js/apps/settings/utils.js
new file mode 100644 (file)
index 0000000..5b40e2e
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * 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;
+}
diff --git a/server/sonar-web/src/main/js/components/controls/Toggle.js b/server/sonar-web/src/main/js/components/controls/Toggle.js
new file mode 100644 (file)
index 0000000..c843657
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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>
+    );
+  }
+}
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/Toggle-test.js b/server/sonar-web/src/main/js/components/controls/__tests__/Toggle-test.js
new file mode 100644 (file)
index 0000000..f194063
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import 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);
+  });
+});
index 1ed5c1f4e52019d60aa74fc33dea66186bbd3cff..b67f7f5eb7165b3d976172827385d07d7d0f0f52 100644 (file)
 .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);
+}
index c3eadcab7bb45a8beed963149469ccaf3fc27c8b..f2ea4d2b3367efb82a59c09e1083bf5ed3fc4950 100644 (file)
@@ -36,6 +36,11 @@ export function translateWithParameters (messageKey, ...parameters) {
   }
 }
 
+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;
 }
index bf88a0658be8808a691a17588fa04a8463d12bd3..ab92b6aaec33b8022607e99fc2c69821f88af44c 100644 (file)
@@ -201,5 +201,5 @@ export function requestDelete (url, data) {
  * @returns {Promise}
  */
 export function delay (response) {
-  return new Promise(resolve => setTimeout(() => resolve(response), 3000));
+  return new Promise(resolve => setTimeout(() => resolve(response), 500));
 }
index 05475d007e8b500a25689626d8a46394df721776..3ba2a2edaa2a442d0a5f0b8745877b6a9d6dd973 100644 (file)
@@ -92,7 +92,7 @@
   left: 0;
   line-height: @formControlHeight;
   padding-left: 8px;
-  padding-right: 8px;
+  padding-right: 24px;
   position: absolute;
   right: 0;
   top: 0;
index ffb3a31395a869f6361c69b44e56aeacb1851b32..bdc1c17ced310b9c9daca6f846dbf769dd693db9 100644 (file)
@@ -30,6 +30,7 @@ input[type=password],
 input[type=email],
 input[type=search],
 input[type=date],
+input[type=number],
 textarea,
 select {
   border: 1px solid @darkGrey;
@@ -53,7 +54,8 @@ input[type=text],
 input[type=password],
 input[type=email],
 input[type=search],
-input[type=date] {
+input[type=date],
+input[type=number] {
   height: @formControlHeight;
   padding: 0 6px;
 }
@@ -132,6 +134,17 @@ input[type="submit"].button-red {
   }
 }
 
+.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 {
@@ -229,6 +242,12 @@ input[type="submit"].button-red {
 .input-super-large {
   width: 100%;
   max-width: 300px;
+  min-width: 200px;
+}
+
+textarea.input-super-large {
+  max-width: 600px;
+  min-width: 300px;
 }
 
 em.mandatory {
@@ -244,6 +263,7 @@ label[for] {
   display: inline-block;
   vertical-align: middle;
   font-size: 0;
+  white-space: nowrap;
 
   & > li {
     display: inline-block;
index e157aebfbd4be65b41d8252a35b39bdad802af3a..142ed35ea8e185fc4f88796b4ff4b1d2adcc5ccb 100644 (file)
@@ -97,6 +97,26 @@ table.data.condensed > tbody > tr > td {
   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;
index a3dad4b490a01b53b1139f05d46fff14ac156435..40393a473a0f263e0b2758e0a9c172679dec33b2 100644 (file)
@@ -28,6 +28,10 @@ class ProjectController < ApplicationController
     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
@@ -78,18 +82,6 @@ class ProjectController < ApplicationController
                               :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
index e5bdc3f0c4f5e2f5e97deb7b235b1dfe232f562e..ee4e72f070e7f93617887a0675e91de6fc303154 100644 (file)
 # 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
index ce82fb311bc7905827afbc3b6cb45cdb2dcaf68a..c0f9f94d9f035ab8f6cf1285eda6a8cb3c726aa0 100644 (file)
@@ -1,5 +1,3 @@
-<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 %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_error.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_error.html.erb
deleted file mode 100644 (file)
index 6dff575..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<% updated_property = @updated_properties[key] if @updated_properties -%>
-
-<% if updated_property && !updated_property.valid? -%>
-  <div class="error"><%= updated_property.validation_error_message -%></div>
-<% end -%>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_multi_value.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_multi_value.html.erb
deleted file mode 100644 (file)
index 0689736..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<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>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_properties.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_properties.html.erb
deleted file mode 100644 (file)
index b19a337..0000000
+++ /dev/null
@@ -1,216 +0,0 @@
-<% 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') -%>:&nbsp;<%= 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>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_set_instance.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_set_instance.html.erb
deleted file mode 100644 (file)
index 10d29f4..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<% 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 -%>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_settings.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_settings.html.erb
deleted file mode 100644 (file)
index 8ae1f0c..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-<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>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_single_value.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_single_value.html.erb
deleted file mode 100644 (file)
index f9a0bca..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<%= render "settings/type_#{property_type(property, value)}", :property => property, :field => nil, :value => value, :name => input_name(property), :id => "input_#{h property.key}" -%>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_special.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_special.html.erb
deleted file mode 100644 (file)
index afa5e1c..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<iframe src="<%= url -%>" width="100%" height="900" frameborder="0" style="overflow-y: auto;" name="settings_iframe"></iframe>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_BOOLEAN.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_BOOLEAN.html.erb
deleted file mode 100644 (file)
index 74e6c8c..0000000
+++ /dev/null
@@ -1 +0,0 @@
-<%= 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
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_FLOAT.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_FLOAT.html.erb
deleted file mode 100644 (file)
index 721480a..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<%
-   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
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_INTEGER.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_INTEGER.html.erb
deleted file mode 100644 (file)
index 5f7c39e..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<%
-   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
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_LICENSE.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_LICENSE.html.erb
deleted file mode 100644 (file)
index aeaa68e..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-<% 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 %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_METRIC.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_METRIC.html.erb
deleted file mode 100644 (file)
index 18e706b..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<%
-   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
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PASSWORD.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PASSWORD.html.erb
deleted file mode 100644 (file)
index d8c1556..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<%
-   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) %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PROPERTY_SET.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PROPERTY_SET.html.erb
deleted file mode 100644 (file)
index ae8159a..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<% 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
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PROPERTY_SET_DEFINITION.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_PROPERTY_SET_DEFINITION.html.erb
deleted file mode 100644 (file)
index beec593..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-<% 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>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_REGULAR_EXPRESSION.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_REGULAR_EXPRESSION.html.erb
deleted file mode 100644 (file)
index 8274fd8..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<%
-   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
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_SINGLE_SELECT_LIST.html.erb
deleted file mode 100644 (file)
index 4153c33..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-<%= 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
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_STRING.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_STRING.html.erb
deleted file mode 100644 (file)
index b28c756..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<%
-   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
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_TEXT.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_TEXT.html.erb
deleted file mode 100644 (file)
index af46cf4..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<%
-   options = {:id => id}
-   options[:size] = (defined? size) ? size : nil
-%>
-<%= property_input_field(name, PropertyType::TYPE_TEXT, value, PropertiesHelper::SCREEN_SETTINGS, options) %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_USER_LOGIN.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/settings/_type_USER_LOGIN.html.erb
deleted file mode 100644 (file)
index 6f94fa6..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-<%
-   options = {:id => id}
-   options[:size] = (defined? size) ? size : nil
-%>
-<%= property_input_field(name, PropertyType::TYPE_USER_LOGIN, value, PropertiesHelper::SCREEN_SETTINGS, options) %>
index 55ae2ad65ec4ec1f4c19f3b6c0103c9b6b9e3d91..6c42ad1d4b789a6b4db07e0be3abd82788245a11 100644 (file)
@@ -1,3 +1,3 @@
-<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 %>
diff --git a/server/sonar-web/tests/utils.js b/server/sonar-web/tests/utils.js
new file mode 100644 (file)
index 0000000..afa4f53
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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 }
+  });
+};
index 8656a2f2718785f93517953d281596b637add92f..3397de70d907e5d094532c0d1f3f84a7909b2630 100644 (file)
             <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>
index a21b3887b78dcbcd8f52fe155e6cf352377936d4..8b42dba8ba569f0891ba051d15f7a9c175e97b66 100644 (file)
@@ -147,6 +147,7 @@ search_verb=Search
 see_all=See All
 select_all=Select all
 select_verb=Select
+set=Set
 severity=Severity
 severity_abbreviated=Se.
 shared=Shared
@@ -967,6 +968,20 @@ dashboard.default_dashboard=This dashboard is the default one and is displayed w
 #------------------------------------------------------------------------------
 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