Browse Source

SONAR-9008 support quality profiles for organizations

tags/6.4-RC1
Stas Vilchik 7 years ago
parent
commit
32a73efa05
76 changed files with 2383 additions and 513 deletions
  1. 3
    1
      it/it-tests/src/test/java/it/Category5Suite.java
  2. 206
    0
      it/it-tests/src/test/java/it/organization/OrganizationQualityProfilesPageTest.java
  3. 60
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_compare.html
  4. 110
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_copy.html
  5. 110
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_create.html
  6. 115
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_delete.html
  7. 55
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_changelog.html
  8. 74
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_list.html
  9. 50
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_exporters.html
  10. 60
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_inheritance.html
  11. 50
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_projects.html
  12. 45
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_rules.html
  13. 89
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_filter_by_language.html
  14. 54
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_open_from_list.html
  15. 110
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_rename.html
  16. 80
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_restore.html
  17. 95
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_restore_built_in.html
  18. 100
    0
      it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_set_default.html
  19. 16
    63
      server/sonar-web/src/main/js/api/quality-profiles.js
  20. 2
    1
      server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js
  21. 7
    0
      server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js
  22. 27
    0
      server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap
  23. 5
    0
      server/sonar-web/src/main/js/apps/organizations/routes.js
  24. 6
    1
      server/sonar-web/src/main/js/apps/overview/meta/Meta.js
  25. 15
    1
      server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js
  26. 25
    18
      server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js
  27. 7
    2
      server/sonar-web/src/main/js/apps/project-admin/store/actions.js
  28. 14
    4
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.js
  29. 50
    31
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.js
  30. 2
    1
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.js
  31. 12
    9
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.js
  32. 7
    4
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.js
  33. 8
    5
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.js
  34. 7
    4
      server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.js
  35. 32
    23
      server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.js
  36. 2
    1
      server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.js
  37. 12
    8
      server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.js
  38. 15
    14
      server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.js
  39. 41
    10
      server/sonar-web/src/main/js/apps/quality-profiles/components/App.js
  40. 9
    4
      server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js
  41. 33
    40
      server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js
  42. 23
    12
      server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js
  43. 9
    6
      server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.js
  44. 12
    7
      server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.js
  45. 10
    2
      server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.js
  46. 13
    7
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js
  47. 24
    13
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.js
  48. 28
    17
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.js
  49. 66
    40
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.js
  50. 17
    13
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.js
  51. 26
    17
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.js
  52. 46
    22
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js
  53. 9
    6
      server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.js
  54. 13
    7
      server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.js
  55. 15
    6
      server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.js
  56. 8
    1
      server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.js
  57. 13
    6
      server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.js
  58. 14
    1
      server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js
  59. 32
    22
      server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js
  60. 31
    15
      server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js
  61. 13
    9
      server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.js
  62. 18
    11
      server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js
  63. 25
    0
      server/sonar-web/src/main/js/apps/quality-profiles/propTypes.js
  64. 8
    2
      server/sonar-web/src/main/js/apps/quality-profiles/routes.js
  65. 13
    11
      server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-create-profile.hbs
  66. 3
    0
      server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-restore-profile.hbs
  67. 53
    8
      server/sonar-web/src/main/js/apps/quality-profiles/utils.js
  68. 1
    0
      server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeParentView.js
  69. 3
    0
      server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeProjectsView.js
  70. 1
    0
      server/sonar-web/src/main/js/apps/quality-profiles/views/CopyProfileView.js
  71. 3
    1
      server/sonar-web/src/main/js/apps/quality-profiles/views/CreateProfileView.js
  72. 1
    0
      server/sonar-web/src/main/js/apps/quality-profiles/views/DeleteProfileView.js
  73. 1
    0
      server/sonar-web/src/main/js/apps/quality-profiles/views/RenameProfileView.js
  74. 5
    1
      server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreBuiltInProfilesView.js
  75. 2
    0
      server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreProfileView.js
  76. 4
    5
      server/sonar-web/src/main/js/helpers/urls.js

+ 3
- 1
it/it-tests/src/test/java/it/Category5Suite.java View File

@@ -20,6 +20,7 @@
package it;

import it.organization.OrganizationMembershipTest;
import it.organization.OrganizationQualityProfilesPageTest;
import it.serverSystem.ClusterTest;
import it.serverSystem.RestartTest;
import it.serverSystem.ServerSystemRestartingOrchestrator;
@@ -47,7 +48,8 @@ import org.junit.runners.Suite;
UpdateCenterTest.class,
RealmAuthenticationTest.class,
SsoAuthenticationTest.class,
OrganizationMembershipTest.class
OrganizationMembershipTest.class,
OrganizationQualityProfilesPageTest.class
})
public class Category5Suite {


+ 206
- 0
it/it-tests/src/test/java/it/organization/OrganizationQualityProfilesPageTest.java View File

@@ -0,0 +1,206 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info 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.organization;

import com.codeborne.selenide.Condition;
import com.sonar.orchestrator.Orchestrator;
import com.sonar.orchestrator.build.SonarScanner;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.organization.CreateWsRequest;
import pageobjects.Navigation;

import static com.codeborne.selenide.Selenide.$;
import static java.util.Collections.emptyMap;
import static util.ItUtils.newAdminWsClient;
import static util.ItUtils.projectDir;
import static util.ItUtils.xooPlugin;
import static util.selenium.Selenese.runSelenese;

public class OrganizationQualityProfilesPageTest {

@ClassRule
public static final Orchestrator orchestrator = Orchestrator.builderEnv()
.addPlugin(xooPlugin())
.build();

private static WsClient adminWsClient;
private static final String ORGANIZATION = "test-org";

@BeforeClass
public static void setUp() {
adminWsClient = newAdminWsClient(orchestrator);
orchestrator.resetData();
orchestrator.getServer().post("api/organizations/enable_support", emptyMap());
createOrganization();
}

@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 testNoGlobalPage(){
Navigation nav = Navigation.get(orchestrator);
nav.open("/profiles");
$(".page-wrapper-simple").should(Condition.visible);
}

@Test
public void testHomePage() throws Exception {
runSelenese(orchestrator,
"/organization/OrganizationQualityProfilesPageTest/should_display_list.html",
"/organization/OrganizationQualityProfilesPageTest/should_open_from_list.html",
"/organization/OrganizationQualityProfilesPageTest/should_filter_by_language.html");
}

@Test
public void testProfilePage() throws Exception {
runSelenese(orchestrator,
"/organization/OrganizationQualityProfilesPageTest/should_display_profile_rules.html",
"/organization/OrganizationQualityProfilesPageTest/should_display_profile_inheritance.html",
"/organization/OrganizationQualityProfilesPageTest/should_display_profile_projects.html",
"/organization/OrganizationQualityProfilesPageTest/should_display_profile_exporters.html");
}

@Test
public void testNotFound() {
Navigation nav = Navigation.get(orchestrator);
nav.open("/organizations/test-org/quality_profiles/show?key=unknown");
$(".quality-profile-not-found").should(Condition.visible);
}

@Test
public void testProfileChangelog() throws Exception {
runSelenese(orchestrator,
"/organization/OrganizationQualityProfilesPageTest/should_display_changelog.html");
}

@Ignore("find a way to know profile key inside selenium tests")
@Test
public void testComparison() throws Exception {
runSelenese(orchestrator, "/organization/OrganizationQualityProfilesPageTest/should_compare.html");
}

@Test
public void testCreation() throws Exception {
runSelenese(orchestrator, "/organization/OrganizationQualityProfilesPageTest/should_create.html");
}

@Test
public void testDeletion() throws Exception {
runSelenese(orchestrator, "/organization/OrganizationQualityProfilesPageTest/should_delete.html");
}

@Test
public void testCopying() throws Exception {
runSelenese(orchestrator, "/organization/OrganizationQualityProfilesPageTest/should_copy.html");
}

@Test
public void testRenaming() throws Exception {
runSelenese(orchestrator, "/organization/OrganizationQualityProfilesPageTest/should_rename.html");
}

@Test
public void testSettingDefault() throws Exception {
runSelenese(orchestrator, "/organization/OrganizationQualityProfilesPageTest/should_set_default.html");
}

@Test
public void testRestoration() throws Exception {
deleteProfile("xoo", "empty");

runSelenese(orchestrator,
"/organization/OrganizationQualityProfilesPageTest/should_restore.html",
"/organization/OrganizationQualityProfilesPageTest/should_restore_built_in.html");
}

private static void createProfile(String language, String name) {
adminWsClient.wsConnector().call(
new PostRequest("api/qualityprofiles/create")
.setParam("language", language)
.setParam("name", name)
.setParam("organization", ORGANIZATION));
}

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)
.setParam("organization", ORGANIZATION));
}

private static void analyzeProject(String path) {
orchestrator.executeBuild(SonarScanner.create(projectDir(path)).setProperties(
"sonar.organization", ORGANIZATION,
"sonar.login", "admin",
"sonar.password", "admin"
));
}

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("organization", ORGANIZATION)
.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)
.setParam("organization", ORGANIZATION));
}

private static void setDefault(String language, String name) {
adminWsClient.wsConnector().call(
new PostRequest("api/qualityprofiles/set_default")
.setParam("language", language)
.setParam("profileName", name)
.setParam("organization", ORGANIZATION));
}

private static void createOrganization() {
adminWsClient.organizations().create(new CreateWsRequest.Builder().setKey(ORGANIZATION).setName(ORGANIZATION).build());
}
}

+ 60
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_compare.html View File

@@ -0,0 +1,60 @@
<?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>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=.quality-profiles-table-name a[href^=&quot;/organizations/test-org/quality_profiles/show?key=xoo-sample&quot;]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=.quality-profiles-table-name a[href^=&quot;/organizations/test-org/quality_profiles/show?key=xoo-sample&quot;]</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>

+ 110
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_copy.html View File

@@ -0,0 +1,110 @@
<?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>waitForElementPresent</td>
<td>css=.js-user-authenticated</td>
<td></td>
</tr>
<tr>
<td>open</td>
<td>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;] .quality-profiles-table-name a</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;] .quality-profiles-table-name a</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>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;copied&quot;]</td>
<td></td>
</tr>
</tbody>
</table>
</body>
</html>

+ 110
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_create.html View File

@@ -0,0 +1,110 @@
<?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>waitForElementPresent</td>
<td>css=.js-user-authenticated</td>
<td></td>
</tr>
<tr>
<td>open</td>
<td>/organizations/test-org/quality_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>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=.quality-profiles-table-row[data-name=&quot;test&quot;]</td>
<td></td>
</tr>
</tbody>
</table>
</body>
</html>

