aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-04-11 14:46:00 +0200
committerGrégoire Aubert <gregaubert@users.noreply.github.com>2017-04-12 16:36:29 +0200
commitb3c2e0d524cd316326d642247972e12d00de0d12 (patch)
treed770c08f3af6a39c4b528060925f1847f9684aa2 /server/sonar-web
parentd2aef03a3830823ae277ad935e01194bd99cb0df (diff)
downloadsonarqube-b3c2e0d524cd316326d642247972e12d00de0d12.tar.gz
sonarqube-b3c2e0d524cd316326d642247972e12d00de0d12.zip
SONAR-9110 Add organization page extensions
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/api/organizations.js6
-rw-r--r--server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.js60
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js66
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/routes.js5
-rw-r--r--server/sonar-web/src/main/js/store/organizations/duck.js2
5 files changed, 123 insertions, 16 deletions
diff --git a/server/sonar-web/src/main/js/api/organizations.js b/server/sonar-web/src/main/js/api/organizations.js
index 41f3c014b9c..df7a1569d47 100644
--- a/server/sonar-web/src/main/js/api/organizations.js
+++ b/server/sonar-web/src/main/js/api/organizations.js
@@ -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
index 00000000000..6f37a031437
--- /dev/null
+++ b/server/sonar-web/src/main/js/app/components/extensions/OrganizationPageExtension.js
@@ -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);
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js
index 0675c1872d8..3fc479d5b54 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.js
@@ -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>
diff --git a/server/sonar-web/src/main/js/apps/organizations/routes.js b/server/sonar-web/src/main/js/apps/organizations/routes.js
index 7f91a2f344d..c01ca215da0 100644
--- a/server/sonar-web/src/main/js/apps/organizations/routes.js
+++ b/server/sonar-web/src/main/js/apps/organizations/routes.js
@@ -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';
@@ -65,6 +66,10 @@ const routes = [
childRoutes: qualityProfilesRoutes
},
{
+ path: 'extension/:pluginKey/:extensionKey',
+ component: OrganizationPageExtension
+ },
+ {
component: OrganizationAdmin,
childRoutes: [
{ path: 'delete', component: OrganizationDelete },
diff --git a/server/sonar-web/src/main/js/store/organizations/duck.js b/server/sonar-web/src/main/js/store/organizations/duck.js
index 13671285423..b897af7845c 100644
--- a/server/sonar-web/src/main/js/store/organizations/duck.js
+++ b/server/sonar-web/src/main/js/store/organizations/duck.js
@@ -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
};