]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9008 support quality profiles for organizations
authorStas Vilchik <vilchiks@gmail.com>
Mon, 27 Mar 2017 12:00:14 +0000 (14:00 +0200)
committerStas Vilchik <stas-vilchik@users.noreply.github.com>
Mon, 3 Apr 2017 08:38:52 +0000 (10:38 +0200)
76 files changed:
it/it-tests/src/test/java/it/Category5Suite.java
it/it-tests/src/test/java/it/organization/OrganizationQualityProfilesPageTest.java [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_compare.html [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_copy.html [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_create.html [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_delete.html [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_changelog.html [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_list.html [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_exporters.html [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_inheritance.html [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_projects.html [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_rules.html [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_filter_by_language.html [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_open_from_list.html [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_rename.html [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_restore.html [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_restore_built_in.html [new file with mode: 0644]
it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_set_default.html [new file with mode: 0644]
server/sonar-web/src/main/js/api/quality-profiles.js
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.js.snap
server/sonar-web/src/main/js/apps/organizations/routes.js
server/sonar-web/src/main/js/apps/overview/meta/Meta.js
server/sonar-web/src/main/js/apps/overview/meta/MetaQualityProfiles.js
server/sonar-web/src/main/js/apps/project-admin/quality-profiles/QualityProfiles.js
server/sonar-web/src/main/js/apps/project-admin/store/actions.js
server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.js
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogContainer.js
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogEmpty.js
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangelogSearch.js
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ChangesList.js
server/sonar-web/src/main/js/apps/quality-profiles/changelog/ParameterChange.js
server/sonar-web/src/main/js/apps/quality-profiles/changelog/SeverityChange.js
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonContainer.js
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonEmpty.js
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonForm.js
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.js
server/sonar-web/src/main/js/apps/quality-profiles/components/App.js
server/sonar-web/src/main/js/apps/quality-profiles/components/AppContainer.js
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileActions.js
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.js
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileDate.js
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileLink.js
server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileNotFound.js
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileDetails.js
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileExporters.js
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileHeader.js
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritance.js
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileInheritanceBox.js
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileProjects.js
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.js
server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRulesRow.js
server/sonar-web/src/main/js/apps/quality-profiles/home/Evolution.js
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionDeprecated.js
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionRules.js
server/sonar-web/src/main/js/apps/quality-profiles/home/EvolutionStagnant.js
server/sonar-web/src/main/js/apps/quality-profiles/home/HomeContainer.js
server/sonar-web/src/main/js/apps/quality-profiles/home/PageHeader.js
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.js
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListHeader.js
server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.js
server/sonar-web/src/main/js/apps/quality-profiles/propTypes.js
server/sonar-web/src/main/js/apps/quality-profiles/routes.js
server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-create-profile.hbs
server/sonar-web/src/main/js/apps/quality-profiles/templates/quality-profiles-restore-profile.hbs
server/sonar-web/src/main/js/apps/quality-profiles/utils.js
server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeParentView.js
server/sonar-web/src/main/js/apps/quality-profiles/views/ChangeProjectsView.js
server/sonar-web/src/main/js/apps/quality-profiles/views/CopyProfileView.js
server/sonar-web/src/main/js/apps/quality-profiles/views/CreateProfileView.js
server/sonar-web/src/main/js/apps/quality-profiles/views/DeleteProfileView.js
server/sonar-web/src/main/js/apps/quality-profiles/views/RenameProfileView.js
server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreBuiltInProfilesView.js
server/sonar-web/src/main/js/apps/quality-profiles/views/RestoreProfileView.js
server/sonar-web/src/main/js/helpers/urls.js

index 9050e43e2a290f8b72087c190c8ca69d320c9bf3..5bd6f18cc49c6db82e987a15e1ed6d47873d0e1f 100644 (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 {
 
diff --git a/it/it-tests/src/test/java/it/organization/OrganizationQualityProfilesPageTest.java b/it/it-tests/src/test/java/it/organization/OrganizationQualityProfilesPageTest.java
new file mode 100644 (file)
index 0000000..3cabbe5
--- /dev/null
@@ -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());
+  }
+}
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_compare.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_compare.html
new file mode 100644 (file)
index 0000000..b8ea98d
--- /dev/null
@@ -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>
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_copy.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_copy.html
new file mode 100644 (file)
index 0000000..e233ad8
--- /dev/null
@@ -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>
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_create.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_create.html
new file mode 100644 (file)
index 0000000..253880e
--- /dev/null
@@ -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>
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_delete.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_delete.html
new file mode 100644 (file)
index 0000000..bdaab3e
--- /dev/null
@@ -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>
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_changelog.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_changelog.html
new file mode 100644 (file)
index 0000000..417700e
--- /dev/null
@@ -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>
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_list.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_list.html
new file mode 100644 (file)
index 0000000..b334194
--- /dev/null
@@ -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>
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_exporters.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_exporters.html
new file mode 100644 (file)
index 0000000..b26d162
--- /dev/null
@@ -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>
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_inheritance.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_inheritance.html
new file mode 100644 (file)
index 0000000..c403fbf
--- /dev/null
@@ -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>
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_projects.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_projects.html
new file mode 100644 (file)
index 0000000..062014e
--- /dev/null
@@ -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>
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_rules.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_display_profile_rules.html
new file mode 100644 (file)
index 0000000..6b497fc
--- /dev/null
@@ -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>
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_filter_by_language.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_filter_by_language.html
new file mode 100644 (file)
index 0000000..a913a70
--- /dev/null
@@ -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>
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_open_from_list.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_open_from_list.html
new file mode 100644 (file)
index 0000000..2e753ba
--- /dev/null
@@ -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>
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_rename.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_rename.html
new file mode 100644 (file)
index 0000000..c0c437e
--- /dev/null
@@ -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>
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_restore.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_restore.html
new file mode 100644 (file)
index 0000000..1debd02
--- /dev/null
@@ -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>
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_restore_built_in.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_restore_built_in.html
new file mode 100644 (file)
index 0000000..82005c7
--- /dev/null
@@ -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>
diff --git a/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_set_default.html b/it/it-tests/src/test/resources/organization/OrganizationQualityProfilesPageTest/should_set_default.html
new file mode 100644 (file)
index 0000000..bb99646
--- /dev/null
@@ -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>
index 6f482bd3a9ae4b8b02a3d19d83e4f7efb9bc6f1c..2f178014559d68347f0cffaef115ca86a1a3e4d1 100644 (file)
  * 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);
index 7eebc3d03935f78acd1c6b15c12f0d7671bbc2ad..90249a0e7e276a83f6e52ff141fbd665d7151133 100644 (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()}
index a38d9829abda3b43a76cadf68959bd1186d2395f..fdf72a5009e1464ba36c6c201818af77d09365fa 100644 (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>
index 8fbbcd9b6a30bc93ff2255c56c7dd53ed8dcf6fe..46358abc847f055a64e3c925c3d219916f34e94d 100644 (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
index eae57052d3ea28bb65ed611cd0dacb01e8d39266..1b7f81eda3daa2e6bc32eeab1aee3d9a83e4966e 100644 (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: [
index 9f67196b36885ecc676b6779072c9ba95d28917f..a18e1385f1b54e99d38b1e3851999644b3594281 100644 (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} />
 
index ff4c39b3db04c801b481e83e4a5c8e5b6f3168d3..fb5c176df851faaaecfac9525fb4da11ecb11b71 100644 (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>
index 88d4c79c5c7f6cdaae3b8d46d6a446460c12dee2..ee3d6a0e7747e1f846cb0a577be6d5a31aa2b691 100644 (file)
  * 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);
index 2f96a8bf7233a325af9c39dae851c6f4f91b8415..d3f66e9af6cb3818f199162d201ba0950fe9d6eb 100644 (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));
index cd70278214da8d941c85b2fbda94b50b8fc32b8c..2e84e64d2a30d9c96447c75cf9b2efefee6fa4dc 100644 (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;
index 8de4383ca42dcb2914cf24d30976bba38c313254..c7ea2d89572620897918e8db9072402e95e42d2f 100644 (file)
  * 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 (
index 1baf1d73e4f929b1e0196f7b68d74c75c9687f19..96db0d8c8eb898357bc28bc3bd2f82b31149125a 100644 (file)
  * 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">
index 1106ba94b34231295b97c3e6f26a8770487bb37d..89d2099a22958247c89eb415572fa1dde9d55517 100644 (file)
  * 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();
index 9ec1807227aa3663a4cd6e51fe36e13773d30a25..6377b4f3799ab91959479642054aa6a6f22ad388 100644 (file)
  * 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;
index 7b4e01689790de14c28bffb8bbf28a2ee05d339f..d4e01d55b723009b56ed389a970e979f3a36ce0f 100644 (file)
  * 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;
index c6e20846d83a13c2c1aac4906e79ec106394a032..5497d9ad0fbb9dd53997602b7838b4be69c52de3 100644 (file)
  * 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 (
index a41bc66c8adcbd6ab17146da73180a395ade48df..cdc70a050ed34434ee171d4cad199483d2a25c4c 100644 (file)
  * 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;
index 7d7298dc76105a2e3cfca26956d8d379e771a0e0..b3a19a37f5c91e51a034f8d101e65bcfad3cad3e 100644 (file)
  * 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">
index f686b415b5aea39a88933e0af140e530d5aeb25b..a99f64b2186ba2a03d45758b045d6252617542ed 100644 (file)
  * 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);
   }
 
index bd8793490d3f44961d225fbd5754514a31d5b2ee..c6acf7385469e287a96789c59afdc55508565adc 100644 (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;
     }
index f8fd41c66c5875df46829c03c02d607642fd647d..982553d7993b742cab93fdd0f7f79fdfcf396a07 100644 (file)
  * 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
     });
   }
index a19ddff98b6d5cd987ced29baf94ef5d75d168ad..b5ff40a9ace86c3a4629f4cd310a205422638380 100644 (file)
  */
 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);
index 1616c0936c8baa7af384278476b665aaff05191e..1101c1560ce4c62a579f79803e35b8bc9b356a47 100644 (file)
  * 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>}
index e11cbe0968cbdc7df6829fa694067d0317db6bc4..d55f57dd8fe19d4561208b48064737acdd262106 100644 (file)
  * 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}
index 369a6a067fa056dc8f4e3abb6f0e9d053eebbea2..158e6b9955bc820bba1d561bdb9845c274b1982d 100644 (file)
  * 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() {
index e6d332735b725d6c0a8ac544a684028473892b7f..a60c37ab9b1a9a06c7bc067ef31dc22e1a8a443d 100644 (file)
  * 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}
index 2dff3d655e05496863862b70434494841d90fae7..7401007841dadfc059627d4ef485b198e86b1d20 100644 (file)
  * 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>
index b6deaa8ddc719f1b6e3d7eabf984aa71f65b0972..7b7a296536c44aa1af2a098474f342b6ff3ec02f 100644 (file)
  * 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 (
index 1a7e9df653f1a815ed6a0d40d3e80324df107903..be6702dd7edb13fc0c98800d3a4680731c0e344e 100644 (file)
  * 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() {
index a581dad19833ab7524a0dcf6a755a5520df2a290..3a06c63e47297851363c3548fdadf7214991af7b 100644 (file)
  * 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>
index d17198f89d51572ce8cf7464dc95cdec915a31f5..f6760f674f52e703c42455cec9b651316b769b8b 100644 (file)
  * 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>
index 7e8b464ad16457e21f7c466cd545f25abce56659..5af9d91b2075c118fe13b4b21e1f4466584025e9 100644 (file)
  * 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}
index 650eae3746eaafa60013b1877181c78c72711b49..6d877d785fa0a190339bb23275b1d6f8f5f24687 100644 (file)
  * 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();
   }
 
index 2dee1b5680e7dbf3a8b153fb36adcbc3ce38f431..7a68617ab25cc767b5fbb8f1f86772cf682819c8 100644 (file)
  * 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;
     }
 
index ae74c6158027606c721c7211c5f9cc2039b22589..9a7d140fa144bd9b975ac485d8b531a55be1cb9f 100644 (file)
  * 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;
index b87f3ceb5dffc804d1b630ebfbafd26fc5647414..23995d62e195d55f15a5ace981fac8536e58c1c5 100644 (file)
  * 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>
     );
index 8c2a79cc6978871eb67d6b7bf4859601d5980ed9..ce8f4ddafbc846ceccc936bf462e24e5590099a4 100644 (file)
  * 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>
index 224be2a3b28427df6def63e3c98f0b8c01de61d9..553e6bdf62c9d50de0393c0f5d51525ec8555103 100644 (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;
     }
index 833ec05fd338785290cc2dda425bde2ac8194414..29679aca1aee945bb8b0a6264f3d48912c3f4b14 100644 (file)
  * 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>
index e597942200604f78802511891dec6eecd584c350..e675d6245f9a760018f0e6ad1c97efded0e2fea7 100644 (file)
  * 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>
index 9d4ca3dbefba2cff5f8f2fe57064eb28d4e416e4..48ff0383758ae4340c60b1472bee34b5eba56366 100644 (file)
  * 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>
index 34d0317107f4df1086abd73789d05a0b16986eb1..c2b0681f68694e7b6471c507b21263cf3f75615c 100644 (file)
  * 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>
 
index 3fd7c18d5ff2c8fd3ae71cb34cb3e68bd0c1a7e1..1d0c931aa04557311735bc65f9c3a0ae8a391bfa 100644 (file)
  * 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}
index eca33432e526fcbe2ac21f289670f207018c4f63..3a53ee66e19f818c2d2d60f778c64e6e66e7a08a 100644 (file)
  * 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>
index 5ba29f3a37ea1d483537d752a1dc5e114d814282..f8438011916b9bb15e240df2698c982317e32c25 100644 (file)
  * 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,
index ac8f4c22894894c87958fbf178aad767bcaafb24..57c5ca1baadcbe4033868edb89b70abb7dbdad4f 100644 (file)
  */
 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) {
index 845e4843ef6b571f27083e71f1d5e9f92ba27b03..acdc7f93805b863520dbdfacdf4f95833b886bf9 100644 (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>
       </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>
\ No newline at end of file
index 9a8b0182717dceda9e7f3d3491961bf191b98f1f..7c22c83aab5b9a9a9e0f920fdbc60b1bb3a61f26 100644 (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">
index d5ea476bab8413eb6825ef1aed9a3a35550384f9..64bb0ea07d6def6b74e260e6c3e702bc5e16b0cc 100644 (file)
 // @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
+  };
+};
index 01c83a69ee17f00140c45c75d850579b94f9dd38..912aecb10cb35d66f631ba5a336a1eb981be9fa5 100644 (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';
index df5a7facd0c7d016fdc5ceaef557726e9aacece3..c08cc80a8cf566d845418c2f89f0283e6ba9e2d0 100644 (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;
index c16397a652f20cd8138326a16dcddad67c9f7eab..711946b8bfa99c52742c0ba6556e6c68758b3ca5 100644 (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';
index 8e12b9668a1643c82801d0a98636b6251d6a7059..fb74811ec692b0b5293d9d127ddf7091462e9967 100644 (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
     };
   }
 });
index 0c5d8a61732cb06b19440ace759ede8fcbda1afb..ec4c5f878a471cb280a9eb87fceb5d3a07a0661c 100644 (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';
index daf8db1ff048f50e9cfc38fff653e25065b06d36..308efa7c143f5f687825b752a96bb9e4c04f36dc 100644 (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';
index 39d0c720887bc149d6377be97381286aa9ddc217..2edf05044b4dadb216c44dd01193399ade5e2e44 100644 (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();
index ebd1fabe87069740d57724a2e2d890da3c23d164..d2393e5053d865dbeb0adcd6134a70dba17a5893 100644 (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
index 571d7a6069ea80922314d7a3b7e0c3acd1cf5240..de25aa553ee134e91cdbe8a2eb57f020c0c0954c 100644 (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);
 }
 
 /**