+ 115
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_delete.html View File

@@ -0,0 +1,115 @@
<?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>waitForElementPresent</td>
<td>css=.js-user-authenticated</td>
<td></td>
</tr>
<tr>
<td>open</td>
<td>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;] .quality-profiles-table-name a</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;] .quality-profiles-table-name a</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=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;Basic&quot;]</td>
<td></td>
</tr>
<tr>
<td>waitForElementNotPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;]</td>
<td></td>
</tr>
<tr>
<td>open</td>
<td>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;Basic&quot;]</td>
<td></td>
</tr>
<tr>
<td>assertElementNotPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;]</td>
<td></td>
</tr>
</tbody>
</table>
</body>
</html>

+ 55
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_changelog.html View File

@@ -0,0 +1,55 @@
<?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>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;Basic&quot;] .quality-profiles-table-name a</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;Basic&quot;] .quality-profiles-table-name a</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=a[href^=&quot;/organizations/test-org/quality_profiles/changelog&quot;]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=a[href^=&quot;/organizations/test-org/quality_profiles/changelog&quot;]</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>*Administrator*Activated*Has Tag*Major*tag*xoo*</td>
</tr>
</tbody>
</table>
</body>
</html>

+ 74
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_list.html View File

@@ -0,0 +1,74 @@
<?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>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>/organizations/test-org/quality_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[data-language=&quot;xoo&quot;]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>css=.quality-profiles-table .data[data-language=&quot;xoo2&quot;]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;Basic&quot;]</td>
<td></td>
</tr>
<tr>
<td>assertText</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;Basic&quot;] .quality-profiles-table-name</td>
<td>*Basic*</td>
</tr>
<tr>
<td>assertText</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;Basic&quot;] .quality-profiles-table-projects</td>
<td>*Default*</td>
</tr>
<tr>
<td>assertText</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;Basic&quot;] .quality-profiles-table-rules</td>
<td>*1*</td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;Basic&quot;] .quality-profiles-table-rules a[href^=&quot;/organizations/test-org/rules#qprofile&quot;]</td>
<td></td>
</tr>
<tr>
<td>assertText</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;empty&quot;] .quality-profiles-table-projects</td>
<td>*0*</td>
</tr>
<tr>
<td>assertText</td>
<td>css=table[data-language=&quot;xoo2&quot;] tr[data-name=&quot;Basic&quot;] .quality-profiles-table-name</td>
<td>*Basic*</td>
</tr>
</tbody>
</table>
</body>
</html>

+ 50
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_exporters.html View File

@@ -0,0 +1,50 @@
<?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>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;] .quality-profiles-table-name a</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;] .quality-profiles-table-name a</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=&quot;XooFakeExporter&quot;]</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=.quality-profile-exporters a[href^=&quot;/api/qualityprofiles/export&quot;]</td>
<td></td>
</tr>
</tbody>
</table>
</body>
</html>

+ 60
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_inheritance.html View File

@@ -0,0 +1,60 @@
<?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>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;] .quality-profiles-table-name a</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;] .quality-profiles-table-name a</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>

+ 50
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_projects.html View File

@@ -0,0 +1,50 @@
<?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>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;] .quality-profiles-table-name a</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;] .quality-profiles-table-name a</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>

+ 45
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_rules.html View File

@@ -0,0 +1,45 @@
<?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>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;Basic&quot;] .quality-profiles-table-name a</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;Basic&quot;] .quality-profiles-table-name a</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>

+ 89
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_filter_by_language.html View File

@@ -0,0 +1,89 @@
<?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>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>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=.quality-profiles-table .data[data-language=&quot;xoo&quot;]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>css=.quality-profiles-table .data[data-language=&quot;xoo2&quot;]</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=&quot;xoo2&quot;]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=.js-language-filter-option[data-language=&quot;xoo2&quot;]</td>
<td></td>
</tr>
<tr>
<td>waitForElementNotPresent</td>
<td>css=.quality-profiles-table[data-language=&quot;xoo&quot;]</td>
<td></td>
</tr>
<tr>
<td>assertElementPresent</td>
<td>css=.quality-profiles-table .data[data-language=&quot;xoo2&quot;]</td>
<td></td>
</tr>
<tr>
<td>assertText</td>
<td>css=.js-language-filter</td>
<td>*Xoo2*</td>
</tr>
<tr>
<td>open</td>
<td>/organizations/test-org/quality_profiles?language=xoo2</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=.quality-profiles-table .data[data-language=&quot;xoo2&quot;]</td>
<td></td>
</tr>
<tr>
<td>assertElementNotPresent</td>
<td>css=.quality-profiles-table[data-language=&quot;xoo&quot;]</td>
<td></td>
</tr>
<tr>
<td>assertText</td>
<td>css=.js-language-filter</td>
<td>*Xoo2*</td>
</tr>
</tbody>
</table>
</body>
</html>

+ 54
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_open_from_list.html View File

@@ -0,0 +1,54 @@
<?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>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>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;Basic&quot;]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;Basic&quot;] .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>

+ 110
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_rename.html View File

@@ -0,0 +1,110 @@
<?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>waitForElementPresent</td>
<td>css=.js-user-authenticated</td>
<td></td>
</tr>
<tr>
<td>open</td>
<td>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;] .quality-profiles-table-name a</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;] .quality-profiles-table-name a</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>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;new name&quot;] .quality-profiles-table-name a</td>
<td></td>
</tr>
</tbody>
</table>
</body>
</html>

+ 80
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_restore.html View File

@@ -0,0 +1,80 @@
<?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>waitForElementPresent</td>
<td>css=.js-user-authenticated</td>
<td></td>
</tr>
<tr>
<td>open</td>
<td>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=.js-more-admin-actions</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=.js-more-admin-actions</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>

+ 95
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_restore_built_in.html View File

@@ -0,0 +1,95 @@
<?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>waitForElementPresent</td>
<td>css=.js-user-authenticated</td>
<td></td>
</tr>
<tr>
<td>open</td>
<td>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=.quality-profiles-table-row[data-name=&quot;sample&quot;]</td>
<td></td>
</tr>
<tr>
<td>assertElementNotPresent</td>
<td>css=.quality-profiles-table-row[data-name=&quot;empty&quot;]</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=.js-more-admin-actions</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=#quality-profiles-restore-built-in</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=&quot;empty&quot;]</td>
<td></td>
</tr>
</tbody>
</table>
</body>
</html>

+ 100
- 0
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_set_default.html View File

@@ -0,0 +1,100 @@
<?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>waitForElementPresent</td>
<td>css=.js-user-authenticated</td>
<td></td>
</tr>
<tr>
<td>open</td>
<td>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;] .quality-profiles-table-name a</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>css=table[data-language=&quot;xoo&quot;] tr[data-name=&quot;sample&quot;] .quality-profiles-table-name a</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>/organizations/test-org/quality_profiles</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=.quality-profiles-table-row[data-name=&quot;sample&quot;]</td>
<td></td>
</tr>
<tr>
<td>assertText</td>
<td>css=.quality-profiles-table-row[data-name=&quot;sample&quot;] .quality-profiles-table-projects</td>
<td>*Default*</td>
</tr>
</tbody>
</table>
</body>
</html>

+ 16
- 63
server/sonar-web/src/main/js/api/quality-profiles.js View File

@@ -17,14 +17,15 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import { request, checkStatus, parseJSON, getJSON, post, postJSON } from '../helpers/request';

