import it.http.HttpHeadersTest;
import it.projectComparison.ProjectComparisonTest;
import it.projectEvent.EventTest;
+import it.qualityProfile.QualityProfilesPageTest;
import it.serverSystem.ServerSystemTest;
import it.ui.UiTest;
import it.uiExtension.UiExtensionsTest;
// ui extensions
UiExtensionsTest.class,
WsLocalCallTest.class,
- WsTest.class
+ WsTest.class,
+ // quality profiles
+ QualityProfilesPageTest.class
})
public class Category4Suite {
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package it.qualityProfile;
+
+import com.sonar.orchestrator.Orchestrator;
+import com.sonar.orchestrator.build.SonarScanner;
+import com.sonar.orchestrator.selenium.Selenese;
+import it.Category4Suite;
+import org.junit.*;
+import org.junit.experimental.categories.Category;
+import org.sonarqube.ws.client.PostRequest;
+import org.sonarqube.ws.client.WsClient;
+import util.QaOnly;
+import util.selenium.SeleneseTest;
+
+import static util.ItUtils.newAdminWsClient;
+import static util.ItUtils.projectDir;
+
+@Category(QaOnly.class)
+public class QualityProfilesPageTest {
+
+ @ClassRule
+ public static Orchestrator orchestrator = Category4Suite.ORCHESTRATOR;
+ private static WsClient adminWsClient;
+
+ @BeforeClass
+ public static void setUp() {
+ adminWsClient = newAdminWsClient(orchestrator);
+ orchestrator.resetData();
+ }
+
+ @Before
+ public void createSampleProfile() {
+ createProfile("xoo", "sample");
+ inheritProfile("xoo", "sample", "Basic");
+ analyzeProject("shared/xoo-sample");
+ addProfileToProject("xoo", "sample", "sample");
+ }
+
+ @After
+ public void deleteSampleProfile() {
+ setDefault("xoo", "Basic");
+ deleteProfile("xoo", "sample");
+ deleteProfile("xoo", "new name");
+ }
+
+ @Test
+ public void testHomePage() throws Exception {
+ Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("test_home_page",
+ "/qualityProfile/QualityProfilesPageTest/should_display_list.html",
+ "/qualityProfile/QualityProfilesPageTest/should_open_from_list.html",
+ "/qualityProfile/QualityProfilesPageTest/should_filter_by_language.html"
+ ).build();
+ new SeleneseTest(selenese).runOn(orchestrator);
+ }
+
+ @Test
+ public void testProfilePage() throws Exception {
+ Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("test_profile_page",
+ "/qualityProfile/QualityProfilesPageTest/should_display_profile_rules.html",
+ "/qualityProfile/QualityProfilesPageTest/should_display_profile_inheritance.html",
+ "/qualityProfile/QualityProfilesPageTest/should_display_profile_projects.html",
+ "/qualityProfile/QualityProfilesPageTest/should_display_profile_exporters.html"
+ ).build();
+ new SeleneseTest(selenese).runOn(orchestrator);
+ }
+
+ @Test
+ public void testNotFound() {
+ Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("test_not_found",
+ "/qualityProfile/QualityProfilesPageTest/not_found.html").build();
+ orchestrator.executeSelenese(selenese);
+ }
+
+ @Test
+ public void testProfileChangelog() throws Exception {
+ Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("test_profile_changelog",
+ "/qualityProfile/QualityProfilesPageTest/should_display_changelog.html"
+ ).build();
+ new SeleneseTest(selenese).runOn(orchestrator);
+ }
+
+ @Ignore("find a way to know profile key inside selenium tests")
+ @Test
+ public void testComparison() throws Exception {
+ Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("test_comparison",
+ "/qualityProfile/QualityProfilesPageTest/should_compare.html"
+ ).build();
+ new SeleneseTest(selenese).runOn(orchestrator);
+ }
+
+ @Test
+ public void testCreation() throws Exception {
+ Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("test_creation",
+ "/qualityProfile/QualityProfilesPageTest/should_create.html"
+ ).build();
+ new SeleneseTest(selenese).runOn(orchestrator);
+ }
+
+ @Test
+ public void testDeletion() throws Exception {
+ Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("test_deletion",
+ "/qualityProfile/QualityProfilesPageTest/should_delete.html"
+ ).build();
+ new SeleneseTest(selenese).runOn(orchestrator);
+ }
+
+ @Test
+ public void testCopying() throws Exception {
+ Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("test_copying",
+ "/qualityProfile/QualityProfilesPageTest/should_copy.html"
+ ).build();
+ new SeleneseTest(selenese).runOn(orchestrator);
+ }
+
+ @Test
+ public void testRenaming() throws Exception {
+ Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("test_renaming",
+ "/qualityProfile/QualityProfilesPageTest/should_rename.html"
+ ).build();
+ new SeleneseTest(selenese).runOn(orchestrator);
+ }
+
+ @Test
+ public void testSettingDefault() throws Exception {
+ Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("test_setting_default",
+ "/qualityProfile/QualityProfilesPageTest/should_set_default.html"
+ ).build();
+ new SeleneseTest(selenese).runOn(orchestrator);
+ }
+
+ @Test
+ public void testRestoration() throws Exception {
+ deleteProfile("xoo", "empty");
+
+ Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("test_restoration",
+ "/qualityProfile/QualityProfilesPageTest/should_restore.html",
+ "/qualityProfile/QualityProfilesPageTest/should_restore_built_in.html"
+ ).build();
+ new SeleneseTest(selenese).runOn(orchestrator);
+ }
+
+ private static void createProfile(String language, String name) {
+ adminWsClient.wsConnector().call(
+ new PostRequest("api/qualityprofiles/create")
+ .setParam("language", language)
+ .setParam("name", name));
+ }
+
+ private static void inheritProfile(String language, String name, String parentName) {
+ adminWsClient.wsConnector().call(
+ new PostRequest("api/qualityprofiles/change_parent")
+ .setParam("language", language)
+ .setParam("profileName", name)
+ .setParam("parentName", parentName));
+ }
+
+ private static void analyzeProject(String path) {
+ orchestrator.executeBuild(SonarScanner.create(projectDir(path)));
+ }
+
+ private static void addProfileToProject(String language, String profileName, String projectKey) {
+ adminWsClient.wsConnector().call(
+ new PostRequest("api/qualityprofiles/add_project")
+ .setParam("language", language)
+ .setParam("profileName", profileName)
+ .setParam("projectKey", projectKey));
+ }
+
+ private static void deleteProfile(String language, String name) {
+ adminWsClient.wsConnector().call(
+ new PostRequest("api/qualityprofiles/delete")
+ .setParam("language", language)
+ .setParam("profileName", name));
+ }
+
+ private static void setDefault(String language, String name) {
+ adminWsClient.wsConnector().call(
+ new PostRequest("api/qualityprofiles/set_default")
+ .setParam("language", language)
+ .setParam("profileName", name));
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package it.qualityProfile;
-
-public class ToDoTest {
-}
<table cellpadding="1" cellspacing="1" border="1">
<tbody>
<tr>
- <td>open</td>
- <td>/sessions/logout</td>
- <td></td>
- </tr>
- <tr>
- <td>open</td>
- <td>/sessions/new</td>
- <td></td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=login</td>
- <td>not_profileadm</td>
- </tr>
- <tr>
- <td>type</td>
- <td>id=password</td>
- <td>userpwd</td>
- </tr>
- <tr>
- <td>clickAndWait</td>
- <td>name=commit</td>
- <td></td>
- </tr>
- <tr>
- <td>open</td>
- <td>/profiles</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>css=.quality-profiles-results</td>
- <td>*Basic*</td>
- </tr>
- <tr>
- <td>assertNotText</td>
- <td>css=.search-navigator-filters</td>
- <td>*Create*</td>
- </tr>
- <tr>
- <td>assertNotText</td>
- <td>css=.search-navigator-filters</td>
- <td>*Restore Profile*</td>
- </tr>
- <tr>
- <td>assertNotText</td>
- <td>css=.search-navigator-filters</td>
- <td>*Restore Built-in Profiles*</td>
- </tr>
- <tr>
- <td>open</td>
- <td>/project/profile/sample</td>
- <td></td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>id=content</td>
- <td>*Log In to SonarQube*</td>
- </tr>
- <tr>
- <td>waitForText</td>
- <td>id=login_form</td>
- <td>glob:*You are not authorized to access this page*</td>
- </tr>
- </tbody>
+ <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>id=login</td>
+ <td>not_profileadm</td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>id=password</td>
+ <td>userpwd</td>
+</tr>
+<tr>
+ <td>clickAndWait</td>
+ <td>name=commit</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-row[data-name="Basic"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profiles-table-row[data-name="Basic"] .quality-profiles-table-name a</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForText</td>
+ <td>css=.quality-profile-header</td>
+ <td>*Basic*</td>
+</tr>
+<tr>
+ <td>assertElementNotPresent</td>
+ <td>css=.js-change-parent</td>
+ <td></td>
+</tr>
+</tbody>
</table>
</body>
</html>
<td></td>
</tr>
<tr>
- <td>waitForText</td>
- <td>css=.quality-profiles-results</td>
- <td>*Basic*</td>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-row[data-name="Basic"]</td>
+ <td></td>
</tr>
<tr>
- <td>waitForText</td>
- <td>css=.search-navigator-filters</td>
- <td>*Create*</td>
+ <td>click</td>
+ <td>css=.quality-profiles-table-row[data-name="Basic"] .quality-profiles-table-name a</td>
+ <td></td>
</tr>
<tr>
- <td>waitForText</td>
- <td>css=.search-navigator-filters</td>
- <td>*Restore Profile*</td>
+ <td>waitForText</td>
+ <td>css=.quality-profile-header</td>
+ <td>*Basic*</td>
</tr>
<tr>
- <td>waitForText</td>
- <td>css=.search-navigator-filters</td>
- <td>*Restore Built-in Profiles*</td>
+ <td>assertElementPresent</td>
+ <td>css=.js-change-parent</td>
+ <td></td>
</tr>
<tr>
- <td>open</td>
- <td>/project/profile/sample</td>
- <td></td>
+ <td>open</td>
+ <td>/project/profile/sample</td>
+ <td></td>
</tr>
<tr>
- <td>waitForText</td>
- <td>id=content</td>
- <td>*Quality Profiles*</td>
+ <td>waitForText</td>
+ <td>id=content</td>
+ <td>*Quality Profiles*</td>
</tr>
<tr>
- <td>assertValue</td>
- <td>id=submit-xoo</td>
- <td>glob:*Update*</td>
+ <td>assertValue</td>
+ <td>id=submit-xoo</td>
+ <td>glob:*Update*</td>
</tr>
</tbody>
</table>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <title>not_found</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+ <tbody>
+ <tr>
+ <td>open</td>
+ <td>/profiles/show?key=unknown</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-not-found</td>
+ <td></td>
+ </tr>
+ </tbody>
+</table>
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_display_changelog</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_display_changelog</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-header .dropdown-toggle</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profile-header .dropdown-toggle</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=#quality-profile-compare</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.js-profile-comparison .Select</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.js-profile-comparison .Select-control</td>
+ <td></td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_create</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_create</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/sessions/logout</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/sessions/login</td>
+ <td></td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>id=password</td>
+ <td>admin</td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>id=login</td>
+ <td>admin</td>
+</tr>
+<tr>
+ <td>clickAndWait</td>
+ <td>name=commit</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-header .dropdown-toggle</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profile-header .dropdown-toggle</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=#quality-profile-copy</td>
+ <td></td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>css=#copy-profile-name</td>
+ <td>copied</td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=#copy-profile-submit</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForText</td>
+ <td>css=.quality-profile-header</td>
+ <td>*copied*</td>
+</tr>
+<tr>
+ <td>waitForText</td>
+ <td>css=.quality-profile-rules</td>
+ <td>*1*Bugs*0*Vulnerabilities*0*Code Smells*1*</td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-row[data-name="copied"]</td>
+ <td></td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_create</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_create</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/sessions/logout</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/sessions/login</td>
+ <td></td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>id=password</td>
+ <td>admin</td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>id=login</td>
+ <td>admin</td>
+</tr>
+<tr>
+ <td>clickAndWait</td>
+ <td>name=commit</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=#quality-profiles-create</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=#quality-profiles-create</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=#create-profile-name</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertElementPresent</td>
+ <td>css=#create-profile-form-backup-XooProfileImporter</td>
+ <td></td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>css=#create-profile-name</td>
+ <td>test</td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=#create-profile-submit</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-header</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForText</td>
+ <td>css=.quality-profile-header</td>
+ <td>*test*</td>
+</tr>
+<tr>
+ <td>waitForText</td>
+ <td>css=.quality-profile-rules</td>
+ <td>*Bugs*0*Vulnerabilities*0*Code Smells*0*</td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-row[data-name="test"]</td>
+ <td></td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_create</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_create</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/sessions/logout</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/sessions/login</td>
+ <td></td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>id=password</td>
+ <td>admin</td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>id=login</td>
+ <td>admin</td>
+</tr>
+<tr>
+ <td>clickAndWait</td>
+ <td>name=commit</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-header .dropdown-toggle</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profile-header .dropdown-toggle</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=#quality-profile-delete</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=#delete-profile-submit</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=#delete-profile-submit</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-row[data-name="Basic"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementNotPresent</td>
+ <td>css=.quality-profiles-table-row[data-name="sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-row[data-name="Basic"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertElementNotPresent</td>
+ <td>css=.quality-profiles-table-row[data-name="sample"]</td>
+ <td></td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_display_changelog</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_display_changelog</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-basic"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-basic"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=a[href^="/profiles/changelog?key=xoo-basic"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=a[href^="/profiles/changelog?key=xoo-basic"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.js-profile-changelog-event</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>css=.js-profile-changelog-event</td>
+ <td>*System*Activated*Has Tag*Major*tag*xoo*</td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_display_list</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_display_list</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertElementPresent</td>
+ <td>css=.quality-profiles-table[data-language="xoo"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertElementPresent</td>
+ <td>css=.quality-profiles-table[data-language="xoo2"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertElementPresent</td>
+ <td>css=.quality-profiles-table-row[data-key^="xoo-basic"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>css=.quality-profiles-table-row[data-key^="xoo-basic"] .quality-profiles-table-name</td>
+ <td>*Basic*</td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>css=.quality-profiles-table-row[data-key^="xoo-basic"] .quality-profiles-table-projects</td>
+ <td>*Default*</td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>css=.quality-profiles-table-row[data-key^="xoo-basic"] .quality-profiles-table-rules</td>
+ <td>*1*</td>
+</tr>
+<tr>
+ <td>assertElementPresent</td>
+ <td>css=.quality-profiles-table-row[data-key^="xoo-basic"] .quality-profiles-table-rules a[href^="/coding_rules#qprofile=xoo-basic"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>css=.quality-profiles-table-row[data-key^="xoo-empty"] .quality-profiles-table-projects</td>
+ <td>*0*</td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>css=.quality-profiles-table-row[data-key^="xoo2-basic"] .quality-profiles-table-name</td>
+ <td>*Basic*</td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_display_profile_projects.html</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_display_profile_projects.html</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-exporters</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-exporters [data-key="XooFakeExporter"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-exporters a[href^="/api/qualityprofiles/export"]</td>
+ <td></td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_display_profile_inheritance.html</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_display_profile_inheritance.html</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-inheritance</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.js-inheritance-ancestor</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>css=.js-inheritance-ancestor</td>
+ <td>*Basic*1*</td>
+</tr>
+<tr>
+ <td>assertElementPresent</td>
+ <td>css=.js-inheritance-current</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>css=.js-inheritance-current</td>
+ <td>*sample*1*</td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_display_profile_projects.html</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_display_profile_projects.html</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-projects</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.js-profile-project</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>css=.js-profile-project</td>
+ <td>*Sample*</td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_display_profile_rules.html</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_display_profile_rules.html</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-basic"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-basic"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-rules</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForText</td>
+ <td>css=.quality-profile-rules</td>
+ <td>*Active*1*Bugs*0*Vulnerabilities*0*Code Smells*1*</td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_filter_by_language</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_filter_by_language</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table[data-language="xoo"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertElementPresent</td>
+ <td>css=.quality-profiles-table[data-language="xoo2"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>css=.js-language-filter</td>
+ <td>*All*</td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.js-language-filter</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.js-language-filter-option[data-language="xoo2"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.js-language-filter-option[data-language="xoo2"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementNotPresent</td>
+ <td>css=.quality-profiles-table[data-language="xoo"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertElementPresent</td>
+ <td>css=.quality-profiles-table[data-language="xoo2"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>css=.js-language-filter</td>
+ <td>*Xoo2*</td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/profiles?language=xoo2</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table[data-language="xoo2"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertElementNotPresent</td>
+ <td>css=.quality-profiles-table[data-language="xoo"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>css=.js-language-filter</td>
+ <td>*Xoo2*</td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_open_from_list</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_open_from_list</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-row[data-key^="xoo-basic"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profiles-table-row[data-key^="xoo-basic"] .quality-profiles-table-name a</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-header</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-rules</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-inheritance</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-projects</td>
+ <td></td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_create</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_create</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/sessions/logout</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/sessions/login</td>
+ <td></td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>id=password</td>
+ <td>admin</td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>id=login</td>
+ <td>admin</td>
+</tr>
+<tr>
+ <td>clickAndWait</td>
+ <td>name=commit</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-header .dropdown-toggle</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profile-header .dropdown-toggle</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=#quality-profile-rename</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=#rename-profile-name</td>
+ <td></td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>css=#rename-profile-name</td>
+ <td>new name</td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=#rename-profile-submit</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForText</td>
+ <td>css=.quality-profile-header</td>
+ <td>*new name*</td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertElementPresent</td>
+ <td>css=.quality-profiles-table-row[data-name="new name"]</td>
+ <td></td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_create</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_create</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/sessions/logout</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/sessions/login</td>
+ <td></td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>id=password</td>
+ <td>admin</td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>id=login</td>
+ <td>admin</td>
+</tr>
+<tr>
+ <td>clickAndWait</td>
+ <td>name=commit</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=#quality-profiles-restore</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=#quality-profiles-restore</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=#restore-profile-backup</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=#restore-profile-submit</td>
+ <td></td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_create</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_create</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/sessions/logout</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/sessions/login</td>
+ <td></td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>id=password</td>
+ <td>admin</td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>id=login</td>
+ <td>admin</td>
+</tr>
+<tr>
+ <td>clickAndWait</td>
+ <td>name=commit</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.js-restore-built-in[data-language="xoo"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertElementNotPresent</td>
+ <td>css=.quality-profiles-table-row[data-name="empty"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.js-restore-built-in[data-language="xoo"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=#restore-built-in-profiles-submit</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=#restore-built-in-profiles-submit</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=#restore-built-in-profiles-form .alert-success</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.js-modal-close</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-row[data-name="empty"]</td>
+ <td></td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <link rel="selenium.base" href="http://localhost:49506"/>
+ <title>should_create</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+<td rowspan="1" colspan="3">should_create</td>
+</tr>
+</thead>
+<tbody>
+<tr>
+ <td>open</td>
+ <td>/sessions/logout</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/sessions/login</td>
+ <td></td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>id=password</td>
+ <td>admin</td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>id=login</td>
+ <td>admin</td>
+</tr>
+<tr>
+ <td>clickAndWait</td>
+ <td>name=commit</td>
+ <td></td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profiles-table-name a[href^="/profiles/show?key=xoo-sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profile-header .dropdown-toggle</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=.quality-profile-header .dropdown-toggle</td>
+ <td></td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>css=#quality-profile-set-as-default</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForText</td>
+ <td>css=.quality-profile-projects</td>
+ <td>*Default*</td>
+</tr>
+<tr>
+ <td>open</td>
+ <td>/profiles</td>
+ <td></td>
+</tr>
+<tr>
+ <td>waitForElementPresent</td>
+ <td>css=.quality-profiles-table-row[data-name="sample"]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>assertText</td>
+ <td>css=.quality-profiles-table-row[data-name="sample"] .quality-profiles-table-projects</td>
+ <td>*Default*</td>
+</tr>
+</tbody>
+</table>
+</body>
+</html>
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { request, checkStatus, parseJSON } from '../helpers/request';
+import {
+ request,
+ checkStatus,
+ parseJSON,
+ getJSON,
+ post,
+ postJSON
+} from '../helpers/request';
+
+export function getQualityProfiles () {
+ const url = '/api/qualityprofiles/search';
+ return getJSON(url).then(r => r.profiles);
+}
export function createQualityProfile (data) {
return request('/api/qualityprofiles/create')
.then(checkStatus)
.then(parseJSON);
}
+
+export function getProfileProjects (data) {
+ const url = '/api/qualityprofiles/projects';
+ return getJSON(url, data);
+}
+
+export function getProfileInheritance (profileKey) {
+ const url = '/api/qualityprofiles/inheritance';
+ const data = { profileKey };
+ return getJSON(url, data);
+}
+
+export function setDefaultProfile (profileKey) {
+ const url = '/api/qualityprofiles/set_default';
+ const data = { profileKey };
+ return post(url, data);
+}
+
+/**
+ * Rename profile
+ * @param {string} key
+ * @param {string} name
+ * @returns {Promise}
+ */
+export function renameProfile (key, name) {
+ const url = '/api/qualityprofiles/rename';
+ const data = { key, name };
+ return post(url, data);
+}
+
+/**
+ * Copy profile
+ * @param {string} fromKey
+ * @param {string} toName
+ * @returns {Promise}
+ */
+export function copyProfile (fromKey, toName) {
+ const url = '/api/qualityprofiles/copy';
+ const data = { fromKey, toName };
+ return postJSON(url, data);
+}
+
+/**
+ * Delete profile
+ * @param {string} profileKey
+ * @returns {Promise}
+ */
+export function deleteProfile (profileKey) {
+ const url = '/api/qualityprofiles/delete';
+ const data = { profileKey };
+ return post(url, data);
+}
+
+/**
+ * Change profile parent
+ * @param {string} profileKey
+ * @param {string} parentKey
+ * @returns {Promise}
+ */
+export function changeProfileParent (profileKey, parentKey) {
+ const url = '/api/qualityprofiles/change_parent';
+ const data = { profileKey, parentKey };
+ return post(url, data);
+}
+
+/**
+ * Get list of available importers
+ * @returns {Promise}
+ */
+export function getImporters () {
+ const url = '/api/qualityprofiles/importers';
+ return getJSON(url).then(r => r.importers);
+}
+
+/**
+ * Get list of available exporters
+ * @returns {Promise}
+ */
+export function getExporters () {
+ const url = '/api/qualityprofiles/exporters';
+ return getJSON(url).then(r => r.exporters);
+}
+
+/**
+ * Restore built-in profiles
+ * @param {string} languageKey
+ * @returns {Promise}
+ */
+export function restoreBuiltInProfiles (languageKey) {
+ const url = '/api/qualityprofiles/restore_built_in';
+ const data = { language: languageKey };
+ return post(url, data);
+}
+
+/**
+ * Get changelog of a quality profile
+ * @param {Object} data API parameters
+ * @returns {Promise}
+ */
+export function getProfileChangelog (data) {
+ const url = '/api/qualityprofiles/changelog';
+ return getJSON(url, data);
+}
+
+/**
+ * Compare two profiles
+ * @param {string} leftKey
+ * @param {string} rightKey
+ * @returns {Promise}
+ */
+export function compareProfiles (leftKey, rightKey) {
+ const url = '/api/qualityprofiles/compare';
+ const data = { leftKey, rightKey };
+ return getJSON(url, data);
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { getJSON } from '../helpers/request';
+
+export function searchRules (data) {
+ const url = '/api/rules/search';
+ return getJSON(url, data);
+}
+
+export function takeFacet (response, property) {
+ const facet = response.facets.find(facet => facet.property === property);
+ return facet ? facet.values : [];
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { expect } from 'chai';
+import { sortProfiles } from '../utils';
+
+function createProfile (key, parentKey) {
+ return { name: key, key, parentKey };
+}
+
+function checkOrder (list, order) {
+ const listKeys = list.map(item => item.key);
+ expect(listKeys).to.deep.equal(order);
+}
+
+describe('Quality Profiles :: Utils', () => {
+ describe('#sortProfiles', () => {
+ it('should sort when no parents', () => {
+ const profile1 = createProfile('profile1');
+ const profile2 = createProfile('profile2');
+ const profile3 = createProfile('profile3');
+ checkOrder(
+ sortProfiles([profile1, profile2, profile3]),
+ ['profile1', 'profile2', 'profile3']
+ );
+ });
+
+ it('should sort by name', () => {
+ const profile1 = createProfile('profile1');
+ const profile2 = createProfile('profile2');
+ const profile3 = createProfile('profile3');
+ checkOrder(
+ sortProfiles([profile3, profile1, profile2]),
+ ['profile1', 'profile2', 'profile3']
+ );
+ });
+
+ it('should sort with children', () => {
+ const child1 = createProfile('child1', 'parent');
+ const child2 = createProfile('child2', 'parent');
+ const parent = createProfile('parent');
+ checkOrder(
+ sortProfiles([child1, child2, parent]),
+ ['parent', 'child1', 'child2']
+ );
+ });
+
+ it('should sort single branch', () => {
+ const profile1 = createProfile('profile1');
+ const profile2 = createProfile('profile2', 'profile3');
+ const profile3 = createProfile('profile3', 'profile1');
+ checkOrder(
+ sortProfiles([profile3, profile2, profile1]),
+ ['profile1', 'profile3', 'profile2']
+ );
+ });
+ });
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import _ from 'underscore';
-import Marionette from 'backbone.marionette';
-import CreateProfileView from './create-profile-view';
-import RestoreProfileView from './restore-profile-view';
-import RestoreBuiltInProfilesView from './restore-built-in-profiles-view';
-import Template from './templates/quality-profiles-actions.hbs';
-
-export default Marionette.ItemView.extend({
- template: Template,
-
- events: {
- 'click #quality-profiles-create': 'onCreateClick',
- 'click #quality-profiles-restore': 'onRestoreClick',
- 'click #quality-profiles-restore-built-in': 'onRestoreBuiltInClick',
-
- 'click .js-filter-by-language': 'onLanguageClick'
- },
-
- onCreateClick (e) {
- e.preventDefault();
- this.create();
- },
-
- onRestoreClick (e) {
- e.preventDefault();
- this.restore();
- },
-
- onRestoreBuiltInClick (e) {
- e.preventDefault();
- this.restoreBuiltIn();
- },
-
- onLanguageClick (e) {
- e.preventDefault();
- const language = $(e.currentTarget).data('language');
- this.filterByLanguage(language);
- },
-
- create () {
- const that = this;
- this.requestImporters().done(function () {
- new CreateProfileView({
- collection: that.collection,
- languages: that.languages,
- importers: that.importers
- }).render();
- });
- },
-
- restore () {
- new RestoreProfileView({
- collection: this.collection
- }).render();
- },
-
- restoreBuiltIn () {
- new RestoreBuiltInProfilesView({
- collection: this.collection,
- languages: this.languages
- }).render();
- },
-
- requestLanguages () {
- const that = this;
- const url = window.baseUrl + '/api/languages/list';
- return $.get(url).done(function (r) {
- that.languages = r.languages;
- });
- },
-
- requestImporters () {
- const that = this;
- const url = window.baseUrl + '/api/qualityprofiles/importers';
- return $.get(url).done(function (r) {
- that.importers = r.importers;
- });
- },
-
- filterByLanguage (language) {
- this.selectedLanguage = _.findWhere(this.languages, { key: language });
- this.render();
- this.collection.trigger('filter', language);
- },
-
- serializeData () {
- return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
- canWrite: this.options.canWrite,
- languages: this.languages,
- selectedLanguage: this.selectedLanguage
- });
- }
-});
-
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import $ from 'jquery';
-import Backbone from 'backbone';
-import Marionette from 'backbone.marionette';
-import Router from './router';
-import Controller from './controller';
-import Layout from './layout';
-import Profiles from './profiles';
-import ActionsView from './actions-view';
-import ProfilesView from './profiles-view';
-
-const App = new Marionette.Application();
-const requestUser = $.get(window.baseUrl + '/api/users/current').done(function (r) {
- App.canWrite = r.permissions.global.indexOf('profileadmin') !== -1;
-});
-const requestExporters = $.get(window.baseUrl + '/api/qualityprofiles/exporters').done(function (r) {
- App.exporters = r.exporters;
-});
-const init = function () {
- const options = window.sonarqube;
-
- // Layout
- this.layout = new Layout({ el: options.el });
- this.layout.render();
- $('#footer').addClass('search-navigator-footer');
-
- // Profiles List
- this.profiles = new Profiles();
-
- // Controller
- this.controller = new Controller({ app: this });
-
- // Actions View
- this.actionsView = new ActionsView({
- collection: this.profiles,
- canWrite: this.canWrite
- });
- this.actionsView.requestLanguages().done(function () {
- App.layout.actionsRegion.show(App.actionsView);
+import React from 'react';
+import { render } from 'react-dom';
+import {
+ Router,
+ Route,
+ IndexRoute,
+ Redirect,
+ useRouterHistory
+} from 'react-router';
+import { createHistory } from 'history';
+import App from './components/App';
+import ProfileContainer from './components/ProfileContainer';
+import HomeContainer from './home/HomeContainer';
+import ProfileDetails from './details/ProfileDetails';
+import ChangelogContainer from './changelog/ChangelogContainer';
+import ComparisonContainer from './compare/ComparisonContainer';
+
+window.sonarqube.appStarted.then(options => {
+ const el = document.querySelector(options.el);
+
+ const history = useRouterHistory(createHistory)({
+ basename: window.baseUrl + '/profiles'
});
- // Profiles View
- this.profilesView = new ProfilesView({
- collection: this.profiles,
- canWrite: this.canWrite
- });
- this.layout.resultsRegion.show(this.profilesView);
-
- // Router
- this.router = new Router({ app: this });
- Backbone.history.start({
- pushState: true,
- root: options.urlRoot
- });
-};
-
-App.on('start', function () {
- $.when(requestUser, requestExporters).done(function () {
- init.call(App);
- });
+ render((
+ <Router history={history}>
+ <Route path="/" component={App}>
+ <Redirect from="/index" to="/"/>
+
+ <IndexRoute component={HomeContainer}/>
+
+ <Route component={ProfileContainer}>
+ <Route path="show" component={ProfileDetails}/>
+ <Route path="changelog" component={ChangelogContainer}/>
+ <Route path="compare" component={ComparisonContainer}/>
+ </Route>
+ </Route>
+ </Router>
+ ), el);
});
-
-window.sonarqube.appStarted.then(options => App.start(options));
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import _ from 'underscore';
-import Marionette from 'backbone.marionette';
-import ModalFormView from '../../components/common/modal-form';
-import Template from './templates/quality-profiles-change-profile-parent.hbs';
-
-export default ModalFormView.extend({
- template: Template,
-
- onRender () {
- ModalFormView.prototype.onRender.apply(this, arguments);
- this.$('select').select2({
- width: '250px',
- minimumResultsForSearch: 50
- });
- },
-
- onFormSubmit () {
- ModalFormView.prototype.onFormSubmit.apply(this, arguments);
- this.disableForm();
- this.sendRequest();
- },
-
- sendRequest () {
- const that = this;
- const url = window.baseUrl + '/api/qualityprofiles/change_parent';
- const parent = this.$('#change-profile-parent').val();
- const options = {
- profileKey: this.model.get('key'),
- parentKey: parent
- };
- return $.ajax({
- url,
- type: 'POST',
- data: options,
- statusCode: {
- // do not show global error
- 400: null
- }
- }).done(function () {
- that.model.collection.fetch();
- that.model.trigger('select', that.model);
- that.destroy();
- }).fail(function (jqXHR) {
- that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
- that.enableForm();
- });
- },
-
- serializeData () {
- const that = this;
- const profilesData = this.model.collection.toJSON();
- const profiles = _.filter(profilesData, function (profile) {
- return profile.language === that.model.get('language') && profile.key !== that.model.id;
- });
- return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
- profiles
- });
- }
-});
-
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import moment from 'moment';
+import ChangesList from './ChangesList';
+import { translate } from '../../../helpers/l10n';
+import { getRulesUrl } from '../../../helpers/urls';
+
+export default class Changelog extends React.Component {
+ static propTypes = {
+ events: React.PropTypes.array.isRequired
+ };
+
+ render () {
+ const rows = this.props.events.map((event, index) => (
+ <tr key={index} className="js-profile-changelog-event">
+ <td className="thin nowrap">
+ {moment(event.date).format('LLL')}
+ </td>
+
+ <td className="thin nowrap">
+ {event.authorName ? (
+ <span>{event.authorName}</span>
+ ) : (
+ <span className="note">System</span>
+ )}
+ </td>
+
+ <td className="thin nowrap">
+ {translate('quality_profiles.changelog', event.action)}
+ </td>
+
+ <td style={{ lineHeight: '1.5' }}>
+ <a href={getRulesUrl({ 'rule_key': event.ruleKey })}>
+ {event.ruleName}
+ </a>
+ </td>
+
+ <td className="thin nowrap">
+ <ChangesList changes={event.params}/>
+ </td>
+ </tr>
+ ));
+
+ return (
+ <table className="data zebra zebra-hover">
+ <thead>
+ <tr>
+ <th className="thin nowrap">
+ {translate('date')}
+ {' '}
+ <i className="icon-sort-desc"/>
+ </th>
+ <th className="thin nowrap">{translate('user')}</th>
+ <th className="thin nowrap">{translate('action')}</th>
+ <th>{translate('rule')}</th>
+ <th className="thin nowrap">{translate('parameters')}</th>
+ </tr>
+ </thead>
+ <tbody>{rows}</tbody>
+ </table>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import Changelog from './Changelog';
+import ChangelogSearch from './ChangelogSearch';
+import ChangelogEmpty from './ChangelogEmpty';
+import { getProfileChangelog } from '../../../api/quality-profiles';
+import { ProfileType } from '../propTypes';
+
+export default class ChangelogContainer extends React.Component {
+ static propTypes = {
+ location: React.PropTypes.object.isRequired,
+ profile: ProfileType
+ };
+
+ static contextTypes = {
+ router: React.PropTypes.object
+ };
+
+ state = {
+ loading: true
+ };
+
+ componentWillMount () {
+ this.handleFromDateChange = this.handleFromDateChange.bind(this);
+ this.handleToDateChange = this.handleToDateChange.bind(this);
+ this.handleReset = this.handleReset.bind(this);
+ }
+
+ componentDidMount () {
+ this.mounted = true;
+ this.loadChangelog();
+ }
+
+ componentDidUpdate (prevProps) {
+ if (prevProps.location !== this.props.location) {
+ this.loadChangelog();
+ }
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ loadChangelog () {
+ this.setState({ loading: true });
+ const { query } = this.props.location;
+ const data = { profileKey: this.props.profile.key };
+ if (query.since) {
+ data.since = query.since;
+ }
+ if (query.to) {
+ data.to = query.to;
+ }
+
+ getProfileChangelog(data).then(r => {
+ if (this.mounted) {
+ this.setState({
+ events: r.events,
+ total: r.total,
+ page: r.p,
+ loading: false
+ });
+ }
+ });
+ }
+
+ handleFromDateChange (fromDate) {
+ const query = { ...this.props.location.query, since: fromDate };
+ this.context.router.push({ pathname: '/changelog', query });
+ }
+
+ handleToDateChange (toDate) {
+ const query = { ...this.props.location.query, to: toDate };
+ this.context.router.push({ pathname: '/changelog', query });
+ }
+
+ handleReset () {
+ const query = { key: this.props.profile.key };
+ this.context.router.push({ pathname: '/changelog', query });
+ }
+
+ render () {
+ const { query } = this.props.location;
+
+ return (
+ <div className="quality-profile-box js-profile-changelog">
+ <header className="spacer-bottom">
+ <ChangelogSearch
+ fromDate={query.since}
+ toDate={query.to}
+ onFromDateChange={this.handleFromDateChange}
+ onToDateChange={this.handleToDateChange}
+ onReset={this.handleReset}/>
+
+ {this.state.loading && (
+ <i className="spinner spacer-left"/>
+ )}
+ </header>
+
+ {this.state.events != null && this.state.events.length === 0 && (
+ <ChangelogEmpty/>
+ )}
+
+ {this.state.events != null && this.state.events.length > 0 && (
+ <Changelog events={this.state.events}/>
+ )}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { translate } from '../../../helpers/l10n';
+
+export default class ChangelogEmpty extends React.Component {
+ render () {
+ return (
+ <div className="big-spacer-top">
+ {translate('no_results')}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import DateInput from '../../../components/controls/DateInput';
+import { translate } from '../../../helpers/l10n';
+
+export default class ChangelogSearch extends React.Component {
+ static propTypes = {
+ fromDate: React.PropTypes.string,
+ toDate: React.PropTypes.string,
+ onFromDateChange: React.PropTypes.func.isRequired,
+ onToDateChange: React.PropTypes.func.isRequired,
+ onReset: React.PropTypes.func.isRequired
+ };
+
+ handleResetClick (e) {
+ e.preventDefault();
+ e.target.blur();
+ this.props.onReset();
+ }
+
+ render () {
+ return (
+ <div className="display-inline-block"
+ id="quality-profile-changelog-form">
+ <DateInput
+ name="since"
+ value={this.props.fromDate}
+ placeholder="From"
+ onChange={this.props.onFromDateChange}/>
+ {' — '}
+ <DateInput
+ name="to"
+ value={this.props.toDate}
+ placeholder="To"
+ onChange={this.props.onToDateChange}/>
+ <button
+ className="spacer-left"
+ onClick={this.handleResetClick.bind(this)}>
+ {translate('reset_verb')}
+ </button>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import SeverityChange from './SeverityChange';
+import ParameterChange from './ParameterChange';
+
+export default class ChangesList extends React.Component {
+ static propTypes = {
+ changes: React.PropTypes.object.isRequired
+ };
+
+ render () {
+ const { changes } = this.props;
+
+ return (
+ <ul>
+ {Object.keys(changes).map(key => (
+ <li key={key}>
+ {key === 'severity' ? (
+ <SeverityChange severity={changes[key]}/>
+ ) : (
+ <ParameterChange name={key} value={changes[key]}/>
+ )}
+ </li>
+ ))}
+ </ul>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { translateWithParameters } from '../../../helpers/l10n';
+
+export default class ParameterChange extends React.Component {
+ static propTypes = {
+ name: React.PropTypes.string.isRequired,
+ value: React.PropTypes.any
+ };
+
+ render () {
+ const { name, value } = this.props;
+
+ if (value == null) {
+ return (
+ <div>
+ {translateWithParameters(
+ 'quality_profiles.changelog.parameter_reset_to_default_value',
+ name
+ )}
+ </div>
+ );
+ }
+
+ return (
+ <div>
+ {translateWithParameters(
+ 'quality_profiles.parameter_set_to',
+ name,
+ value
+ )}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import SeverityHelper from '../../../components/shared/severity-helper';
+import { translate } from '../../../helpers/l10n';
+
+export default class SeverityChange extends React.Component {
+ static propTypes = {
+ severity: React.PropTypes.string.isRequired
+ };
+
+ render () {
+ return (
+ <div>
+ {translate('quality_profiles.severity_set_to')}
+ {' '}
+ <SeverityHelper severity={this.props.severity}/>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { expect } from 'chai';
+import { shallow } from 'enzyme';
+import React from 'react';
+import Changelog from '../Changelog';
+import ChangesList from '../ChangesList';
+
+function createEvent (overrides) {
+ return {
+ date: '2016-01-01',
+ authorName: 'John',
+ action: 'ACTIVATED',
+ ruleKey: 'squid1234',
+ ruleName: 'Do not do this',
+ params: {},
+ ...overrides
+ };
+}
+
+describe('Quality Profiles :: Changelog', () => {
+ it('should render events', () => {
+ const events = [createEvent(), createEvent()];
+ const changelog = shallow(<Changelog events={events}/>);
+ expect(changelog.find('tbody').find('tr')).to.have.length(2);
+ });
+
+ it('should render event date', () => {
+ const events = [createEvent()];
+ const changelog = shallow(<Changelog events={events}/>);
+ expect(changelog.text()).to.include('2016');
+ });
+
+ it('should render author', () => {
+ const events = [createEvent()];
+ const changelog = shallow(<Changelog events={events}/>);
+ expect(changelog.text()).to.include('John');
+ });
+
+ it('should render system author', () => {
+ const events = [createEvent({ authorName: undefined })];
+ const changelog = shallow(<Changelog events={events}/>);
+ expect(changelog.text()).to.include('System');
+ });
+
+ it('should render action', () => {
+ const events = [createEvent()];
+ const changelog = shallow(<Changelog events={events}/>);
+ expect(changelog.text()).to.include('ACTIVATED');
+ });
+
+ it('should render rule', () => {
+ const events = [createEvent()];
+ const changelog = shallow(<Changelog events={events}/>);
+ expect(changelog.text()).to.include('Do not do this');
+ expect(changelog.find('a').prop('href')).to.include('rule_key=squid1234');
+ });
+
+ it('should render ChangesList', () => {
+ const params = { severity: 'BLOCKER' };
+ const events = [createEvent({ params })];
+ const changelog = shallow(<Changelog events={events}/>);
+ const changesList = changelog.find(ChangesList);
+ expect(changesList).to.have.length(1);
+ expect(changesList.prop('changes')).to.equal(params);
+ });
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { expect } from 'chai';
+import { shallow } from 'enzyme';
+import sinon from 'sinon';
+import React from 'react';
+import ChangelogSearch from '../ChangelogSearch';
+import DateInput from '../../../../components/controls/DateInput';
+
+function click (element) {
+ return element.simulate('click', {
+ target: { blur () {} },
+ preventDefault () {}
+ });
+}
+
+describe('Quality Profiles :: ChangelogSearch', () => {
+ it('should render DateInput', () => {
+ const onFromDateChange = sinon.spy();
+ const onToDateChange = sinon.spy();
+ const output = shallow(
+ <ChangelogSearch
+ fromDate="2016-01-01"
+ toDate="2016-05-05"
+ onFromDateChange={onFromDateChange}
+ onToDateChange={onToDateChange}
+ onReset={sinon.spy()}/>
+ );
+ const dateInputs = output.find(DateInput);
+ expect(dateInputs).to.have.length(2);
+ expect(dateInputs.at(0).prop('value')).to.equal('2016-01-01');
+ expect(dateInputs.at(0).prop('onChange')).to.equal(onFromDateChange);
+ expect(dateInputs.at(1).prop('value')).to.equal('2016-05-05');
+ expect(dateInputs.at(1).prop('onChange')).to.equal(onToDateChange);
+ });
+
+ it('should reset', () => {
+ const onReset = sinon.spy();
+ const output = shallow(
+ <ChangelogSearch
+ fromDate="2016-01-01"
+ toDate="2016-05-05"
+ onFromDateChange={sinon.spy()}
+ onToDateChange={sinon.spy()}
+ onReset={onReset}/>
+ );
+ expect(onReset.called).to.equal(false);
+ click(output.find('button'));
+ expect(onReset.called).to.equal(true);
+ });
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { expect } from 'chai';
+import { shallow } from 'enzyme';
+import React from 'react';
+import ChangesList from '../ChangesList';
+import SeverityChange from '../SeverityChange';
+import ParameterChange from '../ParameterChange';
+
+describe('Quality Profiles :: ChangesList', () => {
+ it('should render changes', () => {
+ const changes = { severity: 'BLOCKER', foo: 'bar' };
+ const output = shallow(
+ <ChangesList changes={changes}/>
+ );
+ expect(output.find('li')).to.have.length(2);
+ });
+
+ it('should render severity change', () => {
+ const changes = { severity: 'BLOCKER' };
+ const output = shallow(
+ <ChangesList changes={changes}/>
+ ).find(SeverityChange);
+ expect(output).to.have.length(1);
+ expect(output.prop('severity')).to.equal('BLOCKER');
+ });
+
+ it('should render parameter change', () => {
+ const changes = { foo: 'bar' };
+ const output = shallow(
+ <ChangesList changes={changes}/>
+ ).find(ParameterChange);
+ expect(output).to.have.length(1);
+ expect(output.prop('name')).to.equal('foo');
+ expect(output.prop('value')).to.equal('bar');
+ });
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { expect } from 'chai';
+import { shallow } from 'enzyme';
+import React from 'react';
+import ParameterChange from '../ParameterChange';
+
+describe('Quality Profiles :: ParameterChange', () => {
+ it('should render different messages', () => {
+ const first = shallow(<ParameterChange name="foo"/>);
+ const second = shallow(<ParameterChange name="foo" value="bar"/>);
+ expect(first.text()).to.not.be.equal(second.text());
+ });
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { expect } from 'chai';
+import { shallow } from 'enzyme';
+import React from 'react';
+import SeverityChange from '../SeverityChange';
+import SeverityHelper from '../../../../components/shared/severity-helper';
+
+describe('Quality Profiles :: SeverityChange', () => {
+ it('should render SeverityHelper', () => {
+ const output = shallow(
+ <SeverityChange severity="BLOCKER"/>
+ ).find(SeverityHelper);
+ expect(output).to.have.length(1);
+ expect(output.prop('severity')).to.equal('BLOCKER');
+ });
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import ComparisonForm from './ComparisonForm';
+import ComparisonResults from './ComparisonResults';
+import { ProfileType, ProfilesListType } from '../propTypes';
+import { compareProfiles } from '../../../api/quality-profiles';
+
+export default class ComparisonContainer extends React.Component {
+ static propTypes = {
+ profile: ProfileType,
+ profiles: ProfilesListType
+ };
+
+ static contextTypes = {
+ router: React.PropTypes.object
+ };
+
+ state = {
+ loading: false
+ };
+
+ componentWillMount () {
+ this.handleCompare = this.handleCompare.bind(this);
+ }
+
+ componentDidMount () {
+ this.mounted = true;
+ this.loadResults();
+ }
+
+ componentDidUpdate (prevProps) {
+ if (prevProps.profile !== this.props.profile ||
+ prevProps.location !== this.props.location) {
+ this.loadResults();
+ }
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ loadResults () {
+ const { withKey } = this.props.location.query;
+ if (!withKey) {
+ this.setState({ left: null, loading: false });
+ return;
+ }
+
+ this.setState({ loading: true });
+ compareProfiles(this.props.profile.key, withKey).then(r => {
+ if (this.mounted) {
+ this.setState({
+ left: r.left,
+ right: r.right,
+ inLeft: r.inLeft,
+ inRight: r.inRight,
+ modified: r.modified,
+ loading: false
+ });
+ }
+ });
+ }
+
+ handleCompare (withKey) {
+ this.context.router.push({
+ pathname: '/compare',
+ query: {
+ key: this.props.profile.key,
+ withKey
+ }
+ });
+ }
+
+ render () {
+ const { profile, profiles, location } = this.props;
+ const { withKey } = location.query;
+ const { left, right, inLeft, inRight, modified } = this.state;
+
+ return (
+ <div className="quality-profile-box js-profile-comparison">
+ <header className="spacer-bottom">
+ <ComparisonForm
+ withKey={withKey}
+ profile={profile}
+ profiles={profiles}
+ onCompare={this.handleCompare}/>
+
+ {this.state.loading && (
+ <i className="spinner spacer-left"/>
+ )}
+ </header>
+
+ {left != null && (
+ <ComparisonResults
+ left={left}
+ right={right}
+ inLeft={inLeft}
+ inRight={inRight}
+ modified={modified}/>
+ )}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { translate } from '../../../helpers/l10n';
+
+export default class ComparisonEmpty extends React.Component {
+ render () {
+ return (
+ <div className="big-spacer-top">
+ {translate('quality_profile.empty_comparison')}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import Select from 'react-select';
+import { ProfileType, ProfilesListType } from '../propTypes';
+import { translate } from '../../../helpers/l10n';
+
+export default class ComparisonForm extends React.Component {
+ static propTypes = {
+ profile: ProfileType.isRequired,
+ profiles: ProfilesListType.isRequired,
+ onCompare: React.PropTypes.func.isRequired
+ };
+
+ handleChange (option) {
+ this.props.onCompare(option.value);
+ }
+
+ render () {
+ const { profile, profiles, withKey } = this.props;
+ const options = profiles
+ .filter(p => p.language === profile.language && p !== profile)
+ .map(p => ({ value: p.key, label: p.name }));
+
+ return (
+ <div className="display-inline-block">
+ <label className="spacer-right">
+ {translate('quality_profiles.compare_with')}
+ </label>
+ <Select
+ value={withKey}
+ options={options}
+ clearable={false}
+ className="input-large"
+ onChange={this.handleChange.bind(this)}/>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import ComparisonEmpty from './ComparisonEmpty';
+import SeverityIcon from '../../../components/shared/severity-icon';
+import { translateWithParameters } from '../../../helpers/l10n';
+import { getRulesUrl } from '../../../helpers/urls';
+
+export default class ComparisonResults extends React.Component {
+ static propTypes = {
+ left: React.PropTypes.shape({
+ name: React.PropTypes.string.isRequired
+ }).isRequired,
+ right: React.PropTypes.shape({
+ name: React.PropTypes.string.isRequired
+ }).isRequired,
+ inLeft: React.PropTypes.array.isRequired,
+ inRight: React.PropTypes.array.isRequired,
+ modified: React.PropTypes.array.isRequired
+ };
+
+ renderRule (rule, severity) {
+ return (
+ <div>
+ <SeverityIcon severity={severity}/>
+ {' '}
+ <a href={getRulesUrl({ 'rule_key': rule.key })}>
+ {rule.name}
+ </a>
+ </div>
+ );
+ }
+
+ renderParameters (params) {
+ if (!params) {
+ return null;
+ }
+ return (
+ <ul>
+ {Object.keys(params).map(key => (
+ <li key={key} className="spacer-top">
+ <code>{key}{': '}{params[key]}</code>
+ </li>
+ ))}
+ </ul>
+ );
+ }
+
+ renderLeft () {
+ if (this.props.inLeft.length === 0) {
+ return null;
+ }
+ const header = (
+ <tr key="left-header">
+ <td>
+ <h6>
+ {translateWithParameters(
+ 'quality_profiles.x_rules_only_in',
+ this.props.inLeft.length
+ )}
+ {' '}
+ {this.props.left.name}
+ </h6>
+ </td>
+ <td> </td>
+ </tr>
+ );
+ const rows = this.props.inLeft.map(rule => (
+ <tr key={`left-${rule.key}`} className="js-comparison-in-left">
+ <td>{this.renderRule(rule, rule.severity)}</td>
+ <td> </td>
+ </tr>
+ ));
+ return [header, ...rows];
+ }
+
+ renderRight () {
+ if (this.props.inRight.length === 0) {
+ return null;
+ }
+ const header = (
+ <tr key="right-header">
+ <td> </td>
+ <td>
+ <h6>
+ {translateWithParameters(
+ 'quality_profiles.x_rules_only_in',
+ this.props.inRight.length
+ )}
+ {' '}
+ {this.props.right.name}
+ </h6>
+ </td>
+ </tr>
+ );
+ const rows = this.props.inRight.map(rule => (
+ <tr key={`right-${rule.key}`}
+ className="js-comparison-in-right">
+ <td> </td>
+ <td>{this.renderRule(rule, rule.severity)}</td>
+ </tr>
+ ));
+ return [header, ...rows];
+ }
+
+ renderModified () {
+ if (this.props.modified.length === 0) {
+ return null;
+ }
+ const header = (
+ <tr key="modified-header">
+ <td colSpan="2" className="text-center">
+ <h6>
+ {translateWithParameters(
+ 'quality_profiles.x_rules_have_different_configuration',
+ this.props.modified.length
+ )}
+ </h6>
+ </td>
+ </tr>
+ );
+ const secondHeader = (
+ <tr key="modified-second-header">
+ <td><h6>{this.props.left.name}</h6></td>
+ <td><h6>{this.props.right.name}</h6></td>
+ </tr>
+ );
+ const rows = this.props.modified.map(rule => (
+ <tr key={`modified-${rule.key}`}
+ className="js-comparison-modified">
+ <td>
+ {this.renderRule(rule, rule.left.severity)}
+ {this.renderParameters(rule.left.params)}
+ </td>
+ <td>
+ {this.renderRule(rule, rule.right.severity)}
+ {this.renderParameters(rule.right.params)}
+ </td>
+ </tr>
+ ));
+ return [header, secondHeader, ...rows];
+ }
+
+ render () {
+ if (!this.props.inLeft.length && !this.props.inRight.length && !this.props.modified.length) {
+ return <ComparisonEmpty/>;
+ }
+
+ return (
+ <table className="data zebra quality-profile-comparison-table">
+ <tbody>
+ {this.renderLeft()}
+ {this.renderRight()}
+ {this.renderModified()}
+ </tbody>
+ </table>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { expect } from 'chai';
+import { shallow } from 'enzyme';
+import React from 'react';
+import Select from 'react-select';
+import ComparisonForm from '../ComparisonForm';
+import { createFakeProfile } from '../../utils';
+
+describe('Quality Profiles :: ComparisonForm', () => {
+ it('should render Select with right options', () => {
+ const profile = createFakeProfile();
+ const profiles = [
+ profile,
+ createFakeProfile({ key: 'another', name: 'another name' }),
+ createFakeProfile({ key: 'java', name: 'java', language: 'java' })
+ ];
+
+ const output = shallow(
+ <ComparisonForm
+ withKey="another"
+ profile={profile}
+ profiles={profiles}
+ onCompare={() => true}/>
+ ).find(Select);
+ expect(output).to.have.length(1);
+ expect(output.prop('value')).to.equal('another');
+ expect(output.prop('options')).to.deep.equal([
+ { value: 'another', label: 'another name' }
+ ]);
+ });
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { expect } from 'chai';
+import { shallow } from 'enzyme';
+import React from 'react';
+import ComparisonResults from '../ComparisonResults';
+import ComparisonEmpty from '../ComparisonEmpty';
+import SeverityIcon from '../../../../components/shared/severity-icon';
+
+describe('Quality Profiles :: ComparisonResults', () => {
+ it('should render ComparisonEmpty', () => {
+ const output = shallow(
+ <ComparisonResults
+ left={{ name: 'left' }}
+ right={{ name: 'right' }}
+ inLeft={[]}
+ inRight={[]}
+ modified={[]}/>
+ );
+ expect(output.is(ComparisonEmpty)).to.equal(true);
+ });
+
+ it('should compare', () => {
+ const inLeft = [
+ { key: 'rule1', name: 'rule1', severity: 'BLOCKER' }
+ ];
+ const inRight = [
+ { key: 'rule2', name: 'rule2', severity: 'CRITICAL' },
+ { key: 'rule3', name: 'rule3', severity: 'MAJOR' }
+ ];
+ const modified = [
+ {
+ key: 'rule4',
+ name: 'rule4',
+ left: {
+ severity: 'BLOCKER',
+ params: { foo: 'bar' }
+ },
+ right: {
+ severity: 'INFO',
+ params: { foo: 'qwe' }
+ }
+ }
+ ];
+
+ const output = shallow(
+ <ComparisonResults
+ left={{ name: 'left' }}
+ right={{ name: 'right' }}
+ inLeft={inLeft}
+ inRight={inRight}
+ modified={modified}/>
+ );
+
+ const leftDiffs = output.find('.js-comparison-in-left');
+ expect(leftDiffs).to.have.length(1);
+ expect(leftDiffs.find('a')).to.have.length(1);
+ expect(leftDiffs.find('a').prop('href')).to.include('rule_key=rule1');
+ expect(leftDiffs.find('a').text()).to.include('rule1');
+ expect(leftDiffs.find(SeverityIcon)).to.have.length(1);
+ expect(leftDiffs.find(SeverityIcon).prop('severity')).to.equal('BLOCKER');
+
+ const rightDiffs = output.find('.js-comparison-in-right');
+ expect(rightDiffs).to.have.length(2);
+ expect(rightDiffs.at(0).find('a')).to.have.length(1);
+ expect(rightDiffs.at(0).find('a').prop('href'))
+ .to.include('rule_key=rule2');
+ expect(rightDiffs.at(0).find('a').text()).to.include('rule2');
+ expect(rightDiffs.at(0).find(SeverityIcon)).to.have.length(1);
+ expect(rightDiffs.at(0).find(SeverityIcon).prop('severity'))
+ .to.equal('CRITICAL');
+
+ const modifiedDiffs = output.find('.js-comparison-modified');
+ expect(modifiedDiffs).to.have.length(1);
+ expect(modifiedDiffs.find('a').at(0).prop('href')).to.include('rule_key=rule4');
+ expect(modifiedDiffs.find('a').at(0).text()).to.include('rule4');
+ expect(modifiedDiffs.find(SeverityIcon)).to.have.length(2);
+ expect(modifiedDiffs.text()).to.include('bar');
+ expect(modifiedDiffs.text()).to.include('qwe');
+ });
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { getLanguages } from '../../../api/languages';
+import {
+ getQualityProfiles,
+ getExporters
+} from '../../../api/quality-profiles';
+import { getCurrentUser } from '../../../api/users';
+import '../styles.css';
+import { sortProfiles } from '../utils';
+
+export default class App extends React.Component {
+ state = { loading: true };
+
+ componentWillMount () {
+ this.updateProfiles = this.updateProfiles.bind(this);
+ }
+
+ componentDidMount () {
+ this.mounted = true;
+ this.loadData();
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ loadData () {
+ this.setState({ loading: true });
+ Promise.all([
+ getCurrentUser(),
+ getLanguages(),
+ getExporters(),
+ getQualityProfiles()
+ ]).then(responses => {
+ if (this.mounted) {
+ const [user, languages, exporters, profiles] = responses;
+ const canAdmin = user.permissions.global.includes('profileadmin');
+ this.setState({
+ languages,
+ exporters,
+ canAdmin,
+ profiles: sortProfiles(profiles),
+ loading: false
+ });
+ }
+ });
+ }
+
+ updateProfiles () {
+ return getQualityProfiles().then(profiles => {
+ if (this.mounted) {
+ this.setState({ profiles: sortProfiles(profiles) });
+ }
+ });
+ }
+
+ renderChild () {
+ if (this.state.loading) {
+ return <i className="spinner"/>;
+ }
+
+ return React.cloneElement(this.props.children, {
+ profiles: this.state.profiles,
+ languages: this.state.languages,
+ exporters: this.state.exporters,
+ canAdmin: this.state.canAdmin,
+ updateProfiles: this.updateProfiles
+ });
+ }
+
+ render () {
+ return (
+ <div className="page page-limited-small">
+ {this.renderChild()}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import Helmet from 'react-helmet';
+import ProfileNotFound from './ProfileNotFound';
+import ProfileHeader from '../details/ProfileHeader';
+import { translate } from '../../../helpers/l10n';
+import { ProfilesListType } from '../propTypes';
+
+export default class ProfileContainer extends React.Component {
+ static propTypes = {
+ location: React.PropTypes.object,
+ profiles: ProfilesListType,
+ canAdmin: React.PropTypes.bool,
+ updateProfiles: React.PropTypes.func
+ };
+
+ componentWillMount () {
+ document.querySelector('html').classList.add('dashboard-page');
+ }
+
+ componentWillUnmount () {
+ document.querySelector('html').classList.remove('dashboard-page');
+ }
+
+ render () {
+ const { profiles, location, ...other } = this.props;
+ const { key } = location.query;
+ const profile = profiles.find(profile => profile.key === key);
+
+ if (!profile) {
+ return <ProfileNotFound/>;
+ }
+
+ const child = React.cloneElement(
+ this.props.children,
+ { profile, profiles, ...other });
+
+ const title = translate('quality_profiles.page') + ' - ' + profile.name;
+
+ return (
+ <div>
+ <Helmet
+ title={title}
+ titleTemplate="SonarQube - %s"/>
+
+ <ProfileHeader
+ profile={profile}
+ canAdmin={this.props.canAdmin}
+ updateProfiles={this.props.updateProfiles}/>
+ {child}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import moment from 'moment';
+import shallowCompare from 'react-addons-shallow-compare';
+import { translate } from '../../../helpers/l10n';
+
+export default class ProfileDate extends React.Component {
+ static propTypes = {
+ date: React.PropTypes.string
+ };
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ render () {
+ const { date } = this.props;
+
+ if (!date) {
+ return (
+ <span className="note">{translate('never')}</span>
+ );
+ }
+
+ return (
+ <span title={moment(date).format('LLL')} data-toggle="tooltip">
+ {moment(date).fromNow()}
+ </span>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { Link } from 'react-router';
+
+export default class ProfileLink extends React.Component {
+ static propTypes = {
+ profileKey: React.PropTypes.string.isRequired
+ };
+
+ render () {
+ const { profileKey, children, ...other } = this.props;
+ const query = { key: profileKey };
+ return (
+ <Link to={{ pathname: '/show', query }} {...other}>
+ {children}
+ </Link>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { IndexLink } from 'react-router';
+import { translate } from '../../../helpers/l10n';
+
+export default class ProfileNotFound extends React.Component {
+ render () {
+ return (
+ <div className="quality-profile-not-found">
+ <div className="note spacer-bottom">
+ <IndexLink to="/" className="text-muted">
+ {translate('quality_profiles.page')}
+ </IndexLink>
+ </div>
+
+ <div>
+ {translate('quality_profiles.not_found')}
+ </div>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { expect } from 'chai';
+import { shallow } from 'enzyme';
+import sinon from 'sinon';
+import React from 'react';
+import Helmet from 'react-helmet';
+import ProfileContainer from '../ProfileContainer';
+import ProfileNotFound from '../ProfileNotFound';
+import ProfileHeader from '../../details/ProfileHeader';
+import { createFakeProfile } from '../../utils';
+
+describe('Quality Profiles :: ProfileContainer', () => {
+ it('should render ProfileHeader', () => {
+ const targetProfile = createFakeProfile({ key: 'profile1' });
+ const profiles = [
+ targetProfile,
+ createFakeProfile({ key: 'profile2' })
+ ];
+ const updateProfiles = sinon.spy();
+ const output = shallow(
+ <ProfileContainer
+ location={{ query: { key: 'profile1' } }}
+ profiles={profiles}
+ canAdmin={false}
+ updateProfiles={updateProfiles}>
+ <div/>
+ </ProfileContainer>
+ );
+ const header = output.find(ProfileHeader);
+ expect(header).to.have.length(1);
+ expect(header.prop('profile')).to.equal(targetProfile);
+ expect(header.prop('canAdmin')).to.equal(false);
+ expect(header.prop('updateProfiles')).to.equal(updateProfiles);
+ });
+
+ it('should render ProfileNotFound', () => {
+ const profiles = [
+ createFakeProfile({ key: 'profile1' }),
+ createFakeProfile({ key: 'profile2' })
+ ];
+ const output = shallow(
+ <ProfileContainer
+ location={{ query: { key: 'random' } }}
+ profiles={profiles}
+ canAdmin={false}
+ updateProfiles={() => true}>
+ <div/>
+ </ProfileContainer>
+ );
+ expect(output.is(ProfileNotFound)).to.equal(true);
+ });
+
+ it('should render Helmet', () => {
+ const profiles = [
+ createFakeProfile({ key: 'profile1', name: 'First Profile' })
+ ];
+ const updateProfiles = sinon.spy();
+ const output = shallow(
+ <ProfileContainer
+ location={{ query: { key: 'profile1' } }}
+ profiles={profiles}
+ canAdmin={false}
+ updateProfiles={updateProfiles}>
+ <div/>
+ </ProfileContainer>
+ );
+ const helmet = output.find(Helmet);
+ expect(helmet).to.have.length(1);
+ expect(helmet.prop('title')).to.include('First Profile');
+ });
+});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import _ from 'underscore';
-import Marionette from 'backbone.marionette';
-import ProfileHeaderView from './profile-header-view';
-import ProfileDetailsView from './profile-details-view';
-
-export default Marionette.Controller.extend({
-
- initialize () {
- this.listenTo(this.options.app.profiles, 'select', this.onProfileSelect);
- this.listenTo(this.options.app.profiles, 'setAsDefault', this.onProfileSetAsDefault);
- this.listenTo(this.options.app.profiles, 'destroy', this.onProfileDestroy);
- },
-
- index () {
- this.fetchProfiles();
- },
-
- show (key) {
- const that = this;
- this.fetchProfiles().done(function () {
- const profile = that.options.app.profiles.findWhere({ key });
- if (profile != null) {
- profile.trigger('select', profile, { trigger: false });
- }
- });
- },
-
- changelog (key, since, to) {
- const that = this;
- this.anchor = 'changelog';
- this.fetchProfiles().done(function () {
- const profile = that.options.app.profiles.findWhere({ key });
- if (profile != null) {
- profile.trigger('select', profile, { trigger: false });
- profile.fetchChangelog({ since, to });
- }
- });
- },
-
- compare (key, withKey) {
- const that = this;
- this.anchor = 'comparison';
- this.fetchProfiles().done(function () {
- const profile = that.options.app.profiles.findWhere({ key });
- if (profile != null) {
- profile.trigger('select', profile, { trigger: false });
- profile.compareWith(withKey);
- }
- });
- },
-
- onProfileSelect (profile, options) {
- const that = this;
- const key = profile.get('key');
- const route = 'show?key=' + encodeURIComponent(key);
- const opts = _.defaults(options || {}, { trigger: true });
- if (opts.trigger) {
- this.options.app.router.navigate(route);
- }
- this.options.app.profilesView.highlight(key);
- this.fetchProfile(profile).done(function () {
- const profileHeaderView = new ProfileHeaderView({
- model: profile,
- canWrite: that.options.app.canWrite
- });
- that.options.app.layout.headerRegion.show(profileHeaderView);
-
- const profileDetailsView = new ProfileDetailsView({
- model: profile,
- canWrite: that.options.app.canWrite,
- exporters: that.options.app.exporters,
- anchor: that.anchor
- });
- that.options.app.layout.detailsRegion.show(profileDetailsView);
-
- that.anchor = null;
- });
- },
-
- onProfileSetAsDefault (profile) {
- const that = this;
- const url = window.baseUrl + '/api/qualityprofiles/set_default';
- const key = profile.get('key');
- const options = { profileKey: key };
- return $.post(url, options).done(function () {
- profile.set({ isDefault: true });
- that.fetchProfiles();
- });
- },
-
- onProfileDestroy () {
- this.options.app.router.navigate('');
- this.options.app.layout.headerRegion.reset();
- this.options.app.layout.detailsRegion.reset();
- this.options.app.layout.renderIntro();
- this.options.app.profilesView.highlight(null);
- },
-
- fetchProfiles () {
- return this.options.app.profiles.fetch({ reset: true });
- },
-
- fetchProfile (profile) {
- return profile.fetch();
- }
-
-});
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import ModalFormView from '../../components/common/modal-form';
-import Profile from './profile';
-import Template from './templates/quality-profiles-copy-profile.hbs';
-
-export default ModalFormView.extend({
- template: Template,
-
- onFormSubmit () {
- ModalFormView.prototype.onFormSubmit.apply(this, arguments);
- this.disableForm();
- this.sendRequest();
- },
-
- sendRequest () {
- const that = this;
- const url = window.baseUrl + '/api/qualityprofiles/copy';
- const name = this.$('#copy-profile-name').val();
- const options = {
- fromKey: this.model.get('key'),
- toName: name
- };
- return $.ajax({
- url,
- type: 'POST',
- data: options,
- statusCode: {
- // do not show global error
- 400: null
- }
- }).done(function (r) {
- that.addProfile(r);
- that.destroy();
- }).fail(function (jqXHR) {
- that.enableForm();
- that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
- });
- },
-
- addProfile (profileData) {
- const profile = new Profile(profileData);
- this.model.collection.add([profile]);
- profile.trigger('select', profile);
- }
-});
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import _ from 'underscore';
-import ModalFormView from '../../components/common/modal-form';
-import Profile from './profile';
-import Template from './templates/quality-profiles-create-profile.hbs';
-import { createQualityProfile } from '../../api/quality-profiles';
-
-export default ModalFormView.extend({
- template: Template,
-
- events () {
- return _.extend(ModalFormView.prototype.events.apply(this, arguments), {
- 'change #create-profile-language': 'onLanguageChange'
- });
- },
-
- onFormSubmit () {
- ModalFormView.prototype.onFormSubmit.apply(this, arguments);
-
- const form = this.$('form')[0];
- const data = new FormData(form);
-
- createQualityProfile(data)
- .then(r => {
- this.addProfile(r.profile);
- this.destroy();
- })
- .catch(e => {
- e.response.json().then(r => this.showErrors(r.errors, r.warnings));
- });
- },
-
- onRender () {
- ModalFormView.prototype.onRender.apply(this, arguments);
- this.$('select').select2({
- width: '250px',
- minimumResultsForSearch: 50
- });
- this.onLanguageChange();
- },
-
- onLanguageChange () {
- const that = this;
- const language = this.$('#create-profile-language').val();
- const importers = this.getImportersForLanguages(language);
- this.$('.js-importer').each(function () {
- that.emptyInput($(this));
- $(this).addClass('hidden');
- });
- importers.forEach(function (importer) {
- that.$(`.js-importer[data-key="${importer.key}"]`).removeClass('hidden');
- });
- },
-
- emptyInput (e) {
- e.wrap('<form>').closest('form').get(0).reset();
- e.unwrap();
- },
-
- addProfile (profileData) {
- const profile = new Profile(profileData);
- this.collection.add([profile]);
- profile.trigger('select', profile);
- },
-
- getImportersForLanguages (language) {
- if (language != null) {
- return this.options.importers.filter(function (importer) {
- return importer.languages.indexOf(language) !== -1;
- });
- } else {
- return [];
- }
- },
-
- serializeData () {
- return _.extend(ModalFormView.prototype.serializeData.apply(this, arguments), {
- languages: this.options.languages,
- importers: this.options.importers
- });
- }
-});
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import ModalFormView from '../../components/common/modal-form';
-import Template from './templates/quality-profiles-delete-profile.hbs';
-
-export default ModalFormView.extend({
- template: Template,
-
- modelEvents: {
- 'destroy': 'destroy'
- },
-
- onFormSubmit () {
- ModalFormView.prototype.onFormSubmit.apply(this, arguments);
- this.disableForm();
- this.sendRequest();
- },
-
- sendRequest () {
- const that = this;
- const url = window.baseUrl + '/api/qualityprofiles/delete';
- const options = { profileKey: this.model.get('key') };
- return $.ajax({
- url,
- type: 'POST',
- data: options,
- statusCode: {
- // do not show global error
- 400: null
- }
- }).done(function () {
- that.model.collection.fetch();
- that.model.trigger('destroy', that.model, that.model.collection);
- }).fail(function (jqXHR) {
- that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
- that.enableForm();
- });
- }
-});
-
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import ProfileRules from './ProfileRules';
+import ProfileProjects from './ProfileProjects';
+import ProfileInheritance from './ProfileInheritance';
+import ProfileEvolution from './ProfileEvolution';
+import ProfileExporters from './ProfileExporters';
+import { ProfileType } from '../propTypes';
+
+export default class ProfileDetails extends React.Component {
+ static propTypes = {
+ profile: ProfileType,
+ canAdmin: React.PropTypes.bool,
+ updateProfiles: React.PropTypes.func
+ };
+
+ render () {
+ return (
+ <div>
+ <div className="quality-profile-grid">
+ <div className="quality-profile-grid-left">
+ <ProfileRules {...this.props}/>
+ <ProfileExporters {...this.props}/>
+ <ProfileEvolution {...this.props}/>
+ </div>
+ <div className="quality-profile-grid-right">
+ <ProfileInheritance {...this.props}/>
+ <ProfileProjects {...this.props}/>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import moment from 'moment';
+import { translate } from '../../../helpers/l10n';
+
+export default class ProfileEvolution extends React.Component {
+ render () {
+ const { profile } = this.props;
+
+ return (
+ <div className="quality-profile-evolution">
+ <div>
+ <h6 className="little-spacer-bottom">
+ {translate('quality_profiles.list.updated')}
+ </h6>
+ {profile.userUpdatedAt ? (
+ <div>
+ {moment(profile.userUpdatedAt).format('LL')}
+ </div>
+ ) : (
+ <div className="note">
+ {translate('never')}
+ </div>
+ )}
+ </div>
+ <div>
+ <h6 className="little-spacer-bottom">
+ {translate('quality_profiles.list.used')}
+ </h6>
+ {profile.lastUsed ? (
+ <div>
+ {moment(profile.lastUsed).format('LL')}
+ </div>
+ ) : (
+ <div className="note">
+ {translate('never')}
+ </div>
+ )}
+ </div>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { translate } from '../../../helpers/l10n';
+
+export default class ProfileExporters extends React.Component {
+ static propTypes = {
+ exporters: React.PropTypes.array.isRequired
+ };
+
+ getExportUrl (exporter) {
+ return window.baseUrl + '/api/qualityprofiles/export' +
+ '?exporterKey=' + encodeURIComponent(exporter.key) +
+ '&language=' + encodeURIComponent(this.props.profile.language) +
+ '&name=' + encodeURIComponent(this.props.profile.name);
+ }
+
+ render () {
+ const { exporters, profile } = this.props;
+ const exportersForLanguage = exporters.filter(e => (
+ e.languages.includes(profile.language)
+ ));
+
+ if (exportersForLanguage.length === 0) {
+ return null;
+ }
+
+ return (
+ <div className="quality-profile-box quality-profile-exporters">
+ <header className="big-spacer-bottom">
+ <h2>{translate('quality_profiles.exporters')}</h2>
+ </header>
+ <ul>
+ {exportersForLanguage.map(exporter => (
+ <li key={exporter.key}
+ data-key={exporter.key}
+ className="spacer-top">
+ <a href={this.getExportUrl(exporter)} target="_blank">
+ {exporter.name}
+ </a>
+ </li>
+ ))}
+ </ul>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { Link, IndexLink } from 'react-router';
+import ProfileLink from '../components/ProfileLink';
+import RenameProfileView from '../views/RenameProfileView';
+import CopyProfileView from '../views/CopyProfileView';
+import DeleteProfileView from '../views/DeleteProfileView';
+import { ProfileType } from '../propTypes';
+import { translate } from '../../../helpers/l10n';
+import { setDefaultProfile } from '../../../api/quality-profiles';
+
+export default class ProfileHeader extends React.Component {
+ static propTypes = {
+ profile: ProfileType.isRequired,
+ canAdmin: React.PropTypes.bool.isRequired,
+ updateProfiles: React.PropTypes.func.isRequired
+ };
+
+ static contextTypes = {
+ router: React.PropTypes.object
+ };
+
+ handleRenameClick (e) {
+ e.preventDefault();
+ new RenameProfileView({
+ profile: this.props.profile
+ }).on('done', () => {
+ this.props.updateProfiles();
+ }).render();
+ }
+
+ handleCopyClick (e) {
+ e.preventDefault();
+ new CopyProfileView({
+ profile: this.props.profile
+ }).on('done', profile => {
+ this.props.updateProfiles().then(() => {
+ this.context.router.push({
+ pathname: '/show',
+ query: { key: profile.key }
+ });
+ });
+ }).render();
+ }
+
+ handleSetDefaultClick (e) {
+ e.preventDefault();
+ setDefaultProfile(this.props.profile.key)
+ .then(this.props.updateProfiles);
+ }
+
+ handleDeleteClick (e) {
+ e.preventDefault();
+ new DeleteProfileView({
+ profile: this.props.profile
+ }).on('done', () => {
+ this.context.router.replace('/');
+ this.props.updateProfiles();
+ }).render();
+ }
+
+ render () {
+ const { profile, canAdmin } = this.props;
+
+ const backupUrl = window.baseUrl +
+ '/api/qualityprofiles/backup?profileKey=' +
+ encodeURIComponent(profile.key);
+
+ // TODO fix inline styles
+
+ return (
+ <header className="page-header quality-profile-header">
+ <div className="note spacer-bottom">
+ <IndexLink to="/" className="text-muted">
+ {translate('quality_profiles.page')}
+ </IndexLink>
+ </div>
+
+ <h1 className="page-title">
+ <ProfileLink
+ profileKey={this.props.profile.key}
+ className="link-base-color">
+ {profile.name}
+ </ProfileLink>
+ <span className="spacer-left small text-muted">
+ {this.props.profile.languageName}
+ </span>
+ </h1>
+
+ <div className="pull-right">
+ <ul className="list-inline" style={{ lineHeight: '24px' }}>
+ <li>
+ <Link
+ to={{ pathname: '/changelog', query: { key: this.props.profile.key } }}
+ className="small text-muted"
+ activeClassName="link-active">
+ {translate('changelog')}
+ </Link>
+ </li>
+ <li>
+ <div className="pull-left dropdown">
+ <button className="dropdown-toggle"
+ data-toggle="dropdown">
+ {translate('actions')}
+ {' '}
+ <i className="icon-dropdown"/>
+ </button>
+ <ul className="dropdown-menu dropdown-menu-right">
+ <li>
+ <Link
+ to={{ pathname: '/compare', query: { key: profile.key } }}
+ id="quality-profile-compare">
+ {translate('compare')}
+ </Link>
+ </li>
+ <li>
+ <a id="quality-profile-backup" href={backupUrl}>
+ {translate('backup_verb')}
+ </a>
+ </li>
+ {canAdmin && (
+ <li>
+ <a id="quality-profile-rename"
+ href="#"
+ onClick={this.handleRenameClick.bind(this)}>
+ {translate('rename')}
+ </a>
+ </li>
+ )}
+ {canAdmin && (
+ <li>
+ <a id="quality-profile-copy"
+ href="#"
+ onClick={this.handleCopyClick.bind(this)}>
+ {translate('copy')}
+ </a>
+ </li>
+ )}
+ {canAdmin && !profile.isDefault && (
+ <li>
+ <a id="quality-profile-set-as-default"
+ href="#"
+ onClick={this.handleSetDefaultClick.bind(this)}>
+ {translate('set_as_default')}
+ </a>
+ </li>
+ )}
+ {canAdmin && !profile.isDefault && (
+ <li>
+ <a id="quality-profile-delete"
+ href="#"
+ onClick={this.handleDeleteClick.bind(this)}>
+ {translate('delete')}
+ </a>
+ </li>
+ )}
+ </ul>
+ </div>
+ </li>
+ </ul>
+ </div>
+ </header>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import ProfileInheritanceBox from './ProfileInheritanceBox';
+import ChangeParentView from '../views/ChangeParentView';
+import { ProfileType, ProfilesListType } from '../propTypes';
+import { translate } from '../../../helpers/l10n';
+import { getProfileInheritance } from '../../../api/quality-profiles';
+
+export default class ProfileInheritance extends React.Component {
+ static propTypes = {
+ profile: ProfileType.isRequired,
+ canAdmin: React.PropTypes.bool.isRequired
+ };
+
+ state = {
+ loading: true
+ };
+
+ componentWillMount () {
+ this.handleChangeParent = this.handleChangeParent.bind(this);
+ }
+
+ componentDidMount () {
+ this.mounted = true;
+ this.loadData();
+ }
+
+ componentDidUpdate (prevProps) {
+ if (prevProps.profile !== this.props.profile) {
+ this.loadData();
+ }
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ loadData () {
+ getProfileInheritance(this.props.profile.key).then(r => {
+ if (this.mounted) {
+ const { ancestors, children } = r;
+ this.setState({
+ children,
+ ancestors: ancestors.reverse(),
+ profile: r.profile,
+ loading: false
+ });
+ }
+ });
+ }
+
+ handleChangeParent (e) {
+ e.preventDefault();
+ new ChangeParentView({
+ profile: this.props.profile,
+ profiles: this.props.profiles
+ }).on('done', () => {
+ this.props.updateProfiles();
+ }).render();
+ }
+
+ render () {
+ return (
+ <div className="quality-profile-inheritance">
+ <header className="big-spacer-bottom clearfix">
+ <h2 className="pull-left">
+ {translate('quality_profiles.profile_inheritance')}
+ </h2>
+ {this.props.canAdmin && (
+ <button
+ className="pull-right js-change-parent"
+ onClick={this.handleChangeParent}>
+ {translate('quality_profiles.change_parent')}
+ </button>
+ )}
+ </header>
+
+ {!this.state.loading && (
+ <table className="data condensed zebra">
+ <tbody>
+ {this.state.ancestors.map((ancestor, index) => (
+ <ProfileInheritanceBox
+ key={ancestor.key}
+ profile={ancestor}
+ depth={index}
+ className="js-inheritance-ancestor"/>
+ ))}
+
+ <ProfileInheritanceBox
+ profile={this.state.profile}
+ depth={this.state.ancestors.length}
+ displayLink={false}
+ className="js-inheritance-current"/>
+
+ {this.state.children.map(child => (
+ <ProfileInheritanceBox
+ key={child.key}
+ profile={child}
+ depth={this.state.ancestors.length + 1}
+ className="js-inheritance-child"/>
+ ))}
+ </tbody>
+ </table>
+ )}
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import ProfileLink from '../components/ProfileLink';
+import { translateWithParameters } from '../../../helpers/l10n';
+
+export default class ProfileInheritanceBox extends React.Component {
+ static propTypes = {
+ profile: React.PropTypes.shape({
+ key: React.PropTypes.string.isRequired,
+ name: React.PropTypes.string.isRequired,
+ activeRuleCount: React.PropTypes.number.isRequired,
+ overridingRuleCount: React.PropTypes.number
+ }).isRequired,
+ depth: React.PropTypes.number.isRequired,
+ displayLink: React.PropTypes.bool.isRequired,
+ className: React.PropTypes.string
+ };
+
+ static defaultProps = {
+ displayLink: true
+ };
+
+ render () {
+ const { profile, className } = this.props;
+ const offset = 25 * this.props.depth;
+
+ return (
+ <tr className={className}>
+ <td>
+ <h6 style={{ paddingLeft: offset }}>
+ {this.props.displayLink ? (
+ <ProfileLink profileKey={profile.key}>
+ {profile.name}
+ </ProfileLink>
+ ) : profile.name}
+ </h6>
+ </td>
+
+ <td>
+ {translateWithParameters(
+ 'quality_profile.x_active_rules',
+ profile.activeRuleCount
+ )}
+ </td>
+
+ <td>
+ {profile.overridingRuleCount != null && (
+ <p>
+ {translateWithParameters(
+ 'quality_profiles.x_overridden_rules',
+ profile.overridingRuleCount
+ )}
+ </p>
+ )}
+ </td>
+ </tr>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import ChangeProjectsView from '../views/ChangeProjectsView';
+import { ProfileType } from '../propTypes';
+import { getProfileProjects } from '../../../api/quality-profiles';
+import { translate } from '../../../helpers/l10n';
+
+export default class ProfileProjects extends React.Component {
+ static propTypes = {
+ profile: ProfileType,
+ canAdmin: React.PropTypes.bool.isRequired
+ };
+
+ state = {
+ projects: null
+ };
+
+ componentWillMount () {
+ this.loadProjects = this.loadProjects.bind(this);
+ }
+
+ componentDidMount () {
+ this.mounted = true;
+ this.loadProjects();
+ }
+
+ componentDidUpdate (prevProps) {
+ if (prevProps.profile !== this.props.profile) {
+ this.loadProjects();
+ }
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ loadProjects () {
+ if (this.props.profile.isDefault) {
+ return;
+ }
+
+ const data = { key: this.props.profile.key };
+ getProfileProjects(data).then(r => {
+ if (this.mounted) {
+ this.setState({
+ projects: r.results,
+ more: r.more,
+ loading: false
+ });
+ }
+ });
+ }
+
+ handleChange (e) {
+ e.preventDefault();
+ e.target.blur();
+ new ChangeProjectsView({
+ profile: this.props.profile,
+ loadProjects: this.loadProjects
+ }).render();
+ }
+
+ renderDefault () {
+ return (
+ <div>
+ <span className="badge spacer-right">
+ {translate('default')}
+ </span>
+ {translate('quality_profiles.projects_for_default')}
+ </div>
+ );
+ }
+
+ renderProjects () {
+ const { projects } = this.state;
+
+ if (projects == null) {
+ return null;
+ }
+
+ if (projects.length === 0) {
+ return (
+ <div>
+ {translate('quality_profiles.no_projects_associated_to_profile')}
+ </div>
+ );
+ }
+
+ return (
+ <ul>
+ {projects.map(project => (
+ <li key={project.uuid}
+ className="spacer-top js-profile-project"
+ data-key={project.key}>
+ <i className="icon-checkbox icon-checkbox-checked"/>
+ {' '}
+ {project.name}
+ </li>
+ ))}
+ </ul>
+ );
+ }
+
+ render () {
+ return (
+ <div className="quality-profile-projects">
+ <header className="page-header">
+ <h2 className="page-title">
+ {translate('projects')}
+ </h2>
+
+ {this.props.canAdmin && !this.props.profile.isDefault && (
+ <div className="pull-right">
+ <button
+ className="js-change-projects"
+ onClick={this.handleChange.bind(this)}>
+ {translate('quality_profiles.change_projects')}
+ </button>
+ </div>
+ )}
+ </header>
+
+ {this.props.profile.isDefault ?
+ this.renderDefault() :
+ this.renderProjects()
+ }
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import keyBy from 'lodash/keyBy';
+import ProfileRulesRow from './ProfileRulesRow';
+import { ProfileType } from '../propTypes';
+import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
+import { searchRules, takeFacet } from '../../../api/rules';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { formatMeasure } from '../../../helpers/measures';
+import {
+ getRulesUrl,
+ getDeprecatedActiveRulesUrl
+} from '../../../helpers/urls';
+
+const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];
+
+export default class ProfileRules extends React.Component {
+ static propTypes = {
+ profile: ProfileType.isRequired,
+ canAdmin: React.PropTypes.bool.isRequired
+ };
+
+ state = {
+ total: null,
+ activatedTotal: null,
+ allByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val'),
+ activatedByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val')
+ };
+
+ componentDidMount () {
+ this.mounted = true;
+ this.loadRules();
+ }
+
+ componentDidUpdate (prevProps) {
+ if (prevProps.profile !== this.props.profile) {
+ this.loadRules();
+ }
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ loadAllRules () {
+ return searchRules({
+ languages: this.props.profile.language,
+ ps: 1,
+ facets: 'types'
+ });
+ }
+
+ loadActivatedRules () {
+ return searchRules({
+ qprofile: this.props.profile.key,
+ activation: 'true',
+ ps: 1,
+ facets: 'types'
+ });
+ }
+
+ loadRules () {
+ Promise.all([
+ this.loadAllRules(),
+ this.loadActivatedRules()
+ ]).then(responses => {
+ if (this.mounted) {
+ const [allRules, activatedRules] = responses;
+ this.setState({
+ total: allRules.total,
+ activatedTotal: activatedRules.total,
+ allByType: keyBy(takeFacet(allRules, 'types'), 'val'),
+ activatedByType: keyBy(takeFacet(activatedRules, 'types'), 'val')
+ });
+ }
+ });
+ }
+
+ getTooltip (count, total) {
+ if (count == null || total == null) {
+ return '';
+ }
+
+ return translateWithParameters(
+ 'quality_profiles.x_activated_out_of_y',
+ formatMeasure(count, 'INT'),
+ formatMeasure(total, 'INT'));
+ }
+
+ renderActiveTitle () {
+ return (
+ <strong>
+ {translate('quality_profile.total_active_rules')}
+ </strong>
+ );
+ }
+
+ renderActiveCount () {
+ const rulesUrl = getRulesUrl({
+ qprofile: this.props.profile.key,
+ activation: 'true'
+ });
+
+ if (this.state.activatedTotal == null) {
+ return null;
+ }
+
+ return (
+ <a href={rulesUrl}>
+ <strong>
+ {formatMeasure(this.state.activatedTotal, 'SHORT_INT')}
+ </strong>
+ </a>
+ );
+ }
+
+ getTooltipForType (type) {
+ const { count } = this.state.activatedByType[type];
+ const total = this.state.allByType[type].count;
+ return this.getTooltip(count, total);
+ }
+
+ renderTitleForType (type) {
+ return <span>{translate('issue.type', type, 'plural')}</span>;
+ }
+
+ renderCountForType (type) {
+ const rulesUrl = getRulesUrl({
+ qprofile: this.props.profile.key,
+ activation: 'true',
+ types: type
+ });
+
+ const { count } = this.state.activatedByType[type];
+
+ if (count == null) {
+ return null;
+ }
+
+ return (
+ <a href={rulesUrl}>
+ {formatMeasure(count, 'SHORT_INT')}
+ </a>
+ );
+ }
+
+ renderDeprecated () {
+ const { profile } = this.props;
+
+ if (profile.activeDeprecatedRuleCount === 0) {
+ return null;
+ }
+
+ const url = getDeprecatedActiveRulesUrl({ qprofile: profile.key });
+
+ return (
+ <div className="quality-profile-rules-deprecated clearfix">
+ <div className="pull-left">
+ {translate('quality_profiles.deprecated_rules')}
+ </div>
+ <div className="pull-right">
+ <a href={url}>
+ {profile.activeDeprecatedRuleCount}
+ </a>
+ </div>
+ </div>
+ );
+ }
+
+ render () {
+ const { total, activatedTotal, allByType, activatedByType } = this.state;
+
+ const activateMoreUrl = getRulesUrl({
+ qprofile: this.props.profile.key,
+ activation: 'false'
+ });
+
+ return (
+ <div className="quality-profile-rules">
+ <header className="clearfix">
+ <h2 className="pull-left">{translate('rules')}</h2>
+
+ {this.props.canAdmin && (
+ <a href={activateMoreUrl}
+ className="button pull-right js-activate-rules">
+ {translate('quality_profiles.activate_more')}
+ </a>
+ )}
+ </header>
+
+ {this.renderDeprecated()}
+
+ <TooltipsContainer options={{ delay: { show: 250, hide: 0 } }}>
+ <ul className="quality-profile-rules-distribution">
+ <li key="all" className="big-spacer-bottom">
+ <ProfileRulesRow
+ count={activatedTotal}
+ total={total}
+ tooltip={this.getTooltip(activatedTotal, total)}
+ renderTitle={this.renderActiveTitle.bind(this)}
+ renderCount={this.renderActiveCount.bind(this)}/>
+ </li>
+
+ {TYPES.map(type => (
+ <li key={type} className="spacer-top">
+ <ProfileRulesRow
+ count={activatedByType[type].count}
+ total={allByType[type].count}
+ tooltip={this.getTooltipForType(type)}
+ renderTitle={this.renderTitleForType.bind(this, type)}
+ renderCount={this.renderCountForType.bind(this, type)}/>
+ </li>
+ ))}
+ </ul>
+ </TooltipsContainer>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import ProgressBar from './ProgressBar';
+
+export default class ProfileRulesRow extends React.Component {
+ static propTypes = {
+ count: React.PropTypes.number,
+ total: React.PropTypes.number,
+ tooltip: React.PropTypes.string,
+ renderTitle: React.PropTypes.func.isRequired,
+ renderCount: React.PropTypes.func.isRequired
+ };
+
+ render () {
+ const { total, count, tooltip, renderTitle, renderCount } = this.props;
+
+ return (
+ <div title={tooltip} data-toggle="tooltip">
+ <div className="clearfix">
+ <div className="pull-left">
+ {renderTitle()}
+ </div>
+ <div className="pull-right">
+ {renderCount()}
+ </div>
+ </div>
+ <div className="little-spacer-top" style={{ height: 2 }}>
+ <ProgressBar
+ count={count || 0}
+ total={total || 0}
+ width={300}/>
+ </div>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+
+export default class ProgressBar extends React.Component {
+ static propTypes = {
+ width: React.PropTypes.number.isRequired,
+ height: React.PropTypes.number,
+ count: React.PropTypes.number.isRequired,
+ total: React.PropTypes.number.isRequired
+ };
+
+ static defaultProps = {
+ height: 2
+ };
+
+ render () {
+ const { width, height } = this.props;
+ const p = this.props.total > 0 ? this.props.count / this.props.total : 0;
+ const fillWidth = this.props.width * p;
+
+ const commonProps = { x: 0, y: 0, rx: 2, height };
+
+ return (
+ <svg width={width} height={height}>
+ <rect
+ {...commonProps}
+ width={width}
+ fill="#e6e6e6"/>
+ <rect
+ {...commonProps}
+ width={fillWidth}
+ className="bar-chart-bar quality-profile-progress-bar"/>
+ </svg>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import EvolutionDeprecated from './EvolutionDeprecated';
+import EvolutionStagnant from './EvolutionStagnant';
+import EvolutionRules from './EvolutionRules';
+import { ProfilesListType } from '../propTypes';
+
+export default class Evolution extends React.Component {
+ static propTypes = {
+ profiles: ProfilesListType.isRequired
+ };
+
+ render () {
+ return (
+ <div className="quality-profiles-evolution">
+ <EvolutionDeprecated profiles={this.props.profiles}/>
+ <EvolutionStagnant profiles={this.props.profiles}/>
+ <EvolutionRules/>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import ProfileLink from '../components/ProfileLink';
+import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls';
+import { ProfilesListType } from '../propTypes';
+import { translateWithParameters, translate } from '../../../helpers/l10n';
+
+export default class EvolutionDeprecated extends React.Component {
+ static propTypes = {
+ profiles: ProfilesListType.isRequired
+ };
+
+ render () {
+ const profilesWithDeprecations = this.props.profiles
+ .filter(profile => profile.activeDeprecatedRuleCount > 0);
+
+ if (profilesWithDeprecations.length === 0) {
+ return null;
+ }
+
+ const totalRules = profilesWithDeprecations
+ .map(p => p.activeDeprecatedRuleCount)
+ .reduce((p, c) => p + c, 0);
+
+ return (
+ <div className="quality-profiles-evolution-deprecated">
+ <div className="spacer-bottom">
+ <strong>{translate('quality_profiles.deprecated_rules')}</strong>
+ </div>
+ <div className="spacer-bottom">
+ {translateWithParameters(
+ 'quality_profiles.x_deprecated_rules_are_still_activated',
+ totalRules,
+ profilesWithDeprecations.length
+ )}
+ </div>
+ <ul>
+ {profilesWithDeprecations.map(profile => (
+ <li key={profile.key} className="spacer-top">
+ <div className="text-ellipsis">
+ <ProfileLink
+ profileKey={profile.key}
+ className="link-no-underline">
+ {profile.name}
+ </ProfileLink>
+ </div>
+ <div className="note">
+ {profile.languageName}
+ {', '}
+ <a className="text-muted"
+ href={getDeprecatedActiveRulesUrl({ qprofile: profile.key })}>
+ {translateWithParameters(
+ 'quality_profile.x_rules',
+ profile.activeDeprecatedRuleCount
+ )}
+ </a>
+ </div>
+ </li>
+ ))}
+ </ul>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import moment from 'moment';
+import sortBy from 'lodash/sortBy';
+import { searchRules } from '../../../api/rules';
+import { translateWithParameters, translate } from '../../../helpers/l10n';
+import { getRulesUrl } from '../../../helpers/urls';
+
+const RULES_LIMIT = 3;
+
+const PERIOD_START_MOMENT = moment().subtract(1, 'month');
+
+function parseRules (r) {
+ const { rules, actives } = r;
+ return rules.map(rule => {
+ const activations = actives[rule.key];
+ return { ...rule, activations: activations ? activations.length : 0 };
+ });
+}
+
+export default class EvolutionRules extends React.Component {
+ state = {};
+
+ componentDidMount () {
+ this.mounted = true;
+ this.loadLatestRules();
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ loadLatestRules () {
+ const data = {
+ 'available_since': PERIOD_START_MOMENT.format('YYYY-MM-DD'),
+ s: 'createdAt',
+ asc: false,
+ ps: RULES_LIMIT,
+ f: 'name,langName,actives'
+ };
+
+ searchRules(data).then(r => {
+ if (this.mounted) {
+ this.setState({
+ latestRules: sortBy(parseRules(r), 'langName'),
+ latestRulesTotal: r.total
+ });
+ }
+ });
+ }
+
+ render () {
+ if (!this.state.latestRulesTotal) {
+ return null;
+ }
+
+ const newRulesUrl = getRulesUrl({
+ 'available_since': PERIOD_START_MOMENT.format('YYYY-MM-DD')
+ });
+
+ return (
+ <div className="quality-profiles-evolution-rules">
+ <div className="clearfix">
+ <strong className="pull-left">
+ {translate('quality_profiles.latest_new_rules')}
+ </strong>
+
+ {this.state.latestRulesTotal > RULES_LIMIT && (
+ <a className="pull-right small text-muted"
+ href={newRulesUrl}>
+ {translate('see_all')}
+ </a>
+ )}
+ </div>
+ <ul>
+ {this.state.latestRules.map(rule => (
+ <li key={rule.key} className="spacer-top">
+ <div className="text-ellipsis">
+ <a className="link-no-underline"
+ href={getRulesUrl({ 'rule_key': rule.key })}>
+ {' '}
+ {rule.name}
+ </a>
+ <div className="note">
+ {rule.activations ? (
+ translateWithParameters(
+ 'quality_profiles.latest_new_rules.activated',
+ rule.langName,
+ rule.activations
+ )
+ ) : (
+ translateWithParameters(
+ 'quality_profiles.latest_new_rules.not_activated',
+ rule.langName
+ )
+ )}
+ </div>
+ </div>
+ </li>
+ ))}
+ </ul>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import moment from 'moment';
+import ProfileLink from '../components/ProfileLink';
+import { ProfilesListType } from '../propTypes';
+import { translate } from '../../../helpers/l10n';
+
+export default class EvolutionStagnant extends React.Component {
+ static propTypes = {
+ profiles: ProfilesListType.isRequired
+ };
+
+ render () {
+ // TODO filter built-in out
+
+ const outdated = this.props.profiles.filter(profile => (
+ moment().diff(moment(profile.rulesUpdatedAt), 'years') >= 1
+ ));
+
+ if (outdated.length === 0) {
+ return null;
+ }
+
+ return (
+ <div className="quality-profiles-evolution-stagnant">
+ <div className="spacer-bottom">
+ <strong>{translate('quality_profiles.stagnant_profiles')}</strong>
+ </div>
+ <div className="spacer-bottom">
+ {translate('quality_profiles.not_updated_more_than_year')}
+ </div>
+ <ul>
+ {outdated.map(profile => (
+ <li key={profile.key} className="spacer-top">
+ <div className="text-ellipsis">
+ <ProfileLink
+ profileKey={profile.key}
+ className="link-no-underline">
+ {profile.name}
+ </ProfileLink>
+ </div>
+ <div className="note">
+ {profile.languageName}
+ {', '}
+ updated on {moment(profile.rulesUpdatedAt).format('LL')}
+ </div>
+ </li>
+ ))}
+ </ul>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import Helmet from 'react-helmet';
+import PageHeader from './PageHeader';
+import Evolution from './Evolution';
+import ProfilesList from './ProfilesList';
+import { translate } from '../../../helpers/l10n';
+
+export default class HomeContainer extends React.Component {
+ render () {
+ return (
+ <div>
+ <Helmet
+ title={translate('quality_profiles.page')}
+ titleTemplate="SonarQube - %s"/>
+
+ <PageHeader {...this.props}/>
+ <Evolution {...this.props}/>
+ <ProfilesList {...this.props}/>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import CreateProfileView from '../views/CreateProfileView';
+import RestoreProfileView from '../views/RestoreProfileView';
+import { translate } from '../../../helpers/l10n';
+import { getImporters } from '../../../api/quality-profiles';
+
+export default class PageHeader extends React.Component {
+ static propTypes = {
+ canAdmin: React.PropTypes.bool.isRequired
+ };
+
+ static contextTypes = {
+ router: React.PropTypes.object
+ };
+
+ state = {};
+
+ componentDidMount () {
+ this.mounted = true;
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ retrieveImporters () {
+ if (this.state.importers) {
+ return Promise.resolve(this.state.importers);
+ } else {
+ return getImporters().then(importers => {
+ this.setState({ importers });
+ return importers;
+ });
+ }
+ }
+
+ handleCreateClick (e) {
+ e.preventDefault();
+ e.target.blur();
+ this.retrieveImporters().then(importers => {
+ new CreateProfileView({
+ languages: this.props.languages,
+ importers
+ }).on('done', profile => {
+ this.props.updateProfiles().then(() => {
+ this.context.router.push({
+ pathname: '/show',
+ query: { key: profile.key }
+ });
+ });
+ }).render();
+ });
+ }
+
+ handleRestoreClick (e) {
+ e.preventDefault();
+ e.target.blur();
+ new RestoreProfileView()
+ .on('done', this.props.updateProfiles)
+ .render();
+ }
+
+ render () {
+ return (
+ <header className="page-header">
+ <h1 className="page-title">
+ {translate('quality_profiles.page')}
+ </h1>
+
+ {this.props.canAdmin && (
+ <div className="page-actions button-group">
+ <button
+ id="quality-profiles-create"
+ onClick={this.handleCreateClick.bind(this)}>
+ {translate('create')}
+ </button>
+
+ <button
+ id="quality-profiles-restore"
+ className="spacer-left"
+ onClick={this.handleRestoreClick.bind(this)}>
+ {translate('quality_profiles.restore_profile')}
+ </button>
+ </div>
+ )}
+
+ <div className="page-description markdown">
+ {translate('quality_profiles.intro1')}
+ <br/>
+ {translate('quality_profiles.intro2')}
+ </div>
+ </header>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { PropTypes as RouterPropTypes } from 'react-router';
+import groupBy from 'lodash/groupBy';
+import pick from 'lodash/pick';
+import ProfilesListRow from './ProfilesListRow';
+import ProfilesListHeader from './ProfilesListHeader';
+import RestoreBuiltInProfilesView from '../views/RestoreBuiltInProfilesView';
+import { ProfilesListType, LanguagesListType } from '../propTypes';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
+
+export default class ProfilesList extends React.Component {
+ static propTypes = {
+ profiles: ProfilesListType,
+ languages: LanguagesListType,
+ location: RouterPropTypes.location
+ };
+
+ handleRestoreBuiltIn (languageKey, e) {
+ e.preventDefault();
+ const language = this.props.languages.find(l => l.key === languageKey);
+ new RestoreBuiltInProfilesView({ language })
+ .on('done', this.props.updateProfiles)
+ .render();
+ }
+
+ renderProfiles (profiles) {
+ return profiles.map(profile => (
+ <ProfilesListRow key={profile.key} profile={profile}/>
+ ));
+ }
+
+ renderHeader (languageKey, profilesCount) {
+ const language = this.props.languages.find(l => l.key === languageKey);
+ return (
+ <thead>
+ <tr>
+ <th>
+ {language.name}
+ {' ('}
+ {translateWithParameters(
+ 'quality_profiles.x_profiles',
+ profilesCount
+ )}
+ {')'}
+ {this.props.canAdmin && (
+ <button
+ className="spacer-left js-restore-built-in"
+ data-language={languageKey}
+ onClick={this.handleRestoreBuiltIn.bind(this, languageKey)}>
+ {translate('quality_profiles.restore_built_in_profiles')}
+ </button>
+ )}
+ </th>
+ <th className="text-right nowrap">
+ {translate('quality_profiles.list.projects')}
+ </th>
+ <th className="text-right nowrap">
+ {translate('quality_profiles.list.rules')}
+ </th>
+ <th className="text-right nowrap">
+ {translate('quality_profiles.list.updated')}
+ </th>
+ <th className="text-right nowrap">
+ {translate('quality_profiles.list.used')}
+ </th>
+ </tr>
+ </thead>
+ );
+ }
+
+ render () {
+ const { profiles, languages } = this.props;
+ const { language } = this.props.location.query;
+
+ const profilesIndex = groupBy(profiles, profile => profile.language);
+ const profilesToShow = language ?
+ pick(profilesIndex, language) :
+ profilesIndex;
+
+ return (
+ <div>
+ <ProfilesListHeader
+ languages={languages}
+ currentFilter={language}/>
+
+ {Object.keys(profilesToShow).length === 0 && (
+ <div className="alert alert-warning">
+ {translate('no_results')}
+ </div>
+ )}
+
+ {Object.keys(profilesToShow).map(languageKey => (
+ <table
+ key={languageKey}
+ data-language={languageKey}
+ className="data zebra zebra-hover quality-profiles-table">
+
+ {this.renderHeader(
+ languageKey,
+ profilesToShow[languageKey].length)}
+
+ <TooltipsContainer>
+ <tbody>
+ {this.renderProfiles(profilesToShow[languageKey])}
+ </tbody>
+ </TooltipsContainer>
+
+ </table>
+ ))}
+
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import { IndexLink } from 'react-router';
+import { LanguagesListType } from '../propTypes';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+
+export default class ProfilesListHeader extends React.Component {
+ static propTypes = {
+ languages: LanguagesListType.isRequired,
+ currentFilter: React.PropTypes.string
+ };
+
+ renderFilterToggle () {
+ const { languages, currentFilter } = this.props;
+ const currentLanguage = currentFilter &&
+ languages.find(l => l.key === currentFilter);
+
+ const label = currentFilter ?
+ translateWithParameters(
+ 'quality_profiles.x_profiles',
+ currentLanguage.name) :
+ translate('quality_profiles.all_profiles');
+
+ return (
+ <a className="dropdown-toggle link-no-underline js-language-filter"
+ href="#"
+ data-toggle="dropdown">
+ {label} <i className="icon-dropdown"/>
+ </a>
+ );
+ }
+
+ renderFilterMenu () {
+ return (
+ <ul className="dropdown-menu">
+ <li>
+ <IndexLink to="/">
+ {translate('quality_profiles.all_profiles')}
+ </IndexLink>
+ </li>
+ {this.props.languages.map(language => (
+ <li key={language.key}>
+ <IndexLink
+ to={{ pathname: '/', query: { language: language.key } }}
+ className="js-language-filter-option"
+ data-language={language.key}>
+ {language.name}
+ </IndexLink>
+ </li>
+ ))}
+ </ul>
+ );
+ }
+
+ render () {
+ if (this.props.languages.length < 2) {
+ return null;
+ }
+
+ const { languages, currentFilter } = this.props;
+ const currentLanguage = currentFilter &&
+ languages.find(l => l.key === currentFilter);
+
+ // if unknown language, then
+ if (currentFilter && !currentLanguage) {
+ return null;
+ }
+
+ return (
+ <header className="quality-profiles-list-header clearfix">
+ <div className="dropdown">
+ {this.renderFilterToggle()}
+ {this.renderFilterMenu()}
+ </div>
+ </header>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import React from 'react';
+import shallowCompare from 'react-addons-shallow-compare';
+import ProfileLink from '../components/ProfileLink';
+import ProfileDate from '../components/ProfileDate';
+import { ProfileType } from '../propTypes';
+import { translate } from '../../../helpers/l10n';
+import { getRulesUrl } from '../../../helpers/urls';
+
+export default class ProfilesListRow extends React.Component {
+ static propTypes = {
+ profile: ProfileType.isRequired
+ };
+
+ shouldComponentUpdate (nextProps, nextState) {
+ return shallowCompare(this, nextProps, nextState);
+ }
+
+ renderName () {
+ const { profile } = this.props;
+ const offset = 25 * (profile.depth - 1);
+ return (
+ <h4 style={{ paddingLeft: offset }}>
+ <ProfileLink profileKey={profile.key}>
+ {profile.name}
+ </ProfileLink>
+ </h4>
+ );
+ }
+
+ renderProjects () {
+ const { profile } = this.props;
+
+ if (profile.isDefault) {
+ return (
+ <span className="badge">
+ {translate('default')}
+ </span>
+ );
+ }
+
+ return (
+ <span>
+ {profile.projectCount}
+ </span>
+ );
+ }
+
+ renderRules () {
+ const { profile } = this.props;
+
+ const activeRulesUrl = getRulesUrl({
+ qprofile: profile.key,
+ activation: 'true'
+ });
+
+ const deprecatedRulesUrl = getRulesUrl({
+ qprofile: profile.key,
+ activation: 'true',
+ statuses: 'DEPRECATED'
+ });
+
+ return (
+ <div>
+ {profile.activeDeprecatedRuleCount > 0 && (
+ <span className="spacer-right">
+ <a className="badge badge-warning"
+ href={deprecatedRulesUrl}
+ title={translate('quality_profiles.deprecated_rules')}
+ data-toggle="tooltip">
+ {profile.activeDeprecatedRuleCount}
+ </a>
+ </span>
+ )}
+
+ <a href={activeRulesUrl}>
+ {profile.activeRuleCount}
+ </a>
+ </div>
+ );
+ }
+
+ renderUpdateDate () {
+ return <ProfileDate date={this.props.profile.userUpdatedAt}/>;
+ }
+
+ renderUsageDate () {
+ return <ProfileDate date={this.props.profile.lastUsed}/>;
+ }
+
+ render () {
+ return (
+ <tr className="quality-profiles-table-row"
+ data-key={this.props.profile.key}
+ data-name={this.props.profile.name}>
+ <td className="quality-profiles-table-name">
+ {this.renderName()}
+ </td>
+ <td className="quality-profiles-table-projects thin nowrap text-right">
+ {this.renderProjects()}
+ </td>
+ <td className="quality-profiles-table-rules thin nowrap text-right">
+ {this.renderRules()}
+ </td>
+ <td className="quality-profiles-table-date thin nowrap text-right">
+ {this.renderUpdateDate()}
+ </td>
+ <td className="quality-profiles-table-date thin nowrap text-right">
+ {this.renderUsageDate()}
+ </td>
+ </tr>
+ );
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Marionette from 'backbone.marionette';
-import Template from './templates/quality-profiles-intro.hbs';
-
-export default Marionette.ItemView.extend({
- template: Template
-});
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Marionette from 'backbone.marionette';
-import IntroView from './intro-view';
-import Template from './templates/quality-profiles-layout.hbs';
-
-export default Marionette.LayoutView.extend({
- template: Template,
-
- regions: {
- headerRegion: '.search-navigator-workspace-header',
- actionsRegion: '.search-navigator-filters',
- resultsRegion: '.quality-profiles-results',
- detailsRegion: '.search-navigator-workspace-details'
- },
-
- onRender () {
- const navigator = this.$('.search-navigator');
- navigator.addClass('sticky search-navigator-extended-view');
- const top = navigator.offset().top;
- this.$('.search-navigator-workspace-header').css({ top });
- this.$('.search-navigator-side').css({ top }).isolatedScroll();
- this.renderIntro();
- },
-
- renderIntro () {
- this.detailsRegion.show(new IntroView());
- }
-});
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Marionette from 'backbone.marionette';
-import Template from './templates/quality-profile-changelog.hbs';
-
-export default Marionette.ItemView.extend({
- template: Template,
-
- events: {
- 'submit #quality-profile-changelog-form': 'onFormSubmit',
- 'click .js-show-more-changelog': 'onShowMoreChangelogClick',
- 'click .js-hide-changelog': 'onHideChangelogClick'
- },
-
- onFormSubmit (e) {
- e.preventDefault();
- this.model.fetchChangelog(this.getSearchParameters());
- },
-
- onShowMoreChangelogClick (e) {
- e.preventDefault();
- this.model.fetchMoreChangelog();
- },
-
- onHideChangelogClick (e) {
- e.preventDefault();
- this.model.resetChangelog();
- },
-
- getSearchParameters () {
- const form = this.$('#quality-profile-changelog-form');
- return {
- since: form.find('[name="since"]').val(),
- to: form.find('[name="to"]').val()
- };
- }
-});
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import _ from 'underscore';
-import Marionette from 'backbone.marionette';
-import Template from './templates/quality-profile-comparison.hbs';
-
-export default Marionette.ItemView.extend({
- template: Template,
-
- events: {
- 'submit #quality-profile-comparison-form': 'onFormSubmit',
- 'click .js-hide-comparison': 'onHideComparisonClick'
- },
-
- onRender () {
- this.$('select').select2({
- width: '250px',
- minimumResultsForSearch: 50
- });
- },
-
- onFormSubmit (e) {
- e.preventDefault();
- const withKey = this.$('#quality-profile-comparison-with-key').val();
- this.model.compareWith(withKey);
- },
-
- onHideComparisonClick (e) {
- e.preventDefault();
- this.model.resetComparison();
- },
-
- getProfilesForComparison () {
- const profiles = this.model.collection.toJSON();
- const key = this.model.id;
- const language = this.model.get('language');
- return profiles.filter(function (profile) {
- return profile.language === language && key !== profile.key;
- });
- },
-
- serializeData () {
- return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
- profiles: this.getProfilesForComparison()
- });
- }
-});
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import _ from 'underscore';
-import Marionette from 'backbone.marionette';
-import ChangeProfileParentView from './change-profile-parent-view';
-import ProfileChangelogView from './profile-changelog-view';
-import ProfileComparisonView from './profile-comparison-view';
-import '../../components/SelectList';
-import Template from './templates/quality-profiles-profile-details.hbs';
-import { translate } from '../../helpers/l10n';
-
-export default Marionette.LayoutView.extend({
- template: Template,
-
- regions: {
- changelogRegion: '#quality-profile-changelog',
- comparisonRegion: '#quality-profile-comparison'
- },
-
- modelEvents: {
- 'change': 'onChange',
- 'flashChangelog': 'flashChangelog'
- },
-
- events: {
- 'click .js-profile': 'onProfileClick',
- 'click #quality-profile-change-parent': 'onChangeParentClick'
- },
-
- onRender () {
- if (!this.model.get('isDefault')) {
- this.initProjectsSelect();
- }
- this.changelogRegion.show(new ProfileChangelogView({ model: this.model }));
- this.comparisonRegion.show(new ProfileComparisonView({ model: this.model }));
- if (this.options.anchor === 'changelog') {
- this.scrollToChangelog();
- this.flashChangelog();
- }
- if (this.options.anchor === 'comparison') {
- this.scrollToComparison();
- }
- this.$('#quality-profile-changelog-form input')
- .datepicker({
- dateFormat: 'yy-mm-dd',
- changeMonth: true,
- changeYear: true
- });
- },
-
- onChange () {
- const changed = Object.keys(this.model.changedAttributes());
- if (!(changed.length === 1 && changed[0] === 'projectCount')) {
- this.render();
- }
- },
-
- initProjectsSelect () {
- const key = this.model.get('key');
- this.projectsSelectList = new window.SelectList({
- el: this.$('#quality-profile-projects-list'),
- width: '100%',
- height: 200,
- readOnly: !this.options.canWrite,
- focusSearch: false,
- format (item) {
- return item.name;
- },
- searchUrl: window.baseUrl + '/api/qualityprofiles/projects?key=' + encodeURIComponent(key),
- selectUrl: window.baseUrl + '/api/qualityprofiles/add_project',
- deselectUrl: window.baseUrl + '/api/qualityprofiles/remove_project',
- extra: {
- profileKey: key
- },
- selectParameter: 'projectUuid',
- selectParameterValue: 'uuid',
- labels: {
- selected: translate('quality_gates.projects.with'),
- deselected: translate('quality_gates.projects.without'),
- all: translate('quality_gates.projects.all'),
- noResults: translate('quality_gates.projects.noResults')
- },
- tooltips: {
- select: translate('quality_profiles.projects.select_hint'),
- deselect: translate('quality_profiles.projects.deselect_hint')
- }
- });
- this.listenTo(this.projectsSelectList.collection, 'change:selected', this.onProjectsChange);
- },
-
- onProfileClick (e) {
- const key = $(e.currentTarget).data('key');
- const profile = this.model.collection.get(key);
- if (profile != null) {
- e.preventDefault();
- this.model.collection.trigger('select', profile);
- }
- },
-
- onChangeParentClick (e) {
- e.preventDefault();
- this.changeParent();
- },
-
- onProjectsChange () {
- this.model.collection.updateForLanguage(this.model.get('language'));
- },
-
- changeParent () {
- new ChangeProfileParentView({
- model: this.model
- }).render();
- },
-
- scrollTo (selector) {
- const el = this.$(selector);
- const parent = el.scrollParent();
- const elOffset = el.offset();
- let parentOffset = parent.offset();
- if (parent.is(document)) {
- parentOffset = { top: 0 };
- }
- if (elOffset != null && parentOffset != null) {
- const scrollTop = elOffset.top - parentOffset.top - 53;
- parent.scrollTop(scrollTop);
- }
- },
-
- scrollToChangelog () {
- this.scrollTo('#quality-profile-changelog');
- },
-
- scrollToComparison () {
- this.scrollTo('#quality-profile-comparison');
- },
-
- getExporters () {
- const language = this.model.get('language');
- return this.options.exporters.filter(function (exporter) {
- return exporter.languages.indexOf(language) !== -1;
- });
- },
-
- flashChangelog () {
- const changelogEl = this.$(this.changelogRegion.el);
- changelogEl.addClass('flash in');
- setTimeout(function () {
- changelogEl.removeClass('in');
- }, 2000);
- },
-
- serializeData () {
- const key = this.model.get('key');
- const rulesSearchUrl = `/coding_rules#qprofile=${encodeURIComponent(key)}|activation=true`;
- const activateRulesUrl = `/coding_rules#qprofile=${encodeURIComponent(key)}|activation=false`;
- return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
- rulesSearchUrl,
- activateRulesUrl,
- canWrite: this.options.canWrite,
- exporters: this.getExporters()
- });
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import _ from 'underscore';
-import Marionette from 'backbone.marionette';
-import ProfileCopyView from './copy-profile-view';
-import ProfileRenameView from './rename-profile-view';
-import ProfileDeleteView from './delete-profile-view';
-import Template from './templates/quality-profiles-profile-header.hbs';
-
-export default Marionette.ItemView.extend({
- template: Template,
-
- modelEvents: {
- 'change': 'render'
- },
-
- events: {
- 'click #quality-profile-backup': 'onBackupClick',
- 'click #quality-profile-copy': 'onCopyClick',
- 'click #quality-profile-rename': 'onRenameClick',
- 'click #quality-profile-set-as-default': 'onDefaultClick',
- 'click #quality-profile-delete': 'onDeleteClick'
- },
-
- onBackupClick (e) {
- $(e.currentTarget).blur();
- },
-
- onCopyClick (e) {
- e.preventDefault();
- this.copy();
- },
-
- onRenameClick (e) {
- e.preventDefault();
- this.rename();
- },
-
- onDefaultClick (e) {
- e.preventDefault();
- this.setAsDefault();
- },
-
- onDeleteClick (e) {
- e.preventDefault();
- this.delete();
- },
-
- copy () {
- new ProfileCopyView({ model: this.model }).render();
- },
-
- rename () {
- new ProfileRenameView({ model: this.model }).render();
- },
-
- setAsDefault () {
- this.model.trigger('setAsDefault', this.model);
- },
-
- delete () {
- new ProfileDeleteView({ model: this.model }).render();
- },
-
- serializeData () {
- const key = this.model.get('key');
- return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
- encodedKey: encodeURIComponent(key),
- canWrite: this.options.canWrite
- });
- }
-});
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import _ from 'underscore';
-import Marionette from 'backbone.marionette';
-import Template from './templates/quality-profiles-profile.hbs';
-import { formatMeasure } from '../../helpers/measures';
-
-export default Marionette.ItemView.extend({
- tagName: 'a',
- className: 'list-group-item',
- template: Template,
-
- modelEvents: {
- 'change': 'render'
- },
-
- events: {
- 'click': 'onClick'
- },
-
- onRender () {
- this.$el.toggleClass('active', this.options.highlighted);
- this.$el.attr('data-key', this.model.id);
- this.$el.attr('data-language', this.model.get('language'));
- this.$('[data-toggle="tooltip"]').tooltip({ container: 'body' });
- },
-
- onDestroy () {
- this.$('[data-toggle="tooltip"]').tooltip('destroy');
- },
-
- onClick (e) {
- e.preventDefault();
- this.model.trigger('select', this.model);
- },
-
- serializeData () {
- return _.extend(Marionette.ItemView.prototype.serializeData.apply(this, arguments), {
- projectCountFormatted: formatMeasure(this.model.get('projectCount'), 'INT')
- });
- }
-});
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import _ from 'underscore';
-import Backbone from 'backbone';
-
-export default Backbone.Model.extend({
- idAttribute: 'key',
-
- defaults: {
- activeRuleCount: 0,
- projectCount: 0
- },
-
- fetch () {
- const that = this;
- this.fetchChanged = {};
- return $.when(
- this.fetchProfileRules(),
- this.fetchInheritance()
- ).done(function () {
- that.set(that.fetchChanged);
- });
- },
-
- fetchProfileRules () {
- const that = this;
- const url = window.baseUrl + '/api/rules/search';
- const key = this.id;
- const options = {
- ps: 1,
- facets: 'types',
- qprofile: key,
- activation: 'true'
- };
- return $.get(url, options).done(function (r) {
- const typesFacet = _.findWhere(r.facets, { property: 'types' });
- if (typesFacet != null) {
- const order = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];
- const types = typesFacet.values;
- const typesComparator = function (t) {
- return order.indexOf(t.val);
- };
- const sortedTypes = _.sortBy(types, typesComparator);
- _.extend(that.fetchChanged, { rulesTypes: sortedTypes });
- }
- });
- },
-
- fetchInheritance () {
- const that = this;
- const url = window.baseUrl + '/api/qualityprofiles/inheritance';
- const options = { profileKey: this.id };
- return $.get(url, options).done(function (r) {
- _.extend(that.fetchChanged, r.profile, {
- ancestors: r.ancestors,
- children: r.children
- });
- });
- },
-
- fetchChangelog (options) {
- const that = this;
- const url = window.baseUrl + '/api/qualityprofiles/changelog';
- const opts = _.extend({}, options, { profileKey: this.id });
- return $.get(url, opts).done(function (r) {
- that.set({
- events: r.events,
- eventsPage: r.p,
- totalEvents: r.total,
- eventsParameters: _.clone(options)
- });
- });
- },
-
- fetchMoreChangelog () {
- const that = this;
- const url = window.baseUrl + '/api/qualityprofiles/changelog';
- const page = this.get('eventsPage') || 0;
- const parameters = this.get('eventsParameters') || {};
- const opts = _.extend({}, parameters, { profileKey: this.id, p: page + 1 });
- return $.get(url, opts).done(function (r) {
- const events = that.get('events') || [];
- that.set({
- events: [].concat(events, r.events),
- eventsPage: r.p,
- totalEvents: r.total
- });
- });
- },
-
- resetChangelog () {
- this.unset('events', { silent: true });
- this.unset('eventsPage', { silent: true });
- this.unset('totalEvents');
- },
-
- compareWith (withKey) {
- const that = this;
- const url = window.baseUrl + '/api/qualityprofiles/compare';
- const options = { leftKey: this.id, rightKey: withKey };
- return $.get(url, options).done(function (r) {
- const comparison = _.extend(r, {
- inLeftSize: _.size(r.inLeft),
- inRightSize: _.size(r.inRight),
- modifiedSize: _.size(r.modified)
- });
- that.set({
- comparison,
- comparedWith: withKey
- });
- });
- },
-
- resetComparison () {
- this.unset('comparedWith', { silent: true });
- this.unset('comparison');
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Marionette from 'backbone.marionette';
-import Template from './templates/quality-profiles-empty.hbs';
-
-export default Marionette.ItemView.extend({
- className: 'list-group-item',
- template: Template
-});
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Marionette from 'backbone.marionette';
-import ProfileView from './profile-view';
-import ProfilesEmptyView from './profiles-empty-view';
-import Template from './templates/quality-profiles-profiles.hbs';
-import LanguageTemplate from './templates/quality-profiles-profiles-language.hbs';
-
-export default Marionette.CompositeView.extend({
- className: 'list-group',
- template: Template,
- languageTemplate: LanguageTemplate,
- childView: ProfileView,
- childViewContainer: '.js-list',
- emptyView: ProfilesEmptyView,
-
- collectionEvents: {
- 'filter': 'filterByLanguage'
- },
-
- childViewOptions (model) {
- return {
- collectionView: this,
- highlighted: model.get('key') === this.highlighted
- };
- },
-
- highlight (key) {
- this.highlighted = key;
- this.render();
- },
-
- attachHtml (compositeView, childView, index) {
- const $container = this.getChildViewContainer(compositeView);
- const model = this.collection.at(index);
- if (model != null) {
- const prev = this.collection.at(index - 1);
- let putLanguage = prev == null;
- if (prev != null) {
- const lang = model.get('language');
- const prevLang = prev.get('language');
- if (lang !== prevLang) {
- putLanguage = true;
- }
- }
- if (putLanguage) {
- $container.append(this.languageTemplate(model.toJSON()));
- }
- }
- compositeView._insertAfter(childView);
- },
-
- destroyChildren () {
- Marionette.CompositeView.prototype.destroyChildren.apply(this, arguments);
- this.$('.js-list-language').remove();
- },
-
- filterByLanguage (language) {
- if (language) {
- this.$('[data-language]').addClass('hidden');
- this.$(`[data-language="${language}"]`).removeClass('hidden');
- } else {
- this.$('[data-language]').removeClass('hidden');
- }
- }
-});
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Backbone from 'backbone';
-import Profile from './profile';
-
-export default Backbone.Collection.extend({
- model: Profile,
- url: window.baseUrl + '/api/qualityprofiles/search',
- comparator: 'key',
-
- parse (r) {
- return r.profiles;
- },
-
- updateForLanguage (language) {
- this.fetch({
- data: {
- language
- },
- merge: true,
- reset: false,
- remove: false
- });
- }
-});
-
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { PropTypes } from 'react';
+
+const { shape, string, number, bool, arrayOf } = PropTypes;
+
+export const ProfileType = shape({
+ key: string.isRequired,
+ name: string.isRequired,
+ isDefault: bool.isRequired,
+ isInherited: bool.isRequired,
+ language: string.isRequired,
+ languageName: string.isRequired,
+ activeRuleCount: number.isRequired,
+ activeDeprecatedRuleCount: number.isRequired,
+ projectCount: number,
+ parentKey: string,
+ parentName: string
+});
+
+export const ProfilesListType = arrayOf(ProfileType);
+
+const LanguageType = shape({
+ key: string.isRequired,
+ name: string.isRequired
+});
+
+export const LanguagesListType = arrayOf(LanguageType);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import ModalFormView from '../../components/common/modal-form';
-import Template from './templates/quality-profiles-rename-profile.hbs';
-
-export default ModalFormView.extend({
- template: Template,
-
- onFormSubmit () {
- ModalFormView.prototype.onFormSubmit.apply(this, arguments);
- this.sendRequest();
- },
-
- sendRequest () {
- const that = this;
- const url = window.baseUrl + '/api/qualityprofiles/rename';
- const name = this.$('#rename-profile-name').val();
- const options = {
- key: this.model.get('key'),
- name
- };
- return $.ajax({
- url,
- type: 'POST',
- data: options,
- statusCode: {
- // do not show global error
- 400: null
- }
- }).done(function () {
- that.model.set({ name });
- that.destroy();
- }).fail(function (jqXHR) {
- that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
- });
- }
-});
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import $ from 'jquery';
-import _ from 'underscore';
-import ModalFormView from '../../components/common/modal-form';
-import Template from './templates/quality-profiles-restore-built-in-profiles.hbs';
-import TemplateSuccess from './templates/quality-profiles-restore-built-in-profiles-success.hbs';
-
-export default ModalFormView.extend({
- template: Template,
- successTemplate: TemplateSuccess,
-
- getTemplate () {
- return this.selectedLanguage ? this.successTemplate : this.template;
- },
-
- onFormSubmit () {
- ModalFormView.prototype.onFormSubmit.apply(this, arguments);
- this.disableForm();
- this.sendRequest();
- },
-
- onRender () {
- ModalFormView.prototype.onRender.apply(this, arguments);
- this.$('select').select2({
- width: '250px',
- minimumResultsForSearch: 50
- });
- },
-
- sendRequest () {
- const that = this;
- const url = window.baseUrl + '/api/qualityprofiles/restore_built_in';
- const lang = this.$('#restore-built-in-profiles-language').val();
- const options = { language: lang };
- this.selectedLanguage = _.findWhere(this.options.languages, { key: lang }).name;
- return $.ajax({
- url,
- type: 'POST',
- data: options,
- statusCode: {
- // do not show global error
- 400: null
- }
- }).done(function () {
- that.collection.fetch({ reset: true });
- that.collection.trigger('destroy');
- that.render();
- }).fail(function (jqXHR) {
- that.showErrors(jqXHR.responseJSON.errors, jqXHR.responseJSON.warnings);
- that.enableForm();
- });
- },
-
- serializeData () {
- return _.extend(ModalFormView.prototype.serializeData.apply(this, arguments), {
- languages: this.options.languages,
- selectedLanguage: this.selectedLanguage
- });
- }
-});
-
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import ModalFormView from '../../components/common/modal-form';
-import Profile from './profile';
-import Template from './templates/quality-profiles-restore-profile.hbs';
-import { restoreQualityProfile } from '../../api/quality-profiles';
-
-export default ModalFormView.extend({
- template: Template,
-
- onFormSubmit (e) {
- ModalFormView.prototype.onFormSubmit.apply(this, arguments);
- const data = new FormData(e.currentTarget);
-
- this.disableForm();
-
- restoreQualityProfile(data)
- .then(r => {
- this.profile = r.profile;
- this.ruleSuccesses = r.ruleSuccesses;
- this.ruleFailures = r.ruleFailures;
- this.render();
- this.addProfile(r.profile);
- })
- .catch(e => {
- this.enableForm();
- e.response.json().then(r => this.showErrors(r.errors, r.warnings));
- });
- },
-
- addProfile (profileData) {
- const profile = new Profile(profileData);
- this.collection.add([profile], { merge: true });
- const addedProfile = this.collection.get(profile.id);
- if (addedProfile != null) {
- addedProfile.trigger('select', addedProfile);
- }
- },
-
- serializeData() {
- return Object.assign({}, ModalFormView.prototype.serializeData.apply(this, arguments), {
- profile: this.profile,
- ruleSuccesses: this.ruleSuccesses,
- ruleFailures: this.ruleFailures
- });
- }
-});
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import Backbone from 'backbone';
-
-export default Backbone.Router.extend({
- routes: {
- '': 'index',
- 'index': 'index',
- 'show?key=:key': 'show',
- 'changelog*': 'changelog',
- 'compare*': 'compare'
- },
-
- initialize (options) {
- this.app = options.app;
- },
-
- index () {
- this.app.controller.index();
- },
-
- show (key) {
- this.app.controller.show(key);
- },
-
- changelog () {
- const params = window.getQueryParams();
- this.app.controller.changelog(params.key, params.since, params.to);
- },
-
- compare () {
- const params = window.getQueryParams();
- if (params.key && params.withKey) {
- this.app.controller.compare(params.key, params.withKey);
- }
- }
-});
-
--- /dev/null
+.quality-profile-box {
+ padding: 20px;
+ border: 1px solid #e6e6e6;
+ border-radius: 2px;
+ background-color: #fff;
+}
+
+.quality-profiles-table {
+ margin-top: 30px;
+}
+
+.quality-profiles-table-name { }
+
+.quality-profiles-table-inheritance {
+ width: 280px;
+}
+
+.quality-profiles-table-projects,
+.quality-profiles-table-rules,
+.quality-profiles-table-date {
+ min-width: 120px;
+}
+
+.quality-profiles-list-header {
+ line-height: 24px;
+ margin-bottom: 15px;
+ padding: 5px 10px;
+ border-radius: 3px;
+ background-color: #f3f3f3;
+}
+
+.quality-profile-grid {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+}
+
+.quality-profile-grid-left {
+ width: 340px;
+ flex-shrink: 0;
+}
+
+.quality-profile-grid-right {
+ flex-grow: 1;
+ margin-left: 20px;
+}
+
+.quality-profile-rules,
+.quality-profile-projects,
+.quality-profile-inheritance,
+.quality-profile-evolution {
+ border: 1px solid #e6e6e6;
+ border-radius: 2px;
+ background-color: #fff;
+}
+
+.quality-profile-evolution {
+ padding: 20px;
+}
+
+.quality-profile-projects,
+.quality-profile-inheritance {
+ padding: 15px 20px 20px;
+}
+
+.quality-profile-rules {
+}
+
+.quality-profile-rules > header {
+ padding: 15px 20px;
+}
+
+.quality-profile-rules-distribution {
+ padding: 0 20px 20px;
+}
+
+.quality-profile-rules-deprecated {
+ margin-bottom: 20px;
+ padding: 15px 20px;
+ border-top: 1px solid #e6e6e6;
+ border-bottom: 1px solid #e6e6e6;
+ background-color: #fcf8e3;
+}
+
+.quality-profile-exporters {
+ margin-top: 20px;
+}
+
+.quality-profile-evolution {
+ display: flex;
+ margin-top: 20px;
+}
+
+.quality-profile-evolution > div {
+ width: 50%;
+ text-align: center;
+}
+
+.quality-profile-projects {
+ margin-top: 20px;
+}
+
+.quality-profile-inheritance {
+}
+
+.quality-profile-progress-bar {
+ transition: width 0.5s ease;
+ animation: appear 0.5s forwards;
+}
+
+@keyframes appear {
+ 0% { transform: translateX(-100%); }
+ 100% { transform: translateX(0); }
+}
+
+.quality-profile-not-found {
+ padding-top: 100px;
+ text-align: center;
+}
+
+.quality-profiles-evolution {
+ display: flex;
+ justify-content: flex-start;
+ align-items: stretch;
+ margin-bottom: 30px;
+}
+
+.quality-profiles-evolution-deprecated,
+.quality-profiles-evolution-stagnant,
+.quality-profiles-evolution-rules {
+ width: 325px;
+ padding: 15px 20px;
+ box-sizing: border-box;
+}
+
+.quality-profiles-evolution-deprecated,
+.quality-profiles-evolution-stagnant {
+ margin-right: 30px;
+ border: 1px solid #faebcc;
+ background-color: #fcf8e3;
+}
+
+.quality-profiles-evolution-rules {
+ border: 1px solid #e6e6e6;
+}
+
+.quality-profile-comparison-table {
+ table-layout: fixed;
+}
+++ /dev/null
-<header class="page-header">
- <div class="page-title">
- <span class="h3">{{t 'changelog'}}</span>
- </div>
-</header>
-
-<form class="spacer-bottom" id="quality-profile-changelog-form">
- {{t 'quality_profiles.changelog_from'}}
- <input name="since" type="text" value="{{eventsParameters.since}}" placeholder="{{t 'optional'}}">
- {{t 'to'}}
- <input name="to" type="text" value="{{eventsParameters.to}}" placeholder="{{t 'optional'}}">
- <button id="quality-profile-changelog-form-submit">{{t 'search_verb'}}</button>
-</form>
-
-{{#notEmpty events}}
- <table class="width-100 data zebra">
- <thead>
- <tr>
- <th>{{t 'date'}}</th>
- <th>{{t 'user'}}</th>
- <th>{{t 'action'}}</th>
- <th>{{t 'rule'}}</th>
- <th>{{t 'parameters'}}</th>
- </tr>
- </thead>
- <tbody>
- {{#each events}}
- <tr>
- <td class="text-top nowrap thin">{{dt date}}</td>
- <td class="text-top nowrap thin">{{default authorName 'System'}}</td>
- <td class="text-top nowrap">{{t 'quality_profiles.changelog' action}}</td>
- <td class="text-top"><a href="{{rulePermalink ruleKey}}">{{ruleName}}</a></td>
- <td class="text-top thin">
- <ul>
- {{#each params}}
- <li>
- {{#eq @key 'severity'}}
- <span class="nowrap">{{severityChangelog this}}</span>
- {{else}}
- {{parameterChangelog @key this}}
- {{/eq}}
- </li>
- {{/each}}
- </ul>
- </td>
- </tr>
- {{/each}}
- </tbody>
- </table>
-
-
- <p class="spacer-top text-center">
- {{#unlessLength events totalEvents}}
- <a class="js-show-more-changelog spacer-right" href="#">{{t 'show_more'}}</a>
- {{/unlessLength}}
- <a class="js-hide-changelog" href="#">{{t 'hide'}}</a>
- </p>
-
-{{else}}
- {{#notNull totalEvents}}
- <div class="alert alert-info">
- {{t 'quality_profiles.changelog.empty'}}
- <a class="js-hide-changelog spacer-left" href="#">{{t 'hide'}}</a>
- </div>
- {{/notNull}}
-{{/notEmpty}}
+++ /dev/null
-<header class="page-header">
- <div class="page-title">
- <span class="h3">{{t 'compare'}}</span>
- </div>
-</header>
-
-{{#notEmpty profiles}}
- <form class="spacer-bottom" id="quality-profile-comparison-form">
- <label class="text-middle" for="quality-profile-comparison-with-key">{{t 'with'}}</label>
- <select id="quality-profile-comparison-with-key">
- {{#each profiles}}
- <option value="{{key}}" {{#eq key ../comparedWith}}selected{{/eq}}>{{name}}</option>
- {{/each}}
- </select>
- <button class="text-middle" id="quality-profile-comparison-form-submit">{{t 'compare'}}</button>
- </form>
-{{else}}
- <div class="alert alert-info">{{t 'quality_profiles.no_profiles_for_comparison'}}</div>
-{{/notEmpty}}
-
-{{#notNull comparison}}
- <table class="width-100 data zebra">
- {{#notEmpty comparison.inLeft}}
- <tr>
- <td class="width-50"><h6>{{tp 'quality_profiles.x_rules_only_in' comparison.inLeftSize}} {{comparison.left.name}}</h6></td>
- <td class="width-50"></td>
- </tr>
- {{#each comparison.inLeft}}
- <tr class="js-comparison-in-left">
- <td class="width-50">{{severityIcon severity}} <a href="{{rulePermalink key}}">{{name}}</a></td>
- <td class="width-50"></td>
- </tr>
- {{/each}}
- {{/notEmpty}}
-
- {{#notEmpty comparison.inRight}}
- <tr>
- <td class="width-50"></td>
- <td class="width-50"><h6>{{tp 'quality_profiles.x_rules_only_in' comparison.inRightSize}} {{comparison.right.name}}</h6></td>
- </tr>
- {{#each comparison.inRight}}
- <tr class="js-comparison-in-right">
- <td class="width-50"></td>
- <td class="width-50">{{severityIcon severity}} <a href="{{rulePermalink key}}">{{name}}</a></td>
- </tr>
- {{/each}}
- {{/notEmpty}}
-
- {{#notEmpty comparison.modified}}
- <tr>
- <td class="text-center width-50" colspan="2">
- <h6>{{tp 'quality_profiles.x_rules_have_different_configuration' comparison.modifiedSize}}</h6>
- </td>
- </tr>
- <tr>
- <td class="width-50"><h6>{{comparison.left.name}}</h6></td>
- <td class="width-50"><h6>{{comparison.right.name}}</h6></td>
- </tr>
- {{#each comparison.modified}}
- <tr class="js-comparison-modified">
- <td class="width-50">
- <p>{{severityIcon left.severity}} <a href="{{rulePermalink key}}">{{name}}</a></p>
- {{#notNull left.params}}
- <ul>
- {{#each left.params}}
- <li class="spacer-top"><code>{{@key}}: {{this}}</code></li>
- {{/each}}
- </ul>
- {{/notNull}}
- </td>
- <td class="width-50">
- <p>{{severityIcon right.severity}} <a href="{{rulePermalink key}}">{{name}}</a></p>
- {{#notNull right.params}}
- <ul>
- {{#each right.params}}
- <li class="spacer-top"><code>{{@key}}: {{this}}</code></li>
- {{/each}}
- </ul>
- {{/notNull}}
- </td>
- </tr>
- {{/each}}
- {{/notEmpty}}
- </table>
-
- <p class="spacer-top text-center">
- <a class="js-hide-comparison" href="#">{{t 'hide'}}</a>
- </p>
-{{/notNull}}
+++ /dev/null
-<header class="page-header">
- <h1 class="page-title">{{t 'quality_profiles.page'}}</h1>
-
- {{#if canWrite}}
- <div class="page-actions">
- <div class="button-group dropdown">
- <button id="quality-profiles-create">{{t 'create'}}</button>
- <a id="quality-profiles-actions" class="button dropdown-toggle" href="#"
- data-toggle="dropdown"><i class="icon-dropdown"></i></a>
- <ul class="dropdown-menu dropdown-menu-right">
- <li>
- <a id="quality-profiles-restore" href="#">{{t 'quality_profiles.restore_profile'}}</a>
- </li>
- <li>
- <a id="quality-profiles-restore-built-in" href="#">{{t 'quality_profiles.restore_built_in_profiles'}}</a>
- </li>
- </ul>
- </div>
- </div>
- {{/if}}
-</header>
-
-<div class="dropdown" id="quality-profiles-filter-by-language">
- <span>Show:</span>
- {{#if selectedLanguage}}
- <a class="dropdown-toggle link-no-underline" href="#" data-toggle="dropdown">
- {{tp 'quality_profiles.x_profiles' selectedLanguage.name}} <i class="icon-dropdown"></i>
- </a>
- {{else}}
- <a class="dropdown-toggle link-no-underline" href="#" data-toggle="dropdown">
- {{t 'quality_profiles.all_profiles'}} <i class="icon-dropdown"></i>
- </a>
- {{/if}}
- <ul class="dropdown-menu">
- <li><a class="js-filter-by-language" href="#">{{t 'quality_profiles.all_profiles'}}</a></li>
- {{#each languages}}
- <li><a class="js-filter-by-language" href="#" data-language="{{key}}">{{name}}</a></li>
- {{/each}}
- </ul>
-</div>
--- /dev/null
+<div class="modal-head">
+ <h2>{{t 'projects'}}</h2>
+</div>
+
+<div class="modal-body">
+ <div class="js-modal-messages"></div>
+ <div id="profile-projects"></div>
+</div>
+
+<div class="modal-foot">
+ <a href="#" class="js-modal-close">{{t 'close'}}</a>
+</div>
<div class="modal-body">
<div class="js-modal-messages"></div>
<div class="modal-field">
- <label for="create-profile-name">{{t 'name'}}<em class="mandatory">*</em></label>
- <input id="create-profile-name" name="name" type="text" size="50" maxlength="100" required>
- </div>
+ <label for="create-profile-name">{{t 'name'}}<em class="mandatory">*</em></label>
+ <input id="create-profile-name" name="name" type="text" size="50" maxlength="100" required>
+ </div>
<div class="modal-field spacer-bottom">
<label for="create-profile-language">{{t 'language'}}<em class="mandatory">*</em></label>
<select id="create-profile-language" name="language" required>
</div>
<div class="modal-body">
<div class="js-modal-messages"></div>
- {{#notEmpty children}}
+ {{#if childrenCount}}
<div class="alert alert-warning">{{t 'quality_profiles.this_profile_has_descendants'}}</div>
<p>{{tp 'quality_profiles.are_you_sure_want_delete_profile_x_and_descendants' name languageName}}</p>
{{else}}
<p>{{tp 'quality_profiles.are_you_sure_want_delete_profile_x' name languageName}}</p>
- {{/notEmpty}}
+ {{/if}}
</div>
<div class="modal-foot">
<button id="delete-profile-submit">{{t 'delete'}}</button>
+++ /dev/null
-{{t 'quality_profiles.no_results'}}
+++ /dev/null
-<div class="search-navigator-intro markdown">
- <p>{{t 'quality_profiles.intro1'}}</p>
- <p>{{t 'quality_profiles.intro2'}}</p>
-</div>
+++ /dev/null
-<div class="search-navigator">
- <div class="search-navigator-side search-navigator-side-light">
- <div class="search-navigator-filters"></div>
- <div class="quality-profiles-results panel"></div>
- </div>
-
- <div class="search-navigator-workspace">
- <div class="search-navigator-workspace-header"></div>
- <div class="search-navigator-workspace-details"></div>
- </div>
-</div>
+++ /dev/null
-<div class="panel panel-vertical">
- <div class="columns">
- <div class="column-two-thirds" id="quality-profile-rules">
- <header class="page-header">
- <h3 class="page-title">{{t 'coding_rules'}}</h3>
- </header>
-
- <div>
- <div class="display-inline-block text-right little-spacer-right" style="width: 40px">
- <strong>
- <a href="{{link rulesSearchUrl}}">{{formatMeasure activeRuleCount 'INT'}}</a>
- </strong>
- </div>
- <strong>
- {{tp 'quality_profile.x_active_rules' ''}}
- </strong>
- </div>
-
- {{#notEmpty rulesTypes}}
- <div class="abs-width-400 spacer-top">
- {{#each rulesTypes}}
- <div class="spacer-top">
- <div class="display-inline-block text-right little-spacer-right" style="width: 40px">
- <a href="{{link ../rulesSearchUrl '|types=' val}}">{{formatMeasure count 'INT'}}</a>
- </div>
- {{issueType val}}
- </div>
- {{/each}}
- </div>
- {{/notEmpty}}
-
- <div class="spacer-top">
- <a class="button" href="{{link activateRulesUrl}}">
- {{t 'quality_profiles.activate_more'}}
- </a>
- </div>
- </div>
-
- {{#notEmpty exporters}}
- <div class="column-third" id="quality-profile-permalinks">
- <header class="page-header">
- <h3 class="page-title">{{t 'permalinks'}}</h3>
- </header>
- <ul>
- {{#each exporters}}
- <li class="spacer-bottom" style="line-height: 1.5">
- <a class="link-with-icon" href="{{exporterUrl ../this key}}" target="_blank">
- <i class="icon-detach"></i>
- <span>{{name}}</span>
- </a>
- </li>
- {{/each}}
- </ul>
- </div>
- {{/notEmpty}}
- </div>
-</div>
-
-<div class="panel panel-vertical" id="quality-profile-projects">
- <header class="page-header">
- <h3 class="page-title">{{t 'projects'}}</h3>
- </header>
- {{#if isDefault}}
- {{#if canWrite}}
- <p class="alert alert-info">{{t 'quality_profiles.projects_for_default.edit'}}</p>
- {{else}}
- <p class="alert alert-info">{{t 'quality_profiles.projects_for_default'}}</p>
- {{/if}}
- {{else}}
- <div id="quality-profile-projects-list" class="select-list-on-full-width"></div>
- {{/if}}
-</div>
-
-<div class="panel panel-vertical" id="quality-profile-inheritance">
- <header class="page-header">
- <h3 class="page-title">{{t 'quality_profiles.profile_inheritance'}}</h3>
- {{#if canWrite}}
- <div class="button-group big-spacer-left">
- <button id="quality-profile-change-parent">{{t 'quality_profiles.change_parent'}}</button>
- </div>
- {{/if}}
- </header>
- <div class="text-center">
- {{#notEmpty ancestors}}
- <ul id="quality-profile-ancestors">
- {{#eachReverse ancestors}}
- <li>
- <div class="alert alert-inline alert-info">
- <h6><a class="js-profile" data-key="{{key}}" href="{{profileUrl key}}">{{name}}</a></h6>
- <p class="note">{{tp 'quality_profile.x_active_rules' activeRuleCount}}</p>
- {{#if overridingRuleCount}}
- <p class="note">{{tp 'quality_profiles.x_overridden_rules' overridingRuleCount}}</p>
- {{/if}}
- </div>
- <div class="spacer-top spacer-bottom">
- <i class="icon-move-down"></i>
- </div>
- </li>
- {{/eachReverse}}
- </ul>
- {{/notEmpty}}
-
- <div id="quality-profile-inheritance-current" class="alert alert-inline alert-success">
- <h6>{{name}}</h6>
- <p class="note">{{tp 'quality_profile.x_active_rules' activeRuleCount}}</p>
- {{#if overridingRuleCount}}
- <p class="note">{{tp 'quality_profiles.x_overridden_rules' overridingRuleCount}}</p>
- {{/if}}
- </div>
-
- {{#notEmpty children}}
- <div class="spacer-top spacer-bottom">
- <i class="icon-move-down"></i>
- </div>
- <ul id="quality-profile-children" class="list-inline">
- {{#eachReverse children}}
- <li>
- <div class="alert alert-inline alert-info">
- <h6><a class="js-profile" data-key="{{key}}" href="{{profileUrl key}}">{{name}}</a></h6>
- <p class="note">{{tp 'quality_profile.x_active_rules' activeRuleCount}}</p>
- {{#if overridingRuleCount}}
- <p class="note">{{tp 'quality_profiles.x_overridden_rules' overridingRuleCount}}</p>
- {{/if}}
- </div>
- </li>
- {{/eachReverse}}
- </ul>
- {{/notEmpty}}
- </div>
-</div>
-
-<div class="panel panel-vertical" id="quality-profile-changelog"></div>
-
-<div class="panel panel-vertical" id="quality-profile-comparison"></div>
+++ /dev/null
-<h2 class="search-navigator-header-component">
- {{name}}
- <span class="note">{{languageName}}</span>
-</h2>
-
-<div class="search-navigator-header-actions">
- <div class="button-group">
- <a class="button" href="{{link '/api/qualityprofiles/backup?profileKey=' encodedKey}}"
- id="quality-profile-backup">{{t 'backup_verb'}}</a>
- {{#if canWrite}}
- <button id="quality-profile-rename">{{t 'rename'}}</button>
- <button id="quality-profile-copy">{{t 'copy'}}</button>
- {{#unless isDefault}}
- <button id="quality-profile-set-as-default">{{t 'set_as_default'}}</button>
- <button id="quality-profile-delete" class="button-red">{{t 'delete'}}</button>
- {{/unless}}
- {{/if}}
- </div>
-</div>
+++ /dev/null
-<table>
- <tr>
- <td class="text-top">{{name}}</td>
- {{#if isInherited}}
- <td class="text-top thin spacer-left">
- <i class="icon-inheritance" title="{{tp 'quality_profiles.inherits' parentName}}"
- data-toggle="tooltip" data-placement="bottom"></i>
- </td>
- {{/if}}
- <td class="text-top thin nowrap spacer-left">
- {{#if isDefault}}
- <span class="badge pull-right">{{t 'default'}}</span>
- {{else}}
- <span class="note pull-right">{{tp 'quality_profiles.x_projects' projectCountFormatted}}</span>
- {{/if}}
- </td>
- </tr>
-</table>
-
-
-
+++ /dev/null
-<h6 class="spacer-top js-list-language" data-language="{{language}}">{{languageName}}</h6>
+++ /dev/null
-<div class="js-list"></div>
</div>
<div class="modal-body">
<div class="alert alert-success">
- {{tp 'quality_profiles.restore_built_in_profiles_success_message' selectedLanguage}}
+ {{tp 'quality_profiles.restore_built_in_profiles_success_message' language.name}}
</div>
</div>
<div class="modal-foot">
</div>
<div class="modal-body">
<div class="js-modal-messages"></div>
- <div id="restore-built-in-profiles-form-success" class="alert alert-success hidden"></div>
- <div class="modal-field">
- <label for="restore-built-in-profiles-language">{{t 'language'}}<em class="mandatory">*</em></label>
- <select id="restore-built-in-profiles-language" name="language">
- {{#each languages}}
- <option value="{{key}}">{{name}}</option>
- {{/each}}
- </select>
- </div>
+ {{tp 'quality_profiles.restore_built_in_profiles_confirmation' language.name}}
</div>
<div class="modal-foot">
<button id="restore-built-in-profiles-submit">{{t 'restore'}}</button>
- <a href="#" class="js-modal-close" id="restore-built-in-profiles-cancel">{{t 'cancel'}}</a>
+ <a href="#" class="js-modal-close">{{t 'close'}}</a>
</div>
</form>
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import sortBy from 'lodash/sortBy';
+
+export function sortProfiles (profiles) {
+ const result = [];
+ const sorted = sortBy(profiles, 'name');
+
+ function retrieveChildren (parent) {
+ return sorted.filter(p => (
+ (parent == null && p.parentKey == null) ||
+ (parent != null && p.parentKey === parent.key)
+ ));
+ }
+
+ function putProfile (profile = null, depth = 0) {
+ const children = retrieveChildren(profile);
+
+ if (profile != null) {
+ result.push({ ...profile, childrenCount: children.length, depth });
+ }
+
+ children.forEach(child => putProfile(child, depth + 1));
+ }
+
+ putProfile();
+
+ return result;
+}
+
+export function createFakeProfile (overrides) {
+ return {
+ key: 'key',
+ name: 'name',
+ isDefault: false,
+ isInherited: false,
+ language: 'js',
+ languageName: 'JavaScript',
+ activeRuleCount: 10,
+ activeDeprecatedRuleCount: 2,
+ projectCount: 3,
+ ...overrides
+ };
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import ModalFormView from '../../../components/common/modal-form';
+import Template from '../templates/quality-profiles-change-profile-parent.hbs';
+import { changeProfileParent } from '../../../api/quality-profiles';
+
+export default ModalFormView.extend({
+ template: Template,
+
+ onRender () {
+ ModalFormView.prototype.onRender.apply(this, arguments);
+ this.$('select').select2({
+ width: '250px',
+ minimumResultsForSearch: 50
+ });
+ },
+
+ onFormSubmit () {
+ ModalFormView.prototype.onFormSubmit.apply(this, arguments);
+ this.disableForm();
+ this.sendRequest();
+ },
+
+ sendRequest () {
+ const parent = this.$('#change-profile-parent').val();
+ changeProfileParent(this.options.profile.key, parent)
+ .then(() => {
+ this.destroy();
+ this.trigger('done');
+ })
+ .catch(e => {
+ if (e.response.status === 400) {
+ this.enableForm();
+ e.response.json().then(r => this.showErrors(r.errors, r.warnings));
+ }
+ });
+ },
+
+ serializeData () {
+ const { profile } = this.options;
+ const profiles = this.options.profiles
+ .filter(p => p !== profile && p.language === profile.language);
+ return { ...profile, profiles };
+ }
+});
+
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import ModalFormView from '../../../components/common/modal-form';
+import Template from '../templates/quality-profiles-change-projects.hbs';
+import { translate } from '../../../helpers/l10n';
+import '../../../components/SelectList';
+
+export default ModalFormView.extend({
+ template: Template,
+
+ onRender () {
+ ModalFormView.prototype.onRender.apply(this, arguments);
+
+ const { key } = this.options.profile;
+
+ const searchUrl = window.baseUrl + '/api/qualityprofiles/projects?key=' +
+ encodeURIComponent(key);
+
+ new window.SelectList({
+ searchUrl,
+ el: this.$('#profile-projects'),
+ width: '100%',
+ readOnly: false,
+ focusSearch: false,
+ format (item) {
+ return item.name;
+ },
+ selectUrl: window.baseUrl + '/api/qualityprofiles/add_project',
+ deselectUrl: window.baseUrl + '/api/qualityprofiles/remove_project',
+ extra: {
+ profileKey: key
+ },
+ selectParameter: 'projectUuid',
+ selectParameterValue: 'uuid',
+ labels: {
+ selected: translate('quality_gates.projects.with'),
+ deselected: translate('quality_gates.projects.without'),
+ all: translate('quality_gates.projects.all'),
+ noResults: translate('quality_gates.projects.noResults')
+ },
+ tooltips: {
+ select: translate('quality_profiles.projects.select_hint'),
+ deselect: translate('quality_profiles.projects.deselect_hint')
+ }
+ });
+ },
+
+ onDestroy() {
+ this.options.loadProjects();
+ ModalFormView.prototype.onDestroy.apply(this, arguments);
+ }
+});
+
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import ModalFormView from '../../../components/common/modal-form';
+import Template from '../templates/quality-profiles-copy-profile.hbs';
+import { copyProfile } from '../../../api/quality-profiles';
+
+export default ModalFormView.extend({
+ template: Template,
+
+ onFormSubmit () {
+ ModalFormView.prototype.onFormSubmit.apply(this, arguments);
+ this.disableForm();
+ this.sendRequest();
+ },
+
+ sendRequest () {
+ const name = this.$('#copy-profile-name').val();
+ copyProfile(this.options.profile.key, name)
+ .then(profile => {
+ this.destroy();
+ this.trigger('done', profile);
+ })
+ .catch(e => {
+ if (e.response.status === 400) {
+ this.enableForm();
+ e.response.json().then(r => this.showErrors(r.errors, r.warnings));
+ }
+ });
+ },
+
+ serializeData () {
+ return this.options.profile;
+ }
+});
+
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import $ from 'jquery';
+import _ from 'underscore';
+import ModalFormView from '../../../components/common/modal-form';
+import Template from '../templates/quality-profiles-create-profile.hbs';
+import { createQualityProfile } from '../../../api/quality-profiles';
+
+export default ModalFormView.extend({
+ template: Template,
+
+ events () {
+ return _.extend(ModalFormView.prototype.events.apply(this, arguments), {
+ 'change #create-profile-language': 'onLanguageChange'
+ });
+ },
+
+ onFormSubmit () {
+ ModalFormView.prototype.onFormSubmit.apply(this, arguments);
+
+ const form = this.$('form')[0];
+ const data = new FormData(form);
+
+ createQualityProfile(data)
+ .then(r => {
+ this.trigger('done', r.profile);
+ this.destroy();
+ })
+ .catch(e => {
+ e.response.json().then(r => this.showErrors(r.errors, r.warnings));
+ });
+ },
+
+ onRender () {
+ ModalFormView.prototype.onRender.apply(this, arguments);
+ this.$('select').select2({
+ width: '250px',
+ minimumResultsForSearch: 50
+ });
+ this.onLanguageChange();
+ },
+
+ onLanguageChange () {
+ const that = this;
+ const language = this.$('#create-profile-language').val();
+ const importers = this.getImportersForLanguages(language);
+ this.$('.js-importer').each(function () {
+ that.emptyInput($(this));
+ $(this).addClass('hidden');
+ });
+ importers.forEach(function (importer) {
+ that.$(`.js-importer[data-key="${importer.key}"]`).removeClass('hidden');
+ });
+ },
+
+ emptyInput (e) {
+ e.wrap('<form>').closest('form').get(0).reset();
+ e.unwrap();
+ },
+
+ getImportersForLanguages (language) {
+ if (language != null) {
+ return this.options.importers.filter(function (importer) {
+ return importer.languages.indexOf(language) !== -1;
+ });
+ } else {
+ return [];
+ }
+ },
+
+ serializeData () {
+ return _.extend(ModalFormView.prototype.serializeData.apply(this, arguments), {
+ languages: this.options.languages,
+ importers: this.options.importers
+ });
+ }
+});
+
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import ModalFormView from '../../../components/common/modal-form';
+import Template from '../templates/quality-profiles-delete-profile.hbs';
+import { deleteProfile } from '../../../api/quality-profiles';
+
+export default ModalFormView.extend({
+ template: Template,
+
+ modelEvents: {
+ 'destroy': 'destroy'
+ },
+
+ onFormSubmit () {
+ ModalFormView.prototype.onFormSubmit.apply(this, arguments);
+ this.disableForm();
+ this.sendRequest();
+ },
+
+ sendRequest () {
+ deleteProfile(this.options.profile.key)
+ .then(() => {
+ this.destroy();
+ this.trigger('done');
+ })
+ .catch(e => {
+ if (e.response.status === 400) {
+ this.enableForm();
+ e.response.json().then(r => this.showErrors(r.errors, r.warnings));
+ }
+ });
+ },
+
+ serializeData () {
+ return this.options.profile;
+ }
+});
+
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import ModalFormView from '../../../components/common/modal-form';
+import Template from '../templates/quality-profiles-rename-profile.hbs';
+import { renameProfile } from '../../../api/quality-profiles';
+
+export default ModalFormView.extend({
+ template: Template,
+
+ onFormSubmit () {
+ ModalFormView.prototype.onFormSubmit.apply(this, arguments);
+ this.sendRequest();
+ },
+
+ sendRequest () {
+ const name = this.$('#rename-profile-name').val();
+ renameProfile(this.options.profile.key, name)
+ .then(profile => {
+ this.destroy();
+ this.trigger('done', profile);
+ })
+ .catch(e => {
+ if (e.response.status === 400) {
+ this.enableForm();
+ e.response.json().then(r => this.showErrors(r.errors, r.warnings));
+ }
+ });
+ },
+
+ serializeData () {
+ return this.options.profile;
+ }
+});
+
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import ModalFormView from '../../../components/common/modal-form';
+import Template from '../templates/quality-profiles-restore-built-in-profiles.hbs';
+import TemplateSuccess from '../templates/quality-profiles-restore-built-in-profiles-success.hbs';
+import { restoreBuiltInProfiles } from '../../../api/quality-profiles';
+
+export default ModalFormView.extend({
+ template: Template,
+ successTemplate: TemplateSuccess,
+
+ getTemplate () {
+ return this.done ? this.successTemplate : this.template;
+ },
+
+ onFormSubmit () {
+ ModalFormView.prototype.onFormSubmit.apply(this, arguments);
+ this.disableForm();
+ this.sendRequest();
+ },
+
+ sendRequest () {
+ restoreBuiltInProfiles(this.options.language.key)
+ .then(() => {
+ this.done = true;
+ this.render();
+ this.trigger('done');
+ })
+ .catch(e => {
+ this.enableForm();
+ e.response.json().then(r => this.showErrors(r.errors, r.warnings));
+ });
+ },
+
+ serializeData () {
+ return { language: this.options.language };
+ }
+});
+
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import ModalFormView from '../../../components/common/modal-form';
+import Template from '../templates/quality-profiles-restore-profile.hbs';
+import { restoreQualityProfile } from '../../../api/quality-profiles';
+
+export default ModalFormView.extend({
+ template: Template,
+
+ onFormSubmit (e) {
+ ModalFormView.prototype.onFormSubmit.apply(this, arguments);
+ const data = new FormData(e.currentTarget);
+
+ this.disableForm();
+
+ restoreQualityProfile(data)
+ .then(r => {
+ this.profile = r.profile;
+ this.ruleSuccesses = r.ruleSuccesses;
+ this.ruleFailures = r.ruleFailures;
+ this.render();
+ this.trigger('done');
+ })
+ .catch(e => {
+ this.enableForm();
+ e.response.json().then(r => this.showErrors(r.errors, r.warnings));
+ });
+ },
+
+ serializeData() {
+ return {
+ ...ModalFormView.prototype.serializeData.apply(this, arguments),
+ profile: this.profile,
+ ruleSuccesses: this.ruleSuccesses,
+ ruleFailures: this.ruleFailures
+ };
+ }
+});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import $ from 'jquery';
+import React from 'react';
+import pick from 'lodash/pick';
+import './styles.css';
+
+export default class DateInput extends React.Component {
+ static propTypes = {
+ value: React.PropTypes.string,
+ format: React.PropTypes.string,
+ name: React.PropTypes.string,
+ placeholder: React.PropTypes.string,
+ onChange: React.PropTypes.func.isRequired
+ };
+
+ static defaultProps = {
+ value: '',
+ format: 'yy-mm-dd'
+ };
+
+ componentDidMount () {
+ this.attachDatePicker();
+ }
+
+ componentWillReceiveProps (nextProps) {
+ this.refs.input.value = nextProps.value;
+ }
+
+ handleChange () {
+ const { value } = this.refs.input;
+ this.props.onChange(value);
+ }
+
+ attachDatePicker () {
+ const opts = {
+ dateFormat: this.props.format,
+ changeMonth: true,
+ changeYear: true,
+ onSelect: this.handleChange.bind(this)
+ };
+
+ if ($.fn && $.fn.datepicker) {
+ $(this.refs.input).datepicker(opts);
+ }
+ }
+
+ render () {
+ const inputProps = pick(this.props, ['placeholder', 'name']);
+
+ return (
+ <span className="date-input-control">
+ <input
+ className="date-input-control-input"
+ ref="input"
+ type="text"
+ initialValue={this.props.value}
+ readOnly={true}
+ {...inputProps}/>
+ <span className="date-input-control-icon">
+ <svg width="14" height="14" viewBox="0 0 16 16">
+ <path
+ d="M5.5 6h2v2h-2V6zm3 0h2v2h-2V6zm3 0h2v2h-2V6zm-9 6h2v2h-2v-2zm3 0h2v2h-2v-2zm3 0h2v2h-2v-2zm-3-3h2v2h-2V9zm3 0h2v2h-2V9zm3 0h2v2h-2V9zm-9 0h2v2h-2V9zm11-9v1h-2V0h-7v1h-2V0h-2v16h15V0h-2zm1 15h-13V4h13v11z"/>
+ </svg>
+ </span>
+ </span>
+ );
+ }
+}
--- /dev/null
+.date-input-control {
+ position: relative;
+ display: inline-block;
+ cursor: pointer;
+}
+
+.date-input-control-input {
+ width: 105px;
+ padding-left: 24px !important;
+ cursor: pointer;
+}
+
+.date-input-control-icon {
+ position: absolute;
+ top: 5px;
+ left: 5px;
+}
+
+.date-input-control-icon path {
+ fill: #cdcdcd;
+ transition: fill 0.3s ease;
+}
+
+.date-input-control-input:focus + .date-input-control-icon path {
+ fill: #4b9fd5;
+}
},
componentWillUpdate() {
- this.hideTooltips();
+ this.destroyTooltips();
},
componentDidUpdate () {
if (!this.props.severity) {
return null;
}
- return <span>
- <span className="spacer-right">
- <SeverityIcon severity={this.props.severity}/>
- </span>
- {translate('severity', this.props.severity)}
- </span>;
+ return (
+ <span>
+ <SeverityIcon severity={this.props.severity}/>
+ {' '}
+ {translate('severity', this.props.severity)}
+ </span>
+ );
}
});
export function getQualityGateUrl (key) {
return window.baseUrl + '/quality_gates/show/' + encodeURIComponent(key);
}
+
+/**
+ * Generate URL for the rules page
+ * @param {object} query
+ * @returns {string}
+ */
+export function getRulesUrl (query) {
+ if (query) {
+ const serializedQuery = Object.keys(query).map(criterion => (
+ `${encodeURIComponent(criterion)}=${encodeURIComponent(
+ query[criterion])}`
+ )).join('|');
+ return window.baseUrl + '/coding_rules#' + serializedQuery;
+ }
+ return window.baseUrl + '/coding_rules';
+}
+
+/**
+ * Generate URL for the rules page filtering only active deprecated rules
+ * @param {object} query
+ * @returns {string}
+ */
+export function getDeprecatedActiveRulesUrl (query = {}) {
+ const baseQuery = { activation: 'true', statuses: 'DEPRECATED' };
+ return getRulesUrl({ ...query, ...baseQuery });
+}
padding-bottom: 20px;
}
+.page-limited-small {
+ .page-limited;
+ width: 1080px;
+ box-sizing: border-box;
+}
+
.page-container {
min-width: 1080px;
}
render :action => 'index'
end
+ def create
+ render :action => 'index'
+ end
+
# GET /profiles/export?name=<profile name>&language=<language>&format=<exporter key>
def export
language = params[:language]
--recursive
---compilers js:babel-register,css:tests/null-compiler.js
+--compilers js:babel-register,css:tests/null-compiler.js,hbs:tests/null-compiler.js
--require tests/jsdom-setup.js
}
require.extensions['.css'] = nothing;
+require.extensions['.hbs'] = nothing;
name=Name
name_too_long_x=Name is too long (maximum is {0} characters)
navigation=Navigation
+never=Never
none=None
unassigned=Not assigned
off=Off
issue.type.CODE_SMELL=Code Smell
issue.type.BUG=Bug
issue.type.VULNERABILITY=Vulnerability
+issue.type.CODE_SMELL.plural=Code Smells
+issue.type.BUG.plural=Bugs
+issue.type.VULNERABILITY.plural=Vulnerabilities
+
issue.status.REOPENED=Reopened
issue.status.REOPENED.description=Transitioned to and then back from some other status.
quality_profiles.quality_profiles=Quality Profiles
quality_profiles.new_profile=New Profile
quality_profiles.compare_profiles=Compare Profiles
-quality_profiles.restore_profile=Restore Profile
+quality_profiles.compare_with=Compare with
+quality_profiles.restore_profile=Restore
quality_profiles.restore_submit=Restore
quality_profiles.restore_profile.success={1} rule(s) restored in profile "{0}"
quality_profiles.restore_profile.warning={1} rule(s) restored, {2} rule(s) ignored in profile "{0}"
quality_profiles.profile_inheritance=Inheritance
quality_profiles.available_projects=Available projects
quality_profiles.associated_projects=Associated projects
-quality_profiles.no_projects_associated_to_profile_x=No projects are explicitly associated to the profile "{0}".
+quality_profiles.no_projects_associated_to_profile=No projects are explicitly associated to the profile.
quality_profiles.projects_warning=List of projects explicitly associated to this Quality profile :
quality_profiles.including_x_overriding.suffix=, incl. {0} overriding
quality_profiles.set_parent=Set parent
quality_profiles.last_version_x_with_date=last version {0} ({1})
quality_profiles.version_x_with_date=version {0} ({1})
quality_profiles.version_x=version {0}
-quality_profiles.parameter_set_to_x=Parameter <strong>{0}</strong> set to <strong>{1}</strong>
+quality_profiles.parameter_set_to=Parameter {0} set to {1}
quality_profiles.only_in_profile_x=Only in {0}
quality_profiles.with_different_configuration=With different configuration
quality_profiles.with_same_configuration=With same configuration
quality_profiles.copy_overwrite_x=You are about to copy this quality profile into the existing "{0}" profile, which will fully overwrite it. Please confirm the name if you want to overwrite, or choose another name.
quality_profiles.copy_x_overwritten=Profile '{0}' has been overwritten.
quality_profiles.rename_x_title=Rename Profile {0} - {1}
-quality_profiles.restore_built_in_profiles=Restore Built-in Profiles
-quality_profiles.restore_built_in_profiles_confirmation=Are you sure you want to restore "{0}" profile(s) for {1}?
+quality_profiles.restore_built_in_profiles=Restore Built-in
+quality_profiles.restore_built_in_profiles_confirmation=Are you sure you want to restore {0} built-in profiles?
quality_profiles.restore_built_in_profiles_success_message={0} built-in profiles have been restored.
quality_profiles.including=including
quality_profiles.deprecated=deprecated
quality_profiles.manage_rules_tooltip_x_profile=Manage rules of profile '{0}'
quality_profiles.see_rules_tooltip=See rules of this profile
quality_profiles.see_rules_tooltip_x_profile=See rules of profile '{0}'
-quality_profiles.severity_set_to_x=Severity set to <strong>{0}</strong>
+quality_profiles.severity_set_to=Severity set to
quality_profiles.changelog_from=Changelog from
quality_profiles.changelog.empty=No changes have been done.
quality_profiles.changelog.ACTIVATED=Activated
quality_profiles.changelog.DEACTIVATED=Deactivated
quality_profiles.changelog.UPDATED=Updated
-quality_profiles.changelog.parameter_reset_to_default_value_x=Parameter <strong>{0}</strong> reset to default value
+quality_profiles.changelog.parameter_reset_to_default_value=Parameter {0} reset to default value
quality_profiles.deleted_profile=The profile {0} doesn't exist anymore
quality_profiles.projects_for_default=Every project not specifically associated to a quality profile will be associated to this one by default.
quality_profiles.projects_for_default.edit=You must not select specific projects for the default quality profile.
quality_profiles.inherits=Inherits "{0}"
+quality_profile.x_rules={0} rules
quality_profile.x_active_rules={0} active rules
+quality_profile.total_active_rules=Total Active Rules
quality_profiles.x_overridden_rules={0} overridden rules
quality_profiles.change_parent=Change Parent
quality_profiles.all_profiles=All Profiles
quality_profiles.x_profiles={0} Profiles
+quality_profiles._quality_profiles=quality profiles
quality_profiles.x_projects={0} projects
quality_profiles.no_results=No profiles found. Try installing a language plugin.
quality_profiles.projects.select_hint=Click to associate this project with the quality profile
quality_profiles.projects.deselect_hint=Click to remove association between this project and the quality profile
quality_profiles.no_profiles_for_comparison=There are no profiles for comparison
+quality_profile.empty_comparison=The quality profiles are equal.
quality_profiles.activate_more=Activate More
quality_profiles.intro1=Quality Profiles are collections of rules to apply during an analysis.
quality_profiles.intro2=For each language there is a default profile. All projects not explicitly assigned to some other profile will be analyzed with the default.
+quality_profiles.list.profile=Profile
+quality_profiles.list.inheritance=Inheritance
+quality_profiles.list.projects=Projects
+quality_profiles.list.rules=Rules
+quality_profiles.list.updated=Updated
+quality_profiles.list.used=Used
+quality_profiles.x_activated_out_of_y={0} rules activated out of {1} available
+quality_profiles.change_projects=Change Projects
+quality_profiles.not_found=The requested quality profile was not found.
+quality_profiles.latest_new_rules=Latest New Rules
+quality_profiles.latest_new_rules.activated={0}, activated on {1} profiles
+quality_profiles.latest_new_rules.not_activated={0}, not yet activated
+quality_profiles.deprecated_rules=Deprecated Rules
+quality_profiles.x_deprecated_rules_are_still_activated={0} deprecated rules are still activated on {1} quality profiles:
+quality_profiles.stagnant_profiles=Stagnant Profiles
+quality_profiles.not_updated_more_than_year=The following profiles haven't been updated for more than 1 year: