]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9110 Add organization page extensions
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Tue, 11 Apr 2017 12:46:00 +0000 (14:46 +0200)
committerGrégoire Aubert <gregaubert@users.noreply.github.com>
Wed, 12 Apr 2017 14:36:29 +0000 (16:36 +0200)
server/sonar-web/src/main/js/api/organizations.js
server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js
server/sonar-web/src/main/js/apps/organizations/routes.js
server/sonar-web/src/main/js/store/organizations/duck.js

index 41f3c014b9c55fa0f71d1efe0142324cf462955b..df7a1569d47f0565675859401b071c4d87a283be 100644 (file)
@@ -36,7 +36,11 @@ type GetOrganizationType = null | Organization;
 
 type GetOrganizationNavigation = {
   canAdmin: boolean,
-  isDefault: boolean
+  canDelete: boolean,
+  canProvisionProjects: boolean,
+  isDefault: boolean,
+  pages: Array<{ key: string, name: string }>,
+  adminPages: Array<{ key: string, name: string }>
 };
 
 export const getOrganization = (key: string): Promise<GetOrganizationType> => {
diff --git a/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.js b/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.js
new file mode 100644 (file)
index 0000000..6f37a03
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+// @flow
+import React from 'react';
+import { connect } from 'react-redux';
+import Extension from './Extension';
+import ExtensionNotFound from './ExtensionNotFound';
+import { getOrganizationByKey } from '../../../store/rootReducer';
+import type { Organization } from '../../../store/organizations/duck';
+
+type Props = {
+  organization: Organization,
+  params: {
+    extensionKey: string,
+    organizationKey: string,
+    pluginKey: string
+  }
+};
+
+class OrganizationPageExtension extends React.PureComponent {
+  props: Props;
+
+  render() {
+    const { extensionKey, pluginKey } = this.props.params;
+    const { organization } = this.props;
+
+    let pages = organization.pages || [];
+    if (organization.canAdmin && organization.adminPages) {
+      pages = pages.concat(organization.adminPages);
+    }
+
+    const extension = pages.find(p => p.key === `${pluginKey}/${extensionKey}`);
+    return extension
+      ? <Extension extension={extension} options={{ organization }} />
+      : <ExtensionNotFound />;
+  }
+}
+
+const mapStateToProps = (state, ownProps: Props) => ({
+  organization: getOrganizationByKey(state, ownProps.params.organizationKey)
+});
+
+export default connect(mapStateToProps)(OrganizationPageExtension);
index 0675c1872d8354cb5098cf1d92581faf44316e21..3fc479d5b54067204419c6fbda36b62eec752538 100644 (file)
@@ -22,6 +22,7 @@ import React from 'react';
 import { Link } from 'react-router';
 import { translate } from '../../../helpers/l10n';
 import OrganizationIcon from '../../../components/ui/OrganizationIcon';
+import type { Organization } from '../../../store/organizations/duck';
 
 const ADMIN_PATHS = [
   'edit',
@@ -35,22 +36,11 @@ const ADMIN_PATHS = [
 export default class OrganizationNavigation extends React.Component {
   props: {
     location: { pathname: string },
-    organization: {
-      avatar?: string,
-      description?: string,
-      key: string,
-      name: string,
-      canAdmin?: boolean,
-      canDelete?: boolean,
-      url?: string
-    }
+    organization: Organization
   };
 
-  renderAdministration() {
-    const { organization, location } = this.props;
-
-    const adminActive = ADMIN_PATHS.some(path =>
-      location.pathname.endsWith(`organizations/${organization.key}/${path}`));
+  renderAdministration(adminActive: boolean) {
+    const { organization } = this.props;
 
     return (
       <li className={adminActive ? 'active' : ''}>
@@ -58,6 +48,7 @@ export default class OrganizationNavigation extends React.Component {
           {translate('layout.settings')}&nbsp;<i className="icon-dropdown" />
         </a>
         <ul className="dropdown-menu">
+          {this.renderAdminExtensions()}
           <li>
             <Link to={`/organizations/${organization.key}/groups`} activeClassName="active">
               {translate('user_groups.page')}
@@ -98,12 +89,56 @@ export default class OrganizationNavigation extends React.Component {
     );
   }
 
+  renderAdminExtensions() {
+    const extensions = this.props.organization.adminPages || [];
+    return extensions.map(this.renderExtension);
+  }
+
+  renderExtension = (extension: { key: string, name: string }) => {
+    const { organization } = this.props;
+    const pathname = `/organizations/${organization.key}/extension/${extension.key}`;
+    return (
+      <li key={extension.key}>
+        <Link to={pathname} activeClassName="active">
+          {extension.name}
+        </Link>
+      </li>
+    );
+  };
+
+  renderExtensions(moreActive: boolean) {
+    const extensions = this.props.organization.pages || [];
+    if (extensions.length > 0) {
+      return (
+        <li className={moreActive ? 'active' : ''}>
+          <a
+            className="dropdown-toggle"
+            id="organization-navigation-more"
+            data-toggle="dropdown"
+            href="#">
+            {translate('more')}&nbsp;<i className="icon-dropdown" />
+          </a>
+          <ul className="dropdown-menu">
+            {extensions.map(this.renderExtension)}
+          </ul>
+        </li>
+      );
+    } else {
+      return null;
+    }
+  }
+
   render() {
     const { organization, location } = this.props;
 
     const isHomeActive = location.pathname === `organizations/${organization.key}/projects` ||
       location.pathname === `organizations/${organization.key}/projects/favorite`;
 
+    const adminActive = ADMIN_PATHS.some(path =>
+      location.pathname.endsWith(`organizations/${organization.key}/${path}`));
+
+    const moreActive = !adminActive && location.pathname.includes('/extension/');
+
     return (
       <nav className="navbar navbar-context page-container" id="context-navigation">
         <div className="navbar-context-inner">
@@ -166,7 +201,8 @@ export default class OrganizationNavigation extends React.Component {
                   {translate('coding_rules.page')}
                 </Link>
               </li>
-              {organization.canAdmin && this.renderAdministration()}
+              {this.renderExtensions(moreActive)}
+              {organization.canAdmin && this.renderAdministration(adminActive)}
             </ul>
           </div>
         </div>
index 7f91a2f344d71451f0c2ff349e965826ed392ed9..c01ca215da06276519a9faee737e2963d288c7c3 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import OrganizationPage from './components/OrganizationPage';
+import OrganizationPageExtension from '../../app/components/extensions/OrganizationPageExtension';
 import OrganizationProjects from './components/OrganizationProjects';
 import OrganizationFavoriteProjects from './components/OrganizationFavoriteProjects';
 import OrganizationRules from './components/OrganizationRules';
@@ -64,6 +65,10 @@ const routes = [
         path: 'quality_profiles',
         childRoutes: qualityProfilesRoutes
       },
+      {
+        path: 'extension/:pluginKey/:extensionKey',
+        component: OrganizationPageExtension
+      },
       {
         component: OrganizationAdmin,
         childRoutes: [
index 13671285423ea716ed64a2ecdbb2f361d47fb6ca..b897af7845c2c5691d10a1a22f062b05224c29d9 100644 (file)
@@ -22,6 +22,7 @@ import { combineReducers } from 'redux';
 import { omit, uniq, without } from 'lodash';
 
 export type Organization = {
+  adminPages?: Array<{ key: string, name: string }>,
   avatar?: string,
   canAdmin?: boolean,
   canDelete?: boolean,
@@ -29,6 +30,7 @@ export type Organization = {
   description?: string,
   key: string,
   name: string,
+  pages?: Array<{ key: string, name: string }>,
   url?: string
 };