export function getQualityProfiles(data) {
export function getQualityProfiles(data: { organization?: string, projectKey?: string }) {
const url = '/api/qualityprofiles/search';
return getJSON(url, data).then(r => r.profiles);
}

export function createQualityProfile(data) {
export function createQualityProfile(data: Object) {
return request('/api/qualityprofiles/create')
.setMethod('post')
.setData(data)
@@ -33,7 +34,7 @@ export function createQualityProfile(data) {
.then(parseJSON);
}

export function restoreQualityProfile(data) {
export function restoreQualityProfile(data: Object) {
return request('/api/qualityprofiles/restore')
.setMethod('post')
.setData(data)
@@ -42,128 +43,80 @@ export function restoreQualityProfile(data) {
.then(parseJSON);
}

export function getProfileProjects(data) {
export function getProfileProjects(data: Object) {
const url = '/api/qualityprofiles/projects';
return getJSON(url, data);
}

export function getProfileInheritance(profileKey) {
export function getProfileInheritance(profileKey: string) {
const url = '/api/qualityprofiles/inheritance';
const data = { profileKey };
return getJSON(url, data);
}

export function setDefaultProfile(profileKey) {
export function setDefaultProfile(profileKey: string) {
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) {
export function renameProfile(key: string, name: string) {
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) {
export function copyProfile(fromKey: string, toName: string) {
const url = '/api/qualityprofiles/copy';
const data = { fromKey, toName };
return postJSON(url, data);
}

/**
* Delete profile
* @param {string} profileKey
* @returns {Promise}
*/
export function deleteProfile(profileKey) {
export function deleteProfile(profileKey: string) {
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) {
export function changeProfileParent(profileKey: string, parentKey: string) {
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) {
export function restoreBuiltInProfiles(data: Object) {
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) {
export function getProfileChangelog(data: Object) {
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) {
export function compareProfiles(leftKey: string, rightKey: string) {
const url = '/api/qualityprofiles/compare';
const data = { leftKey, rightKey };
return getJSON(url, data);
}

export function associateProject(profileKey, projectKey) {
export function associateProject(profileKey: string, projectKey: string) {
const url = '/api/qualityprofiles/add_project';
const data = { profileKey, projectKey };
return post(url, data);
}

export function dissociateProject(profileKey, projectKey) {
export function dissociateProject(profileKey: string, projectKey: string) {
const url = '/api/qualityprofiles/remove_project';
const data = { profileKey, projectKey };
return post(url, data);

+ 2
- 1
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js View File

@@ -143,6 +143,7 @@ export default class GlobalNavMenu extends React.Component {

render() {
const governanceInstalled = this.props.appState.qualifiers.includes('VW');
const { organizationsEnabled } = this.props.appState;

return (
<ul className="nav navbar-nav">
@@ -150,7 +151,7 @@ export default class GlobalNavMenu extends React.Component {
{governanceInstalled && this.renderPortfolios()}
{this.renderIssuesLink()}
{this.renderRulesLink()}
{this.renderProfilesLink()}
{!organizationsEnabled && this.renderProfilesLink()}
{this.renderQualityGatesLink()}
{this.renderAdministrationLink()}
{this.renderMore()}

+ 7
- 0
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js View File

@@ -154,6 +154,13 @@ export default class OrganizationNavigation extends React.Component {
{translate('organization.members.page')}
</Link>
</li>
<li>
<Link
to={`/organizations/${organization.key}/quality_profiles`}
activeClassName="active">
{translate('quality_profiles.page')}
</Link>
</li>
{organization.canAdmin && this.renderAdministration()}
</ul>
</div>

+ 27
- 0
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap View File

@@ -44,6 +44,15 @@ exports[`test admin 1`] = `
organization.members.page
</Link>
</li>
<li>
<Link
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
to="/organizations/foo/quality_profiles">
quality_profiles.page
</Link>
</li>
<li
className="">
<a
@@ -165,6 +174,15 @@ exports[`test regular user 1`] = `
organization.members.page
</Link>
</li>
<li>
<Link
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
to="/organizations/foo/quality_profiles">
quality_profiles.page
</Link>
</li>
</ul>
</div>
</div>
@@ -217,6 +235,15 @@ exports[`test undeletable org 1`] = `
organization.members.page
</Link>
</li>
<li>
<Link
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
to="/organizations/foo/quality_profiles">
quality_profiles.page
</Link>
</li>
<li
className="">
<a

+ 5
- 0
server/sonar-web/src/main/js/apps/organizations/routes.js View File

@@ -28,6 +28,7 @@ import OrganizationPermissions from './components/OrganizationPermissions';
import OrganizationPermissionTemplates from './components/OrganizationPermissionTemplates';
import OrganizationProjectsManagement from './components/OrganizationProjectsManagement';
import OrganizationDelete from './components/OrganizationDelete';
import qualityProfilesRoutes from '../quality-profiles/routes';

const routes = [
{
@@ -54,6 +55,10 @@ const routes = [
path: 'members',
component: OrganizationMembersContainer
},
{
path: 'quality_profiles',
childRoutes: qualityProfilesRoutes
},
{
component: OrganizationAdmin,
childRoutes: [

+ 6
- 1
server/sonar-web/src/main/js/apps/overview/meta/Meta.js View File

@@ -57,7 +57,12 @@ const Meta = ({ component, measures, areThereCustomOrganizations }) => {

{shouldShowQualityGate && <MetaQualityGate gate={qualityGate} />}

{shouldShowQualityProfiles && <MetaQualityProfiles profiles={qualityProfiles} />}
{shouldShowQualityProfiles &&
<MetaQualityProfiles
component={component}
customOrganizations={areThereCustomOrganizations}
profiles={qualityProfiles}
/>}

<MetaLinks component={component} />


+ 15
- 1
server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js View File

@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
@@ -27,6 +28,15 @@ import { searchRules } from '../../../api/rules';
import { getLanguages } from '../../../store/rootReducer';

class MetaQualityProfiles extends React.Component {
mounted: boolean;

props: {
component: { organization: string },
customOrganizations: boolean,
languages: { [string]: { name: string } },
profiles: Array<{ key: string, language: string, name: string }>
};

state = {
deprecatedByKey: {}
};
@@ -74,12 +84,16 @@ class MetaQualityProfiles extends React.Component {
const languageFromStore = this.props.languages[profile.language];
const languageName = languageFromStore ? languageFromStore.name : profile.language;

const path = this.props.customOrganizations
? getQualityProfileUrl(profile.key, this.props.component.organization)
: getQualityProfileUrl(profile.key);

const inner = (
<div className="text-ellipsis">
<span className="note spacer-right">
{'(' + languageName + ')'}
</span>
<Link to={getQualityProfileUrl(profile.key)}>
<Link to={path}>
{profile.name}
</Link>
</div>

+ 25
- 18
server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js View File

@@ -17,36 +17,42 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { connect } from 'react-redux';
import shallowCompare from 'react-addons-shallow-compare';
import Header from './Header';
import Table from './Table';
import { fetchProjectProfiles, setProjectProfile } from '../store/actions';
import {
areThereCustomOrganizations,
getProjectAdminAllProfiles,
getProjectAdminProjectProfiles,
getComponent
} from '../../../store/rootReducer';

class QualityProfiles extends React.Component {
static propTypes = {
component: React.PropTypes.object.isRequired,
allProfiles: React.PropTypes.array,
profiles: React.PropTypes.array
};
type Props = {
allProfiles: Array<{}>,
component: { key: string, organization: string },
customOrganizations: boolean,
fetchProjectProfiles: (componentKey: string, organization?: string) => void,
profiles: Array<{}>,
setProjectProfile: (string, string, string) => void
};

componentDidMount() {
this.props.fetchProjectProfiles(this.props.component.key);
}
class QualityProfiles extends React.PureComponent {
props: Props;

shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
componentDidMount() {
if (this.props.customOrganizations) {
this.props.fetchProjectProfiles(this.props.component.key, this.props.component.organization);
} else {
this.props.fetchProjectProfiles(this.props.component.key);
}
}

handleChangeProfile(oldKey, newKey) {
handleChangeProfile = (oldKey, newKey) => {
this.props.setProjectProfile(this.props.component.key, oldKey, newKey);
}
};

render() {
const { allProfiles, profiles } = this.props;
@@ -59,7 +65,7 @@ class QualityProfiles extends React.Component {
? <Table
allProfiles={allProfiles}
profiles={profiles}
onChangeProfile={this.handleChangeProfile.bind(this)}
onChangeProfile={this.handleChangeProfile}
/>
: <i className="spinner" />}
</div>
@@ -69,10 +75,11 @@ class QualityProfiles extends React.Component {

const mapStateToProps = (state, ownProps) => ({
component: getComponent(state, ownProps.location.query.id),
customOrganizations: areThereCustomOrganizations(state),
allProfiles: getProjectAdminAllProfiles(state),
profiles: getProjectAdminProjectProfiles(state, ownProps.location.query.id)
});

export default connect(mapStateToProps, { fetchProjectProfiles, setProjectProfile })(
QualityProfiles
);
const mapDispatchToProps = { fetchProjectProfiles, setProjectProfile };
export default connect(mapStateToProps, mapDispatchToProps)(QualityProfiles);

+ 7
- 2
server/sonar-web/src/main/js/apps/project-admin/store/actions.js View File

@@ -47,9 +47,14 @@ export const receiveProjectProfiles = (projectKey, profiles) => ({
profiles
});

export const fetchProjectProfiles = projectKey =>
export const fetchProjectProfiles = (projectKey, organization) =>
dispatch => {
Promise.all([getQualityProfiles(), getQualityProfiles({ projectKey })]).then(responses => {
Promise.all([
organization ? getQualityProfiles({ organization }) : getQualityProfiles(),
organization
? getQualityProfiles({ organization, projectKey })
: getQualityProfiles({ projectKey })
]).then(responses => {
const [allProfiles, projectProfiles] = responses;
dispatch(receiveProfiles(allProfiles));
dispatch(receiveProjectProfiles(projectKey, projectProfiles));

+ 14
- 4
server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.js View File

@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { Link } from 'react-router';
import moment from 'moment';
@@ -24,10 +25,19 @@ 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
};
type Props = {
events: Array<{
action: string,
authorName: string,
date: string,
params?: {},
ruleKey: string,
ruleName: string
}>
};

export default class Changelog extends React.PureComponent {
props: Props;

render() {
let isEvenRow = false;

+ 50
- 31
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.js View File

@@ -17,40 +17,52 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
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';
import { translate } from '../../../helpers/l10n';

export default class ChangelogContainer extends React.Component {
static propTypes = {
location: React.PropTypes.object.isRequired,
profile: ProfileType
};
import { getProfileChangelogPath } from '../utils';
import type { Profile } from '../propTypes';

type Props = {
location: {
query: {
since?: string,
to?: string
}
},
organization: ?string,
profile: Profile
};

type State = {
events?: Array<*>,
loading: boolean,
page?: number,
total?: number
};

export default class ChangelogContainer extends React.PureComponent {
mounted: boolean;
props: Props;

static contextTypes = {
router: React.PropTypes.object
};

state = {
state: 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) {
componentDidUpdate(prevProps: Props) {
if (prevProps.location !== this.props.location) {
this.loadChangelog();
}
@@ -63,7 +75,7 @@ export default class ChangelogContainer extends React.Component {
loadChangelog() {
this.setState({ loading: true });
const { query } = this.props.location;
const data = { profileKey: this.props.profile.key };
const data: Object = { profileKey: this.props.profile.key };
if (query.since) {
data.since = query.since;
}
@@ -83,13 +95,13 @@ export default class ChangelogContainer extends React.Component {
});
}

loadMore(e) {
loadMore(e: SyntheticInputEvent) {
e.preventDefault();
e.target.blur();

this.setState({ loading: true });
const { query } = this.props.location;
const data = {
const data: Object = {
profileKey: this.props.profile.key,
p: this.state.page + 1
};
@@ -101,7 +113,7 @@ export default class ChangelogContainer extends React.Component {
}

getProfileChangelog(data).then(r => {
if (this.mounted) {
if (this.mounted && this.state.events) {
this.setState({
events: [...this.state.events, ...r.events],
total: r.total,
@@ -112,25 +124,32 @@ export default class ChangelogContainer extends React.Component {
});
}

handleFromDateChange(fromDate) {
const query = { ...this.props.location.query, since: fromDate };
this.context.router.push({ pathname: '/profiles/changelog', query });
}
handleFromDateChange = (fromDate?: string) => {
const path = getProfileChangelogPath(this.props.profile.key, this.props.organization, {
since: fromDate,
to: this.props.location.query.to
});
this.context.router.push(path);
};

handleToDateChange(toDate) {
const query = { ...this.props.location.query, to: toDate };
this.context.router.push({ pathname: '/profiles/changelog', query });
}
handleToDateChange = (toDate?: string) => {
const path = getProfileChangelogPath(this.props.profile.key, this.props.organization, {
since: this.props.location.query.since,
to: toDate
});
this.context.router.push(path);
};

handleReset() {
const query = { key: this.props.profile.key };
this.context.router.push({ pathname: '/profiles/changelog', query });
}
handleReset = () => {
const path = getProfileChangelogPath(this.props.profile.key, this.props.organization);
this.context.router.push(path);
};

render() {
const { query } = this.props.location;

const shouldDisplayFooter = this.state.events != null &&
this.state.total != null &&
this.state.events.length < this.state.total;

return (

+ 2
- 1
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.js View File

@@ -17,10 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { translate } from '../../../helpers/l10n';

export default class ChangelogEmpty extends React.Component {
export default class ChangelogEmpty extends React.PureComponent {
render() {
return (
<div className="big-spacer-top">

+ 12
- 9
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.js View File

@@ -17,20 +17,23 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
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
};
type Props = {
fromDate?: string,
toDate?: string,
onFromDateChange: () => void,
onReset: () => void,
onToDateChange: () => void
};

handleResetClick(e) {
export default class ChangelogSearch extends React.PureComponent {
props: Props;

handleResetClick(e: SyntheticInputEvent) {
e.preventDefault();
e.target.blur();
this.props.onReset();

+ 7
- 4
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.js View File

@@ -17,14 +17,17 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
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
};
type Props = {
changes: { [string]: ?string }
};

export default class ChangesList extends React.PureComponent {
props: Props;

render() {
const { changes } = this.props;

+ 8
- 5
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.js View File

@@ -17,14 +17,17 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
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
};
type Props = {
name: string,
value: ?string
};

export default class ParameterChange extends React.PureComponent {
props: Props;

render() {
const { name, value } = this.props;

+ 7
- 4
server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.js View File

@@ -17,14 +17,17 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
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
};
type Props = {
severity: ?string
};

export default class SeverityChange extends React.PureComponent {
props: Props;

render() {
return (

+ 32
- 23
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.js View File

@@ -17,28 +17,42 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import ComparisonForm from './ComparisonForm';
import ComparisonResults from './ComparisonResults';
import { ProfileType, ProfilesListType } from '../propTypes';
import { compareProfiles } from '../../../api/quality-profiles';
import { getProfileComparePath } from '../utils';
import type { Profile } from '../propTypes';

export default class ComparisonContainer extends React.Component {
static propTypes = {
profile: ProfileType,
profiles: ProfilesListType
};
type Props = {
location: { query: { withKey?: string } },
organization: ?string,
profile: Profile,
profiles: Array<Profile>
};

type State = {
loading: boolean,
left?: { name: string },
right?: { name: string },
inLeft?: Array<*>,
inRight?: Array<*>,
modified?: Array<*>
};

export default class ComparisonContainer extends React.PureComponent {
mounted: boolean;
props: Props;
state: State;

static contextTypes = {
router: React.PropTypes.object
};

state = {
loading: false
};

componentWillMount() {
this.handleCompare = this.handleCompare.bind(this);
constructor(props: Props) {
super(props);
this.state = { loading: false };
}

componentDidMount() {
@@ -46,7 +60,7 @@ export default class ComparisonContainer extends React.Component {
this.loadResults();
}

componentDidUpdate(prevProps) {
componentDidUpdate(prevProps: Props) {
if (prevProps.profile !== this.props.profile || prevProps.location !== this.props.location) {
this.loadResults();
}
@@ -59,7 +73,7 @@ export default class ComparisonContainer extends React.Component {
loadResults() {
const { withKey } = this.props.location.query;
if (!withKey) {
this.setState({ left: null, loading: false });
this.setState({ left: undefined, loading: false });
return;
}

@@ -78,15 +92,10 @@ export default class ComparisonContainer extends React.Component {
});
}

handleCompare(withKey) {
this.context.router.push({
pathname: '/profiles/compare',
query: {
key: this.props.profile.key,
withKey
}
});
}
handleCompare = (withKey: string) => {
const path = getProfileComparePath(this.props.profile.key, this.props.organization, withKey);
this.context.router.push(path);
};

render() {
const { profile, profiles, location } = this.props;

+ 2
- 1
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.js View File

@@ -17,10 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { translate } from '../../../helpers/l10n';

export default class ComparisonEmpty extends React.Component {
export default class ComparisonEmpty extends React.PureComponent {
render() {
return (
<div className="big-spacer-top">

+ 12
- 8
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.js View File

@@ -17,19 +17,23 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import Select from 'react-select';
import { ProfileType, ProfilesListType } from '../propTypes';
import { translate } from '../../../helpers/l10n';
import type { Profile } from '../propTypes';

export default class ComparisonForm extends React.Component {
static propTypes = {
profile: ProfileType.isRequired,
profiles: ProfilesListType.isRequired,
onCompare: React.PropTypes.func.isRequired
};
type Props = {
profile: Profile,
profiles: Array<Profile>,
onCompare: (string) => void,
withKey: string
};

handleChange(option) {
export default class ComparisonForm extends React.PureComponent {
props: Props;

handleChange(option: { value: string }) {
this.props.onCompare(option.value);
}


+ 15
- 14
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.js View File

@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { Link } from 'react-router';
import ComparisonEmpty from './ComparisonEmpty';
@@ -24,20 +25,20 @@ 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
};
type Params = { [string]: string };

renderRule(rule, severity) {
type Props = {
left: { name: string },
right: { name: string },
inLeft: Array<*>,
inRight: Array<*>,
modified: Array<*>
};

export default class ComparisonResults extends React.PureComponent {
props: Props;

renderRule(rule: { key: string, name: string }, severity: string) {
return (
<div>
<SeverityIcon severity={severity} />
@@ -49,7 +50,7 @@ export default class ComparisonResults extends React.Component {
);
}

renderParameters(params) {
renderParameters(params: Params) {
if (!params) {
return null;
}

+ 41
- 10
server/sonar-web/src/main/js/apps/quality-profiles/components/App.js View File

@@ -17,17 +17,36 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { getQualityProfiles, getExporters } from '../../../api/quality-profiles';
import { sortProfiles } from '../utils';
import type { Exporter } from '../propTypes';
import '../styles.css';

export default class App extends React.Component {
state = { loading: true };
type Props = {
children: React.Element<*>,
currentUser: { permissions: { global: Array<string> } },
languages: Array<*>,
organization: { canAdmin?: boolean, key: string } | null
};

type State = {
loading: boolean,
exporters?: Array<Exporter>,
profiles?: Array<*>
};

export default class App extends React.PureComponent {
mounted: boolean;
props: Props;
state: State = { loading: true };

componentWillMount() {
document.querySelector('html').classList.add('dashboard-page');
this.updateProfiles = this.updateProfiles.bind(this);
const html = document.querySelector('html');
if (html) {
html.classList.add('dashboard-page');
}
}

componentDidMount() {
@@ -37,12 +56,21 @@ export default class App extends React.Component {

componentWillUnmount() {
this.mounted = false;
document.querySelector('html').classList.remove('dashboard-page');
const html = document.querySelector('html');
if (html) {
html.classList.remove('dashboard-page');
}
}

fetchProfiles() {
const { organization } = this.props;
const data = organization ? { organization: organization.key } : {};
return getQualityProfiles(data);
}

loadData() {
this.setState({ loading: true });
Promise.all([getExporters(), getQualityProfiles()]).then(responses => {
Promise.all([getExporters(), this.fetchProfiles()]).then(responses => {
if (this.mounted) {
const [exporters, profiles] = responses;
this.setState({
@@ -54,13 +82,13 @@ export default class App extends React.Component {
});
}

updateProfiles() {
return getQualityProfiles().then(profiles => {
updateProfiles = () => {
return this.fetchProfiles().then(profiles => {
if (this.mounted) {
this.setState({ profiles: sortProfiles(profiles) });
}
});
}
};

renderChild() {
if (this.state.loading) {
@@ -69,13 +97,16 @@ export default class App extends React.Component {

const finalLanguages = Object.values(this.props.languages);

const canAdmin = this.props.currentUser.permissions.global.includes('profileadmin');
const canAdmin = this.props.organization
? this.props.organization.canAdmin
: this.props.currentUser.permissions.global.includes('profileadmin');

return React.cloneElement(this.props.children, {
profiles: this.state.profiles,
languages: finalLanguages,
exporters: this.state.exporters,
updateProfiles: this.updateProfiles,
organization: this.props.organization ? this.props.organization.key : null,
canAdmin
});
}

+ 9
- 4
server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js View File

@@ -19,9 +19,14 @@
*/
import { connect } from 'react-redux';
import App from './App';
import { getLanguages, getCurrentUser } from '../../../store/rootReducer';
import { getLanguages, getCurrentUser, getOrganizationByKey } from '../../../store/rootReducer';

export default connect(state => ({
const mapStateToProps = (state, ownProps) => ({
currentUser: getCurrentUser(state),
languages: getLanguages(state)
}))(App);
languages: getLanguages(state),
organization: ownProps.params.organizationKey
? getOrganizationByKey(state, ownProps.params.organizationKey)
: null
});

export default connect(mapStateToProps)(App);

+ 33
- 40
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js View File

@@ -17,78 +17,74 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { Link } from 'react-router';
import RenameProfileView from '../views/RenameProfileView';
import CopyProfileView from '../views/CopyProfileView';
import DeleteProfileView from '../views/DeleteProfileView';
import { translate } from '../../../helpers/l10n';
import { ProfileType } from '../propTypes';
import { getRulesUrl } from '../../../helpers/urls';
import { setDefaultProfile } from '../../../api/quality-profiles';
import { getProfilePath, getProfileComparePath, getProfilesPath } from '../utils';
import type { Profile } from '../propTypes';

export default class ProfileActions extends React.Component {
static propTypes = {
profile: ProfileType.isRequired,
canAdmin: React.PropTypes.bool.isRequired,
updateProfiles: React.PropTypes.func.isRequired
};
type Props = {
canAdmin: boolean,
organization: ?string,
profile: Profile,
updateProfiles: () => Promise<*>
};

export default class ProfileActions extends React.PureComponent {
props: Props;

static contextTypes = {
router: React.PropTypes.object
};

handleRenameClick(e) {
handleRenameClick = (e: SyntheticInputEvent) => {
e.preventDefault();
new RenameProfileView({
profile: this.props.profile
})
.on('done', () => {
this.props.updateProfiles();
})
new RenameProfileView({ profile: this.props.profile })
.on('done', () => this.props.updateProfiles())
.render();
}
};

handleCopyClick(e) {
handleCopyClick = (e: SyntheticInputEvent) => {
e.preventDefault();
new CopyProfileView({
profile: this.props.profile
})
new CopyProfileView({ profile: this.props.profile })
.on('done', profile => {
this.props.updateProfiles().then(() => {
this.context.router.push({
pathname: '/profiles/show',
query: { key: profile.key }
});
this.context.router.push(getProfilePath(profile.key, this.props.organization));
});
})
.render();
}
};

handleSetDefaultClick(e) {
handleSetDefaultClick = (e: SyntheticInputEvent) => {
e.preventDefault();
setDefaultProfile(this.props.profile.key).then(this.props.updateProfiles);
}
};

handleDeleteClick(e) {
handleDeleteClick = (e: SyntheticInputEvent) => {
e.preventDefault();
new DeleteProfileView({
profile: this.props.profile
})
new DeleteProfileView({ profile: this.props.profile })
.on('done', () => {
this.context.router.replace('/profiles');
this.context.router.replace(getProfilesPath(this.props.organization));
this.props.updateProfiles();
})
.render();
}
};

render() {
const { profile, canAdmin } = this.props;

// FIXME use org, name and lang
const backupUrl = window.baseUrl +
'/api/qualityprofiles/backup?profileKey=' +
encodeURIComponent(profile.key);

// FIXME getRulesUrl
const activateMoreUrl = getRulesUrl({
qprofile: profile.key,
activation: 'false'
@@ -109,37 +105,34 @@ export default class ProfileActions extends React.Component {
</li>
<li>
<Link
to={{ pathname: '/profiles/compare', query: { key: profile.key } }}
to={getProfileComparePath(profile.key, this.props.organization)}
id="quality-profile-compare">
{translate('compare')}
</Link>
</li>
{canAdmin &&
<li>
<a id="quality-profile-copy" href="#" onClick={this.handleCopyClick.bind(this)}>
<a id="quality-profile-copy" href="#" onClick={this.handleCopyClick}>
{translate('copy')}
</a>
</li>}
{canAdmin &&
<li>
<a id="quality-profile-rename" href="#" onClick={this.handleRenameClick.bind(this)}>
<a id="quality-profile-rename" href="#" onClick={this.handleRenameClick}>
{translate('rename')}
</a>
</li>}
{canAdmin &&
!profile.isDefault &&
<li>
<a
id="quality-profile-set-as-default"
href="#"
onClick={this.handleSetDefaultClick.bind(this)}>
<a id="quality-profile-set-as-default" href="#" onClick={this.handleSetDefaultClick}>
{translate('set_as_default')}
</a>
</li>}
{canAdmin &&
!profile.isDefault &&
<li>
<a id="quality-profile-delete" href="#" onClick={this.handleDeleteClick.bind(this)}>
<a id="quality-profile-delete" href="#" onClick={this.handleDeleteClick}>
{translate('delete')}
</a>
</li>}

+ 23
- 12
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js View File

@@ -17,31 +17,41 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
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';
import type { Profile } 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
};
type Props = {
canAdmin: boolean,
children: React.Element<*>,
location: { query: { key: string } },
organization: ?string,
profiles: Array<Profile>,
updateProfiles: () => Promise<*>
};

export default class ProfileContainer extends React.PureComponent {
props: Props;

render() {
const { profiles, location, ...other } = this.props;
const { organization, profiles, location, ...other } = this.props;
const { key } = location.query;
const profile = profiles.find(profile => profile.key === key);

if (!profile) {
return <ProfileNotFound />;
return <ProfileNotFound organization={organization} />;
}

const child = React.cloneElement(this.props.children, { profile, profiles, ...other });
const child = React.cloneElement(this.props.children, {
organization,
profile,
profiles,
...other
});

const title = translate('quality_profiles.page') + ' - ' + profile.name;

@@ -50,8 +60,9 @@ export default class ProfileContainer extends React.Component {
<Helmet title={title} titleTemplate="SonarQube - %s" />

<ProfileHeader
profile={profile}
canAdmin={this.props.canAdmin}
organization={organization}
profile={profile}
updateProfiles={this.props.updateProfiles}
/>
{child}

+ 9
- 6
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.js View File

@@ -17,18 +17,21 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
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
};
type Props = {
date?: string
};

shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
export default class ProfileDate extends React.PureComponent {
props: Props;

shouldComponentUpdate(nextProps: Props) {
return shallowCompare(this, nextProps);
}

render() {

+ 12
- 7
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.js View File

@@ -17,20 +17,25 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { Link } from 'react-router';
import { getProfilePath } from '../utils';

export default class ProfileLink extends React.Component {
static propTypes = {
profileKey: React.PropTypes.string.isRequired
};
type Props = {
children?: React.Element<*>,
organization: ?string,
profileKey: string
};

export default class ProfileLink extends React.PureComponent {
props: Props;

render() {
const { profileKey, children, ...other } = this.props;
const query = { key: profileKey };
const { profileKey, organization, children, ...other } = this.props;
return (
<Link
to={{ pathname: '/profiles/show', query }}
to={getProfilePath(profileKey, organization)}
activeClassName="link-no-underline"
{...other}>
{children}

+ 10
- 2
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.js View File

@@ -17,16 +17,24 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { IndexLink } from 'react-router';
import { translate } from '../../../helpers/l10n';
import { getProfilesPath } from '../utils';

type Props = {
organization: ?string
};

export default class ProfileNotFound extends React.PureComponent {
props: Props;

export default class ProfileNotFound extends React.Component {
render() {
return (
<div className="quality-profile-not-found">
<div className="note spacer-bottom">
<IndexLink to="/profiles/" className="text-muted">
<IndexLink to={getProfilesPath(this.props.organization)} className="text-muted">
{translate('quality_profiles.page')}
</IndexLink>
</div>

+ 13
- 7
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js View File

@@ -17,19 +17,25 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import ProfileRules from './ProfileRules';
import ProfileProjects from './ProfileProjects';
import ProfileInheritance from './ProfileInheritance';
import ProfileExporters from './ProfileExporters';
import { ProfileType } from '../propTypes';
import type { Profile, Exporter } from '../propTypes';

export default class ProfileDetails extends React.Component {
static propTypes = {
profile: ProfileType,
canAdmin: React.PropTypes.bool,
updateProfiles: React.PropTypes.func
};
type Props = {
canAdmin: boolean,
exporters: Array<Exporter>,
organization: ?string,
profile: Profile,
profiles: Array<Profile>,
updateProfiles: () => Promise<*>
};

export default class ProfileDetails extends React.PureComponent {
props: Props;

render() {
return (

+ 24
- 13
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.js View File

@@ -17,23 +17,34 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import { stringify } from 'querystring';
import React from 'react';
import { translate } from '../../../helpers/l10n';
import type { Profile, Exporter } from '../propTypes';

export default class ProfileExporters extends React.Component {
static propTypes = {
exporters: React.PropTypes.array.isRequired
};
type Props = {
exporters: Array<Exporter>,
organization: ?string,
profile: Profile
};

getExportUrl(exporter) {
return window.baseUrl +
'/api/qualityprofiles/export' +
'?exporterKey=' +
encodeURIComponent(exporter.key) +
'&language=' +
encodeURIComponent(this.props.profile.language) +
'&name=' +
encodeURIComponent(this.props.profile.name);
export default class ProfileExporters extends React.PureComponent {
props: Props;

getExportUrl(exporter: Exporter) {
const { organization, profile } = this.props;

const path = '/api/qualityprofiles/export';
const parameters: { [string]: string } = {
exporterKey: exporter.key,
language: profile.language,
name: profile.name
};
if (organization) {
Object.assign(parameters, { organization });
}
return window.baseUrl + path + '?' + stringify(parameters);
}

render() {

+ 28
- 17
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.js View File

@@ -17,21 +17,30 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { Link, IndexLink } from 'react-router';
import ProfileLink from '../components/ProfileLink';
import ProfileActions from '../components/ProfileActions';
import ProfileDate from '../components/ProfileDate';
import { ProfileType } from '../propTypes';
import { translate } from '../../../helpers/l10n';
import { isStagnant } from '../utils';
import {
isStagnant,
getProfilesPath,
getProfilesForLanguagePath,
getProfileChangelogPath
} from '../utils';
import type { Profile } from '../propTypes';

export default class ProfileHeader extends React.Component {
static propTypes = {
profile: ProfileType.isRequired,
canAdmin: React.PropTypes.bool.isRequired,
updateProfiles: React.PropTypes.func.isRequired
};
type Props = {
canAdmin: boolean,
organization: ?string,
profile: Profile,
updateProfiles: () => Promise<*>
};

export default class ProfileHeader extends React.PureComponent {
props: Props;

renderUpdateDate() {
const { profile } = this.props;
@@ -81,25 +90,28 @@ export default class ProfileHeader extends React.Component {
}

render() {
const { profile } = this.props;
const { organization, profile } = this.props;

return (
<header className="page-header quality-profile-header">
<div className="note spacer-bottom">
<IndexLink to="/profiles/" className="text-muted">
<IndexLink to={getProfilesPath(organization)} className="text-muted">
{translate('quality_profiles.page')}
</IndexLink>
{' / '}
<Link
to={{ pathname: '/profiles/', query: { language: profile.language } }}
to={getProfilesForLanguagePath(profile.language, organization)}
className="text-muted">
{profile.languageName}
</Link>
</div>

<h1 className="page-title">
<ProfileLink profileKey={profile.key} className="link-base-color">
{profile.name}
<ProfileLink
organization={organization}
profileKey={profile.key}
className="link-base-color">
<span>{profile.name}</span>
</ProfileLink>
</h1>

@@ -108,9 +120,7 @@ export default class ProfileHeader extends React.Component {
{this.renderUpdateDate()}
{this.renderUsageDate()}
<li>
<Link
to={{ pathname: '/profiles/changelog', query: { key: profile.key } }}
className="button">
<Link to={getProfileChangelogPath(profile.key, organization)} className="button">
{translate('changelog')}
</Link>
</li>
@@ -122,8 +132,9 @@ export default class ProfileHeader extends React.Component {
<i className="icon-dropdown" />
</button>
<ProfileActions
profile={profile}
canAdmin={this.props.canAdmin}
organization={organization}
profile={profile}
updateProfiles={this.props.updateProfiles}
/>
</div>

+ 66
- 40
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.js View File

@@ -17,34 +17,58 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import classNames from 'classnames';
import ProfileInheritanceBox from './ProfileInheritanceBox';
import ChangeParentView from '../views/ChangeParentView';
import { ProfileType } from '../propTypes';
import { translate } from '../../../helpers/l10n';
import { getProfileInheritance } from '../../../api/quality-profiles';
import type { Profile } from '../propTypes';

export default class ProfileInheritance extends React.Component {
static propTypes = {
profile: ProfileType.isRequired,
canAdmin: React.PropTypes.bool.isRequired
};
type Props = {
canAdmin: boolean,
organization: ?string,
profile: Profile,
profiles: Array<Profile>,
updateProfiles: () => Promise<*>
};

type State = {
ancestors?: Array<{
activeRuleCount: number,
key: string,
name: string,
overridingRuleCount?: number
}>,
children?: Array<{
activeRuleCount: number,
key: string,
name: string,
overridingRuleCount?: number
}>,
loading: boolean,
profile?: {
activeRuleCount: number,
key: string,
name: string,
overridingRuleCount?: number
}
};

state = {
export default class ProfileInheritance extends React.PureComponent {
mounted: boolean;
props: Props;
state: State = {
loading: true
};

componentWillMount() {
this.handleChangeParent = this.handleChangeParent.bind(this);
}

componentDidMount() {
this.mounted = true;
this.loadData();
}

componentDidUpdate(prevProps) {
componentDidUpdate(prevProps: Props) {
if (prevProps.profile !== this.props.profile) {
this.loadData();
}
@@ -68,20 +92,17 @@ export default class ProfileInheritance extends React.Component {
});
}

handleChangeParent(e) {
handleChangeParent = (e: SyntheticInputEvent) => {
e.preventDefault();
new ChangeParentView({
profile: this.props.profile,
profiles: this.props.profiles
})
.on('done', () => {
this.props.updateProfiles();
})
new ChangeParentView({ profile: this.props.profile, profiles: this.props.profiles })
.on('done', () => this.props.updateProfiles())
.render();
}
};

render() {
const highlightCurrent = !this.state.loading &&
this.state.ancestors != null &&
this.state.children != null &&
(this.state.ancestors.length > 0 || this.state.children.length > 0);
const currentClassName = classNames('js-inheritance-current', {
selected: highlightCurrent
@@ -102,30 +123,35 @@ export default class ProfileInheritance extends React.Component {
{!this.state.loading &&
<table className="data zebra">
<tbody>
{this.state.ancestors.map((ancestor, index) => (
<ProfileInheritanceBox
key={ancestor.key}
profile={ancestor}
depth={index}
className="js-inheritance-ancestor"
/>
))}
{this.state.ancestors != null &&
this.state.ancestors.map((ancestor, index) => (
<ProfileInheritanceBox
className="js-inheritance-ancestor"
depth={index}
key={ancestor.key}
organization={this.props.organization}
profile={ancestor}
/>
))}

<ProfileInheritanceBox
profile={this.state.profile}
depth={this.state.ancestors.length}
displayLink={false}
className={currentClassName}
depth={this.state.ancestors ? this.state.ancestors.length : 0}
displayLink={false}
organization={this.props.organization}
profile={this.state.profile}
/>

{this.state.children.map(child => (
<ProfileInheritanceBox
key={child.key}
profile={child}
depth={this.state.ancestors.length + 1}
className="js-inheritance-child"
/>
))}
{this.state.children != null &&
this.state.children.map(child => (
<ProfileInheritanceBox
className="js-inheritance-child"
depth={this.state.ancestors ? this.state.ancestors.length + 1 : 0}
key={child.key}
organization={this.props.organization}
profile={child}
/>
))}
</tbody>
</table>}
</div>

+ 17
- 13
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.js View File

@@ -17,22 +17,26 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
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,
className: React.PropTypes.string
};
type Props = {
className?: string,
depth: number,
displayLink?: boolean,
organization: ?string,
profile: {
activeRuleCount: number,
key: string,
name: string,
overridingRuleCount?: number
}
};

export default class ProfileInheritanceBox extends React.PureComponent {
props: Props;

static defaultProps = {
displayLink: true
@@ -47,7 +51,7 @@ export default class ProfileInheritanceBox extends React.Component {
<td>
<div style={{ paddingLeft: offset }}>
{this.props.displayLink
? <ProfileLink profileKey={profile.key}>
? <ProfileLink organization={this.props.organization} profileKey={profile.key}>
{profile.name}
</ProfileLink>
: profile.name}

+ 26
- 17
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.js View File

@@ -17,34 +17,42 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { Link } from 'react-router';
import ChangeProjectsView from '../views/ChangeProjectsView';
import QualifierIcon from '../../../components/shared/qualifier-icon';
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 = {
import type { Profile } from '../propTypes';

type Props = {
canAdmin: boolean,
organization: ?string,
profile: Profile,
updateProfiles: () => Promise<*>
};

type State = {
loading: boolean,
more?: boolean,
projects: ?Array<*>
};

export default class ProfileProjects extends React.PureComponent {
mounted: boolean;
props: Props;
state: State = {
loading: true,
projects: null
};

componentWillMount() {
this.loadProjects = this.loadProjects.bind(this);
}

componentDidMount() {
this.mounted = true;
this.loadProjects();
}

componentDidUpdate(prevProps) {
componentDidUpdate(prevProps: Props) {
if (prevProps.profile !== this.props.profile) {
this.loadProjects();
}
@@ -71,12 +79,13 @@ export default class ProfileProjects extends React.Component {
});
}

handleChange(e) {
handleChange(e: SyntheticInputEvent) {
e.preventDefault();
e.target.blur();
new ChangeProjectsView({
profile: this.props.profile,
loadProjects: this.props.updateProfiles
loadProjects: this.props.updateProfiles,
organization: this.props.organization,
profile: this.props.profile
}).render();
}


+ 46
- 22
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js View File

@@ -17,26 +17,36 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { Link } from 'react-router';
import { keyBy } from 'lodash';
import ProfileRulesRow from './ProfileRulesRow';
import { ProfileType } from '../propTypes';
import { searchRules, takeFacet } from '../../../api/rules';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import { getRulesUrl, getDeprecatedActiveRulesUrl } from '../../../helpers/urls';
import IssueTypeIcon from '../../../components/ui/IssueTypeIcon';
import type { Profile } from '../propTypes';

const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];

export default class ProfileRules extends React.Component {
static propTypes = {
profile: ProfileType.isRequired,
canAdmin: React.PropTypes.bool.isRequired
};

state = {
type Props = {
canAdmin: boolean,
profile: Profile
};

type State = {
total: ?number,
activatedTotal: ?number,
allByType?: { [string]: ?{ val: string, count: ?number } },
activatedByType?: { [string]: ?{ val: string, count: ?number } }
};

export default class ProfileRules extends React.PureComponent {
mounted: boolean;
props: Props;
state: State = {
total: null,
activatedTotal: null,
allByType: keyBy(TYPES.map(t => ({ val: t, count: null })), 'val'),
@@ -48,7 +58,7 @@ export default class ProfileRules extends React.Component {
this.loadRules();
}

componentDidUpdate(prevProps) {
componentDidUpdate(prevProps: Props) {
if (prevProps.profile !== this.props.profile) {
this.loadRules();
}
@@ -89,7 +99,7 @@ export default class ProfileRules extends React.Component {
});
}

getTooltip(count, total) {
getTooltip(count: ?number, total: ?number) {
if (count == null || total == null) {
return '';
}
@@ -101,10 +111,17 @@ export default class ProfileRules extends React.Component {
);
}

getTooltipForType(type) {
const { count } = this.state.activatedByType[type];
const total = this.state.allByType[type].count;
return this.getTooltip(count, total);
getTooltipForType(type: string) {
if (
this.state.activatedByType &&
this.state.activatedByType[type] &&
this.state.allByType &&
this.state.allByType[type]
) {
const { count } = this.state.activatedByType[type];
const total = this.state.allByType[type].count;
return this.getTooltip(count, total);
}
}

renderActiveTitle() {
@@ -140,7 +157,7 @@ export default class ProfileRules extends React.Component {
activation: 'false'
});

if (this.state.total == null) {
if (this.state.total == null || this.state.activatedTotal == null) {
return null;
}

@@ -157,7 +174,7 @@ export default class ProfileRules extends React.Component {
);
}

renderTitleForType(type) {
renderTitleForType(type: string) {
return (
<span>
<IssueTypeIcon query={type} className="little-spacer-right" />
@@ -166,14 +183,16 @@ export default class ProfileRules extends React.Component {
);
}

renderCountForType(type) {
renderCountForType(type: string) {
const rulesUrl = getRulesUrl({
qprofile: this.props.profile.key,
activation: 'true',
types: type
});

const { count } = this.state.activatedByType[type];
const count = this.state.activatedByType && this.state.activatedByType[type]
? this.state.activatedByType[type].count
: null;

if (count == null) {
return null;
@@ -186,17 +205,22 @@ export default class ProfileRules extends React.Component {
);
}

renderTotalForType(type) {
renderTotalForType(type: string) {
const rulesUrl = getRulesUrl({
qprofile: this.props.profile.key,
activation: 'false',
types: type
});

const { count } = this.state.activatedByType[type];
const { count: total } = this.state.allByType[type];
const count = this.state.activatedByType && this.state.activatedByType[type]
? this.state.activatedByType[type].count
: null;

if (count == null) {
const total = this.state.allByType && this.state.allByType[type]
? this.state.allByType[type].count
: null;

if (count == null || total == null) {
return null;
}


+ 9
- 6
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.js View File

@@ -17,14 +17,17 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';

export default class ProfileRulesRow extends React.Component {
static propTypes = {
renderTitle: React.PropTypes.func.isRequired,
renderCount: React.PropTypes.func.isRequired,
renderTotal: React.PropTypes.func.isRequired
};
type Props = {
renderCount: () => ?React.Element<*>,
renderTitle: () => React.Element<*>,
renderTotal: () => ?React.Element<*>
};

export default class ProfileRulesRow extends React.PureComponent {
props: Props;

render() {
const { renderTitle, renderCount, renderTotal } = this.props;

+ 13
- 7
server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.js View File

@@ -17,22 +17,28 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import EvolutionDeprecated from './EvolutionDeprecated';
import EvolutionStagnant from './EvolutionStagnant';
import EvolutionRules from './EvolutionRules';
import { ProfilesListType } from '../propTypes';
import type { Profile } from '../propTypes';

export default class Evolution extends React.Component {
static propTypes = {
profiles: ProfilesListType.isRequired
};
type Props = {
organization: ?string,
profiles: Array<Profile>
};

export default class Evolution extends React.PureComponent {
props: Props;

render() {
const { organization, profiles } = this.props;

return (
<div className="quality-profiles-evolution">
<EvolutionDeprecated profiles={this.props.profiles} />
<EvolutionStagnant profiles={this.props.profiles} />
<EvolutionDeprecated organization={organization} profiles={profiles} />
<EvolutionStagnant organization={organization} profiles={profiles} />
<EvolutionRules />
</div>
);

+ 15
- 6
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.js View File

@@ -17,20 +17,26 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { Link } from 'react-router';
import { sortBy } from 'lodash';
import ProfileLink from '../components/ProfileLink';
import { getDeprecatedActiveRulesUrl } from '../../../helpers/urls';
import { ProfilesListType } from '../propTypes';
import { translateWithParameters, translate } from '../../../helpers/l10n';
import type { Profile } from '../propTypes';

export default class EvolutionDeprecated extends React.Component {
static propTypes = {
profiles: ProfilesListType.isRequired
};
type Props = {
organization: ?string,
profiles: Array<Profile>
};

export default class EvolutionDeprecated extends React.PureComponent {
props: Props;

render() {
// FIXME getDeprecatedActiveRulesUrl

const profilesWithDeprecations = this.props.profiles.filter(
profile => profile.activeDeprecatedRuleCount > 0
);
@@ -56,7 +62,10 @@ export default class EvolutionDeprecated extends React.Component {
{sortedProfiles.map(profile => (
<li key={profile.key} className="spacer-top">
<div className="text-ellipsis">
<ProfileLink profileKey={profile.key} className="link-no-underline">
<ProfileLink
organization={this.props.organization}
profileKey={profile.key}
className="link-no-underline">
{profile.name}
</ProfileLink>
</div>

+ 8
- 1
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.js View File

@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { Link } from 'react-router';
import moment from 'moment';
@@ -38,7 +39,11 @@ function parseRules(r) {
});
}

export default class EvolutionRules extends React.Component {
type Props = {};

export default class EvolutionRules extends React.PureComponent {
mounted: boolean;
props: Props;
state = {};

componentDidMount() {
@@ -70,6 +75,8 @@ export default class EvolutionRules extends React.Component {
}

render() {
// FIXME getRulesUrl

if (!this.state.latestRulesTotal) {
return null;
}

+ 13
- 6
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.js View File

@@ -17,17 +17,21 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import moment from 'moment';
import ProfileLink from '../components/ProfileLink';
import { ProfilesListType } from '../propTypes';
import { translate } from '../../../helpers/l10n';
import { isStagnant } from '../utils';
import type { Profile } from '../propTypes';

export default class EvolutionStagnant extends React.Component {
static propTypes = {
profiles: ProfilesListType.isRequired
};
type Props = {
organization: ?string,
profiles: Array<Profile>
};

export default class EvolutionStagnant extends React.PureComponent {
props: Props;

render() {
// TODO filter built-in out
@@ -50,7 +54,10 @@ export default class EvolutionStagnant extends React.Component {
{outdated.map(profile => (
<li key={profile.key} className="spacer-top">
<div className="text-ellipsis">
<ProfileLink profileKey={profile.key} className="link-no-underline">
<ProfileLink
organization={this.props.organization}
profileKey={profile.key}
className="link-no-underline">
{profile.name}
</ProfileLink>
</div>

+ 14
- 1
server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js View File

@@ -17,14 +17,27 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
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';
import type { Profile } from '../propTypes';

type Props = {
canAdmin: boolean,
languages: Array<{ key: string, name: string }>,
location: { query: { [string]: string } },
organization?: string,
profiles: Array<Profile>,
updateProfiles: () => Promise<*>
};

export default class HomeContainer extends React.PureComponent {
props: Props;

export default class HomeContainer extends React.Component {
render() {
return (
<div>

+ 32
- 22
server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js View File

@@ -17,17 +17,25 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import CreateProfileView from '../views/CreateProfileView';
import RestoreProfileView from '../views/RestoreProfileView';
import RestoreBuiltInProfilesView from '../views/RestoreBuiltInProfilesView';
import { getProfilePath } from '../utils';
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
};
type Props = {
canAdmin: boolean,
languages: Array<{ key: string, name: string }>,
organization: ?string,
updateProfiles: () => Promise<*>
};

export default class PageHeader extends React.PureComponent {
mounted: boolean;
props: Props;

static contextTypes = {
router: React.PropTypes.object
@@ -54,37 +62,42 @@ export default class PageHeader extends React.Component {
}
}

handleCreateClick(e) {
handleCreateClick = (e: SyntheticInputEvent) => {
e.preventDefault();
e.target.blur();
this.retrieveImporters().then(importers => {
new CreateProfileView({
languages: this.props.languages,
organization: this.props.organization,
importers
})
.on('done', profile => {
this.props.updateProfiles().then(() => {
this.context.router.push({
pathname: '/profiles/show',
query: { key: profile.key }
});
this.context.router.push(getProfilePath(profile.key, this.props.organization));
});
})
.render();
});
}
};

handleRestoreClick(e) {
handleRestoreClick = (e: SyntheticInputEvent) => {
e.preventDefault();
new RestoreProfileView().on('done', this.props.updateProfiles).render();
}
new RestoreProfileView({
organization: this.props.organization
})
.on('done', this.props.updateProfiles)
.render();
};

handleRestoreBuiltIn(e) {
handleRestoreBuiltIn = (e: SyntheticInputEvent) => {
e.preventDefault();
new RestoreBuiltInProfilesView({ languages: this.props.languages })
new RestoreBuiltInProfilesView({
languages: this.props.languages,
organization: this.props.organization
})
.on('done', this.props.updateProfiles)
.render();
}
};

render() {
return (
@@ -95,7 +108,7 @@ export default class PageHeader extends React.Component {

{this.props.canAdmin &&
<div className="page-actions button-group dropdown">
<button id="quality-profiles-create" onClick={this.handleCreateClick.bind(this)}>
<button id="quality-profiles-create" onClick={this.handleCreateClick}>
{translate('create')}
</button>
<button className="dropdown-toggle js-more-admin-actions" data-toggle="dropdown">
@@ -103,10 +116,7 @@ export default class PageHeader extends React.Component {
</button>
<ul className="dropdown-menu dropdown-menu-right">
<li>
<a
href="#"
id="quality-profiles-restore"
onClick={this.handleRestoreClick.bind(this)}>
<a href="#" id="quality-profiles-restore" onClick={this.handleRestoreClick}>
{translate('quality_profiles.restore_profile')}
</a>
</li>
@@ -115,7 +125,7 @@ export default class PageHeader extends React.Component {
<a
href="#"
id="quality-profiles-restore-built-in"
onClick={this.handleRestoreBuiltIn.bind(this)}>
onClick={this.handleRestoreBuiltIn}>
{translate('quality_profiles.restore_built_in_profiles')}
</a>
</li>

+ 31
- 15
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js View File

@@ -17,36 +17,46 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { groupBy, pick, sortBy } from 'lodash';
import ProfilesListRow from './ProfilesListRow';
import ProfilesListHeader from './ProfilesListHeader';
import { ProfilesListType, LanguagesListType } from '../propTypes';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
import type { Profile } from '../propTypes';

export default class ProfilesList extends React.Component {
static propTypes = {
profiles: ProfilesListType,
languages: LanguagesListType,
location: React.PropTypes.object,
canAdmin: React.PropTypes.bool.isRequired,
updateProfiles: React.PropTypes.func.isRequired
};
type Props = {
canAdmin: boolean,
languages: Array<{ key: string, name: string }>,
location: { query: { [string]: string } },
organization: ?string,
profiles: Array<Profile>,
updateProfiles: () => Promise<*>
};

renderProfiles(profiles) {
export default class ProfilesList extends React.PureComponent {
props: Props;

renderProfiles(profiles: Array<Profile>) {
return profiles.map(profile => (
<ProfilesListRow
canAdmin={this.props.canAdmin}
key={profile.key}
organization={this.props.organization}
profile={profile}
canAdmin={this.props.canAdmin}
updateProfiles={this.props.updateProfiles}
/>
));
}

renderHeader(languageKey, profilesCount) {
renderHeader(languageKey: string, profilesCount: number) {
const language = this.props.languages.find(l => l.key === languageKey);

if (!language) {
return null;
}

return (
<thead>
<tr>
@@ -84,7 +94,11 @@ export default class ProfilesList extends React.Component {

return (
<div>
<ProfilesListHeader languages={languages} currentFilter={language} />
<ProfilesListHeader
currentFilter={language}
languages={languages}
organization={this.props.organization}
/>

{Object.keys(profilesToShow).length === 0 &&
<div className="alert alert-warning spacer-top">
@@ -95,11 +109,13 @@ export default class ProfilesList extends React.Component {
<div key={languageKey} className="quality-profile-box quality-profiles-table">
<table data-language={languageKey} className="data zebra zebra-hover">

{this.renderHeader(languageKey, profilesToShow[languageKey].length)}
{profilesToShow[languageKey] != null &&
this.renderHeader(languageKey, profilesToShow[languageKey].length)}

<TooltipsContainer>
<tbody>
{this.renderProfiles(profilesToShow[languageKey])}
{profilesToShow[languageKey] != null &&
this.renderProfiles(profilesToShow[languageKey])}
</tbody>
</TooltipsContainer>


+ 13
- 9
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.js View File

@@ -17,22 +17,26 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { IndexLink } from 'react-router';
import { LanguagesListType } from '../propTypes';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getProfilesPath, getProfilesForLanguagePath } from '../utils';

export default class ProfilesListHeader extends React.Component {
static propTypes = {
languages: LanguagesListType.isRequired,
currentFilter: React.PropTypes.string
};
type Props = {
currentFilter?: string,
languages: Array<{ key: string, name: string }>,
organization: ?string
};

export default class ProfilesListHeader extends React.PureComponent {
props: Props;

renderFilterToggle() {
const { languages, currentFilter } = this.props;
const currentLanguage = currentFilter && languages.find(l => l.key === currentFilter);

const label = currentFilter
const label = currentLanguage
? translateWithParameters('quality_profiles.x_Profiles', currentLanguage.name)
: translate('quality_profiles.all_profiles');

@@ -50,14 +54,14 @@ export default class ProfilesListHeader extends React.Component {
return (
<ul className="dropdown-menu">
<li>
<IndexLink to="/profiles/">
<IndexLink to={getProfilesPath(this.props.organization)}>
{translate('quality_profiles.all_profiles')}
</IndexLink>
</li>
{this.props.languages.map(language => (
<li key={language.key}>
<IndexLink
to={{ pathname: '/profiles/', query: { language: language.key } }}
to={getProfilesForLanguagePath(language.key, this.props.organization)}
className="js-language-filter-option"
data-language={language.key}>
{language.name}

+ 18
- 11
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js View File

@@ -17,26 +17,30 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { Link } from 'react-router';
import shallowCompare from 'react-addons-shallow-compare';
import ProfileLink from '../components/ProfileLink';
import ProfileDate from '../components/ProfileDate';
import ProfileActions from '../components/ProfileActions';
import { ProfileType } from '../propTypes';
import { translate } from '../../../helpers/l10n';
import { getRulesUrl } from '../../../helpers/urls';
import { isStagnant } from '../utils';
import type { Profile } from '../propTypes';

export default class ProfilesListRow extends React.Component {
static propTypes = {
profile: ProfileType.isRequired,
canAdmin: React.PropTypes.bool.isRequired,
updateProfiles: React.PropTypes.func.isRequired
};
type Props = {
canAdmin: boolean,
organization: ?string,
profile: Profile,
updateProfiles: () => Promise<*>
};

shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this, nextProps, nextState);
export default class ProfilesListRow extends React.PureComponent {
props: Props;

shouldComponentUpdate(nextProps: Props) {
return shallowCompare(this, nextProps);
}

renderName() {
@@ -44,7 +48,7 @@ export default class ProfilesListRow extends React.Component {
const offset = 25 * (profile.depth - 1);
return (
<div style={{ paddingLeft: offset }}>
<ProfileLink profileKey={profile.key}>
<ProfileLink organization={this.props.organization} profileKey={profile.key}>
{profile.name}
</ProfileLink>
</div>
@@ -70,6 +74,8 @@ export default class ProfilesListRow extends React.Component {
}

renderRules() {
// FIXME getRulesUrl

const { profile } = this.props;

const activeRulesUrl = getRulesUrl({
@@ -150,8 +156,9 @@ export default class ProfilesListRow extends React.Component {
<i className="icon-dropdown" />
</button>
<ProfileActions
profile={this.props.profile}
canAdmin={this.props.canAdmin}
organization={this.props.organization}
profile={this.props.profile}
updateProfiles={this.props.updateProfiles}
/>
</div>

+ 25
- 0
server/sonar-web/src/main/js/apps/quality-profiles/propTypes.js View File

@@ -17,10 +17,35 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import { PropTypes } from 'react';

const { shape, string, number, bool, arrayOf } = PropTypes;

export type Profile = {
key: string,
name: string,
isDefault: boolean,
isInherited: boolean,
language: string,
languageName: string,
activeRuleCount: number,
activeDeprecatedRuleCount: number,
projectCount?: number,
parentKey?: string,
parentName?: string,
userUpdatedAt?: string,
lastUsed?: string,
rulesUpdatedAt: string,
depth: number
};

export type Exporter = {
key: string,
name: string,
languages: Array<string>
};

export const ProfileType = shape({
key: string.isRequired,
name: string.isRequired,

+ 8
- 2
server/sonar-web/src/main/js/apps/quality-profiles/routes.js View File

@@ -19,9 +19,15 @@
*/
const routes = [
{
getComponent(_, callback) {
getComponent(state, callback) {
require.ensure([], require => {
callback(null, require('./components/AppContainer').default);
const AppContainer = require('./components/AppContainer').default;
if (state.params.organizationKey) {
callback(null, AppContainer);
} else {
const forSingleOrganization = require('../organizations/forSingleOrganization').default;
callback(null, forSingleOrganization(AppContainer));
}
});
},
getIndexRoute(_, callback) {

+ 13
- 11
server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-create-profile.hbs View File

@@ -1,14 +1,16 @@
<form id="create-profile-form" action="{{link '/api/qualityprofiles/create'}}" enctype="multipart/form-data"
method="POST">
<form id="create-profile-form" action="{{link '/api/qualityprofiles/create'}}" enctype="multipart/form-data" method="POST">
<div class="modal-head">
<h2>{{t 'quality_profiles.new_profile'}}</h2>
</div>
<div class="modal-body">
<div class="js-modal-messages"></div>
{{#if organization}}
<input type="hidden" name="organization" value="{{organization}}">
{{/if}}
<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>
@@ -18,15 +20,15 @@
</select>
</div>
{{#each importers}}
<div class="modal-field spacer-bottom js-importer" data-key="{{key}}">
<label for="create-profile-form-backup-{{key}}">{{name}}</label>
<input id="create-profile-form-backup-{{key}}" name="backup_{{key}}" type="file">
<p class="note">{{t 'quality_profiles.optional_configuration_file'}}</p>
</div>
<div class="modal-field spacer-bottom js-importer" data-key="{{key}}">
<label for="create-profile-form-backup-{{key}}">{{name}}</label>
<input id="create-profile-form-backup-{{key}}" name="backup_{{key}}" type="file">
<p class="note">{{t 'quality_profiles.optional_configuration_file'}}</p>
</div>
{{/each}}
</div>
<div class="modal-foot">
<button id="create-profile-submit">{{t 'create'}}</button>
<a href="#" class="js-modal-close" id="create-profile-cancel">{{t 'cancel'}}</a>
</div>
</form>
</form>

+ 3
- 0
server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-restore-profile.hbs View File

@@ -5,6 +5,9 @@

<div class="modal-body">
<div class="js-modal-messages"></div>
{{#if organization}}
<input type="hidden" name="organization" value="{{organization}}">
{{/if}}
{{#if profile}}
{{#if ruleFailures}}
<div class="alert alert-warning">

+ 53
- 8
server/sonar-web/src/main/js/apps/quality-profiles/utils.js View File

@@ -20,14 +20,9 @@
// @flow
import { sortBy } from 'lodash';
import moment from 'moment';
import type { Profile } from './propTypes';

type Profiles = Array<{
key: string,
name: string,
parentKey?: string
}>;

export function sortProfiles(profiles: Profiles) {
export function sortProfiles(profiles: Array<Profile>) {
const result = [];
const sorted = sortBy(profiles, 'name');

@@ -67,6 +62,56 @@ export function createFakeProfile(overrides: {}) {
};
}

export function isStagnant(profile: { userUpdatedAt: string }) {
export function isStagnant(profile: Profile) {
return moment().diff(moment(profile.userUpdatedAt), 'years') >= 1;
}

export const getProfilesPath = (organization: ?string) =>
organization ? `/organizations/${organization}/quality_profiles` : '/profiles';

export const getProfilesForLanguagePath = (language: string, organization: ?string) => ({
pathname: organization ? `/organizations/${organization}/quality_profiles` : '/profiles',
query: { language }
});

export const getProfilePath = (profile: string, organization: ?string) => ({
pathname: organization
? `/organizations/${organization}/quality_profiles/show`
: '/profiles/show',
query: { key: profile }
});

export const getProfileComparePath = (profile: string, organization: ?string, withKey?: string) => {
const query: Object = { key: profile };
if (withKey) {
Object.assign(query, { withKey });
}
return {
pathname: organization
? `/organizations/${organization}/quality_profiles/compare`
: '/profiles/compare',
query
};
};

export const getProfileChangelogPath = (
profile: string,
organization: ?string,
filter?: { since?: string, to?: string }
) => {
const query: Object = { key: profile };
if (filter) {
if (filter.since) {
Object.assign(query, { since: filter.since });
}
if (filter.to) {
Object.assign(query, { to: filter.to });
}
}
return {
pathname: organization
? `/organizations/${organization}/quality_profiles/changelog`
: '/profiles/changelog',
query
};
};

+ 1
- 0
server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeParentView.js View File

@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import ModalFormView from '../../../components/common/modal-form';
import Template from '../templates/quality-profiles-change-profile-parent.hbs';
import { changeProfileParent } from '../../../api/quality-profiles';

+ 3
- 0
server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeProjectsView.js View File

@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import escapeHtml from 'escape-html';
import ModalFormView from '../../../components/common/modal-form';
import Template from '../templates/quality-profiles-change-projects.hbs';
@@ -27,6 +28,8 @@ export default ModalFormView.extend({
template: Template,

onRender() {
// TODO remove uuid usage

ModalFormView.prototype.onRender.apply(this, arguments);

const { key } = this.options.profile;

+ 1
- 0
server/sonar-web/src/main/js/apps/quality-profiles/views/CopyProfileView.js View File

@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import ModalFormView from '../../../components/common/modal-form';
import Template from '../templates/quality-profiles-copy-profile.hbs';
import { copyProfile } from '../../../api/quality-profiles';

+ 3
- 1
server/sonar-web/src/main/js/apps/quality-profiles/views/CreateProfileView.js View File

@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import $ from 'jquery';
import ModalFormView from '../../../components/common/modal-form';
import Template from '../templates/quality-profiles-create-profile.hbs';
@@ -87,7 +88,8 @@ export default ModalFormView.extend({
return {
...ModalFormView.prototype.serializeData.apply(this, arguments),
languages: this.options.languages,
importers: this.options.importers
importers: this.options.importers,
organization: this.options.organization
};
}
});

+ 1
- 0
server/sonar-web/src/main/js/apps/quality-profiles/views/DeleteProfileView.js View File

@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import ModalFormView from '../../../components/common/modal-form';
import Template from '../templates/quality-profiles-delete-profile.hbs';
import { deleteProfile } from '../../../api/quality-profiles';

+ 1
- 0
server/sonar-web/src/main/js/apps/quality-profiles/views/RenameProfileView.js View File

@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import ModalFormView from '../../../components/common/modal-form';
import Template from '../templates/quality-profiles-rename-profile.hbs';
import { renameProfile } from '../../../api/quality-profiles';

+ 5
- 1
server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreBuiltInProfilesView.js View File

@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
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';
@@ -47,7 +48,10 @@ export default ModalFormView.extend({
sendRequest() {
const language = this.$('#restore-built-in-profiles-language').val();
this.selectedLanguage = this.options.languages.find(l => l.key === language).name;
restoreBuiltInProfiles(language)
const data = this.options.organization
? { language, organization: this.options.organization }
: { language };
restoreBuiltInProfiles(data)
.then(() => {
this.done = true;
this.render();

+ 2
- 0
server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreProfileView.js View File

@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import ModalFormView from '../../../components/common/modal-form';
import Template from '../templates/quality-profiles-restore-profile.hbs';
import { restoreQualityProfile } from '../../../api/quality-profiles';
@@ -47,6 +48,7 @@ export default ModalFormView.extend({
serializeData() {
return {
...ModalFormView.prototype.serializeData.apply(this, arguments),
organization: this.options.organization,
profile: this.profile,
ruleSuccesses: this.ruleSuccesses,
ruleFailures: this.ruleFailures

+ 4
- 5
server/sonar-web/src/main/js/helpers/urls.js View File

@@ -17,6 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { getProfilePath } from '../apps/quality-profiles/utils';

/**
* Generate URL for a component's home page
* @param {string} componentKey
@@ -92,11 +94,8 @@ export function getComponentPermissionsUrl(componentKey) {
* @param {string} key
* @returns {Object}
*/
export function getQualityProfileUrl(key) {
return {
pathname: '/profiles/show',
query: { key }
};
export function getQualityProfileUrl(key, organization) {
return getProfilePath(key, organization);
}

/**

Loading…
Cancel
Save