aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2017-12-11 17:59:17 +0100
committerStas Vilchik <stas.vilchik@sonarsource.com>2018-01-02 10:38:10 +0100
commit0d3c531f4a6ef72a1f0982808543273760ea05ba (patch)
treed7a1ce57ace32f1613b639b4392c2849886eae8e
parentd04588cdf547d0e8f9290ad1cf1fa3fb0ecf6371 (diff)
downloadsonarqube-0d3c531f4a6ef72a1f0982808543273760ea05ba.tar.gz
sonarqube-0d3c531f4a6ef72a1f0982808543273760ea05ba.zip
SONAR-10186 Switch between organizations from the header
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.css57
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx223
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx112
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationExtensions.tsx70
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx81
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeaderContainer.tsx33
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenu.tsx78
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx49
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.tsx45
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationAdministration-test.tsx37
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationHeader-test.tsx56
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMenu-test.tsx54
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMeta-test.tsx37
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap548
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap71
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationHeader-test.tsx.snap120
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenu-test.tsx.snap201
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMeta-test.tsx.snap18
-rw-r--r--server/sonar-web/src/main/js/components/icons-components/DropdownIcon.tsx41
19 files changed, 1172 insertions, 759 deletions
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.css b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.css
new file mode 100644
index 00000000000..f61bbd33bd2
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.css
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+.organization-switch {
+ display: inline-block;
+}
+
+.organization-switch .dropdown-toggle {
+ display: block;
+ height: calc(4 * var(--gridSize));
+ line-height: calc(4 * var(--gridSize) - 2px);
+ padding: 0 var(--gridSize);
+ border: 1px solid transparent;
+ border-radius: 2px;
+ box-sizing: border-box;
+ color: var(--baseFontColor) !important;
+ transition: all 0.3s ease;
+}
+
+.organization-switch .dropdown-toggle:hover,
+.organization-switch.open .dropdown-toggle {
+ border-color: var(--barBorderColor);
+ background-color: #fff;
+ box-shadow: var(--defaultShadow);
+}
+
+.organization-switch.open .dropdown-toggle {
+ border-bottom-color: transparent;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+.organization-switch .dropdown-menu {
+ min-width: 100%;
+ margin-left: -1px;
+}
+
+.organization-switch .dropdown-menu > li > a {
+ padding-left: var(--gridSize);
+ padding-right: var(--gridSize);
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx
index 4cffcc47d80..b57d9840517 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx
@@ -18,222 +18,25 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as classNames from 'classnames';
-import { Link } from 'react-router';
+import OrganizationNavigationHeaderContainer from './OrganizationNavigationHeaderContainer';
+import OrganizationNavigationMeta from './OrganizationNavigationMeta';
+import OrganizationNavigationMenu from './OrganizationNavigationMenu';
import * as theme from '../../../app/theme';
-import { translate } from '../../../helpers/l10n';
import ContextNavBar from '../../../components/nav/ContextNavBar';
-import NavBarTabs from '../../../components/nav/NavBarTabs';
-import OrganizationAvatar from '../../../components/common/OrganizationAvatar';
-import { getQualityGatesUrl } from '../../../helpers/urls';
-import { Extension, Organization } from '../../../app/types';
-
-const ADMIN_PATHS = [
- 'edit',
- 'groups',
- 'delete',
- 'permissions',
- 'permission_templates',
- 'projects_management'
-];
+import { Organization } from '../../../app/types';
+import './OrganizationNavigation.css';
interface Props {
location: { pathname: string };
organization: Organization;
}
-export default class OrganizationNavigation extends React.PureComponent<Props> {
- renderAdministration(adminActive: boolean) {
- const { organization } = this.props;
-
- return (
- <li className="dropdown">
- <a
- className={classNames('dropdown-toggle', { active: adminActive })}
- data-toggle="dropdown"
- id="organization-navigation-admin"
- href="#">
- {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')}
- </Link>
- </li>
- <li>
- <Link to={`/organizations/${organization.key}/permissions`} activeClassName="active">
- {translate('permissions.page')}
- </Link>
- </li>
- <li>
- <Link
- to={`/organizations/${organization.key}/permission_templates`}
- activeClassName="active">
- {translate('permission_templates')}
- </Link>
- </li>
- <li>
- <Link
- to={`/organizations/${organization.key}/projects_management`}
- activeClassName="active">
- {translate('projects_management')}
- </Link>
- </li>
- <li>
- <Link to={`/organizations/${organization.key}/edit`} activeClassName="active">
- {translate('edit')}
- </Link>
- </li>
- {organization.canDelete && (
- <li>
- <Link to={`/organizations/${organization.key}/delete`} activeClassName="active">
- {translate('delete')}
- </Link>
- </li>
- )}
- </ul>
- </li>
- );
- }
-
- renderAdminExtensions() {
- const extensions = this.props.organization.adminPages || [];
- return extensions.map(this.renderExtension);
- }
-
- renderExtension = (extension: Extension) => {
- 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 adminPathsWithExtensions = (organization.adminPages || [])
- .map(e => `extension/${e.key}`)
- .concat(ADMIN_PATHS);
-
- const adminActive = adminPathsWithExtensions.some(path =>
- location.pathname.endsWith(`organizations/${organization.key}/${path}`)
- );
- const moreActive = !adminActive && location.pathname.includes('/extension/');
-
- return (
- <ContextNavBar id="context-navigation" height={theme.contextNavHeightRaw}>
- <div className="navbar-context-header">
- <h1 className="display-inline-block">
- <OrganizationAvatar organization={organization} />
- <Link
- to={`/organizations/${organization.key}`}
- className="link-base-color link-no-underline spacer-left">
- {organization.name}
- </Link>
- </h1>
- {organization.description != null && (
- <div className="navbar-context-description">
- <p className="text-limited text-top" title={organization.description}>
- {organization.description}
- </p>
- </div>
- )}
- </div>
-
- <div className="navbar-context-meta">
- <div className="text-muted">
- <strong>{translate('organization.key')}:</strong> {organization.key}
- </div>
- {organization.url != null && (
- <div>
- <p className="text-limited text-top">
- <a
- className="link-underline"
- href={organization.url}
- title={organization.url}
- rel="nofollow">
- {organization.url}
- </a>
- </p>
- </div>
- )}
- </div>
-
- <NavBarTabs className="navbar-context-tabs">
- <li>
- <Link
- to={`/organizations/${organization.key}/projects`}
- className={isHomeActive ? 'active' : ''}>
- {translate('projects.page')}
- </Link>
- </li>
- <li>
- <Link
- to={{
- pathname: `/organizations/${organization.key}/issues`,
- query: { resolved: 'false' }
- }}
- activeClassName="active">
- {translate('issues.page')}
- </Link>
- </li>
- <li>
- <Link
- to={`/organizations/${organization.key}/quality_profiles`}
- activeClassName="active">
- {translate('quality_profiles.page')}
- </Link>
- </li>
- <li>
- <Link to={`/organizations/${organization.key}/rules`} activeClassName="active">
- {translate('coding_rules.page')}
- </Link>
- </li>
- <li>
- <Link to={getQualityGatesUrl(organization.key)} activeClassName="active">
- {translate('quality_gates.page')}
- </Link>
- </li>
- <li>
- <Link to={`/organizations/${organization.key}/members`} activeClassName="active">
- {translate('organization.members.page')}
- </Link>
- </li>
- {this.renderExtensions(moreActive)}
- {organization.canAdmin && this.renderAdministration(adminActive)}
- </NavBarTabs>
- </ContextNavBar>
- );
- }
+export default function OrganizationNavigation({ organization, location }: Props) {
+ return (
+ <ContextNavBar id="context-navigation" height={theme.contextNavHeightRaw}>
+ <OrganizationNavigationHeaderContainer organization={organization} />
+ <OrganizationNavigationMeta organization={organization} />
+ <OrganizationNavigationMenu location={location} organization={organization} />
+ </ContextNavBar>
+ );
}
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx
new file mode 100644
index 00000000000..98d50b19d61
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx
@@ -0,0 +1,112 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { Link } from 'react-router';
+import * as classNames from 'classnames';
+import { Organization } from '../../../app/types';
+import { translate } from '../../../helpers/l10n';
+import Dropdown from '../../../components/controls/Dropdown';
+import DropdownIcon from '../../../components/icons-components/DropdownIcon';
+
+interface Props {
+ location: { pathname: string };
+ organization: Organization;
+}
+
+const ADMIN_PATHS = [
+ 'edit',
+ 'groups',
+ 'delete',
+ 'permissions',
+ 'permission_templates',
+ 'projects_management'
+];
+
+export default function OrganizationNavigationAdministration({ location, organization }: Props) {
+ const extensions = organization.adminPages || [];
+ const adminPathsWithExtensions = extensions.map(e => `extension/${e.key}`).concat(ADMIN_PATHS);
+ const adminActive = adminPathsWithExtensions.some(path =>
+ location.pathname.endsWith(`organizations/${organization.key}/${path}`)
+ );
+
+ return (
+ <Dropdown>
+ {({ onToggleClick, open }) => (
+ <li className={classNames('dropdown', { open })}>
+ <a
+ className={classNames('dropdown-toggle', { active: adminActive })}
+ id="organization-navigation-admin"
+ href="#"
+ onClick={onToggleClick}>
+ {translate('layout.settings')}
+ <DropdownIcon />
+ </a>
+ <ul className="dropdown-menu">
+ {extensions.map(extension => (
+ <li key={extension.key}>
+ <Link
+ to={`/organizations/${organization.key}/extension/${extension.key}`}
+ activeClassName="active">
+ {extension.name}
+ </Link>
+ </li>
+ ))}
+ <li>
+ <Link to={`/organizations/${organization.key}/groups`} activeClassName="active">
+ {translate('user_groups.page')}
+ </Link>
+ </li>
+ <li>
+ <Link to={`/organizations/${organization.key}/permissions`} activeClassName="active">
+ {translate('permissions.page')}
+ </Link>
+ </li>
+ <li>
+ <Link
+ to={`/organizations/${organization.key}/permission_templates`}
+ activeClassName="active">
+ {translate('permission_templates')}
+ </Link>
+ </li>
+ <li>
+ <Link
+ to={`/organizations/${organization.key}/projects_management`}
+ activeClassName="active">
+ {translate('projects_management')}
+ </Link>
+ </li>
+ <li>
+ <Link to={`/organizations/${organization.key}/edit`} activeClassName="active">
+ {translate('edit')}
+ </Link>
+ </li>
+ {organization.canDelete && (
+ <li>
+ <Link to={`/organizations/${organization.key}/delete`} activeClassName="active">
+ {translate('delete')}
+ </Link>
+ </li>
+ )}
+ </ul>
+ </li>
+ )}
+ </Dropdown>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationExtensions.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationExtensions.tsx
new file mode 100644
index 00000000000..097fcba9bc0
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationExtensions.tsx
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { Link } from 'react-router';
+import * as classNames from 'classnames';
+import { Organization } from '../../../app/types';
+import { translate } from '../../../helpers/l10n';
+import Dropdown from '../../../components/controls/Dropdown';
+
+interface Props {
+ location: { pathname: string };
+ organization: Organization;
+}
+
+export default function OrganizationNavigationExtensions({ location, organization }: Props) {
+ const extensions = organization.pages || [];
+ if (extensions.length === 0) {
+ return null;
+ }
+ const active = extensions.some(
+ extension =>
+ location.pathname === `/organizations/${organization.key}/extension/${extension.key}`
+ );
+
+ return (
+ <Dropdown>
+ {({ onToggleClick, open }) => (
+ <li className={classNames('dropdown', { open })}>
+ <a
+ className={classNames('dropdown-toggle', { active })}
+ id="organization-navigation-more"
+ href="#"
+ onClick={onToggleClick}>
+ {translate('more')}
+ <i className="icon-dropdown little-spacer-left" />
+ </a>
+
+ <ul className="dropdown-menu">
+ {extensions.map(extension => (
+ <li key={extension.key}>
+ <Link
+ to={`/organizations/${organization.key}/extension/${extension.key}`}
+ activeClassName="active">
+ {extension.name}
+ </Link>
+ </li>
+ ))}
+ </ul>
+ </li>
+ )}
+ </Dropdown>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx
new file mode 100644
index 00000000000..afa4332544c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx
@@ -0,0 +1,81 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import * as classNames from 'classnames';
+import { sortBy } from 'lodash';
+import { Organization } from '../../../app/types';
+import OrganizationAvatar from '../../../components/common/OrganizationAvatar';
+import Dropdown from '../../../components/controls/Dropdown';
+import DropdownIcon from '../../../components/icons-components/DropdownIcon';
+import OrganizationLink from '../../../components/ui/OrganizationLink';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ organization: Organization;
+ organizations: Organization[];
+}
+
+export default function OrganizationNavigationHeader({ organization, organizations }: Props) {
+ const other = organizations.filter(o => o.key !== organization.key);
+
+ return (
+ <div className="navbar-context-header">
+ <h1 className="display-inline-block">
+ <OrganizationAvatar organization={organization} />
+ {other.length ? (
+ <Dropdown>
+ {({ onToggleClick, open }) => (
+ <div className={classNames('organization-switch', 'dropdown', { open })}>
+ <a className="dropdown-toggle" href="#" onClick={onToggleClick}>
+ {organization.name}
+ <DropdownIcon className="little-spacer-left" />
+ </a>
+ <ul className="dropdown-menu">
+ {sortBy(other, org => org.name.toLowerCase()).map(organization => (
+ <li key={organization.key}>
+ <OrganizationLink className="dropdown-item-flex" organization={organization}>
+ <div>
+ <OrganizationAvatar organization={organization} small={true} />
+ <span className="spacer-left">{organization.name}</span>
+ </div>
+ {organization.isAdmin && (
+ <span className="outline-badge spacer-left">{translate('admin')}</span>
+ )}
+ </OrganizationLink>
+ </li>
+ ))}
+ </ul>
+ </div>
+ )}
+ </Dropdown>
+ ) : (
+ <span className="spacer-left">{organization.name}</span>
+ )}
+ </h1>
+ {organization.description != null && (
+ <div className="navbar-context-description">
+ <p className="text-limited text-top" title={organization.description}>
+ {organization.description}
+ </p>
+ </div>
+ )}
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeaderContainer.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeaderContainer.tsx
new file mode 100644
index 00000000000..c94996e6a76
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeaderContainer.tsx
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+import { connect } from 'react-redux';
+import OrganizationNavigationHeader from './OrganizationNavigationHeader';
+import { Organization } from '../../../app/types';
+import { getMyOrganizations } from '../../../store/rootReducer';
+
+interface StateProps {
+ organizations: Organization[];
+}
+
+const mapStateToProps = (state: any): StateProps => ({
+ organizations: getMyOrganizations(state)
+});
+
+export default connect(mapStateToProps)(OrganizationNavigationHeader);
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenu.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenu.tsx
new file mode 100644
index 00000000000..048c9f3b750
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenu.tsx
@@ -0,0 +1,78 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { Link } from 'react-router';
+import { Organization } from '../../../app/types';
+import NavBarTabs from '../../../components/nav/NavBarTabs';
+import { translate } from '../../../helpers/l10n';
+import { getQualityGatesUrl } from '../../../helpers/urls';
+import OrganizationNavigationExtensions from './OrganizationNavigationExtensions';
+import OrganizationNavigationAdministration from './OrganizationNavigationAdministration';
+
+interface Props {
+ location: { pathname: string };
+ organization: Organization;
+}
+
+export default function OrganizationNavigationMenu({ location, organization }: Props) {
+ return (
+ <NavBarTabs className="navbar-context-tabs">
+ <li>
+ <Link to={`/organizations/${organization.key}/projects`} activeClassName="active">
+ {translate('projects.page')}
+ </Link>
+ </li>
+ <li>
+ <Link
+ to={{
+ pathname: `/organizations/${organization.key}/issues`,
+ query: { resolved: 'false' }
+ }}
+ activeClassName="active">
+ {translate('issues.page')}
+ </Link>
+ </li>
+ <li>
+ <Link to={`/organizations/${organization.key}/quality_profiles`} activeClassName="active">
+ {translate('quality_profiles.page')}
+ </Link>
+ </li>
+ <li>
+ <Link to={`/organizations/${organization.key}/rules`} activeClassName="active">
+ {translate('coding_rules.page')}
+ </Link>
+ </li>
+ <li>
+ <Link to={getQualityGatesUrl(organization.key)} activeClassName="active">
+ {translate('quality_gates.page')}
+ </Link>
+ </li>
+ <li>
+ <Link to={`/organizations/${organization.key}/members`} activeClassName="active">
+ {translate('organization.members.page')}
+ </Link>
+ </li>
+ <OrganizationNavigationExtensions location={location} organization={organization} />
+ {organization.canAdmin && (
+ <OrganizationNavigationAdministration location={location} organization={organization} />
+ )}
+ </NavBarTabs>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx
new file mode 100644
index 00000000000..b720223abe1
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx
@@ -0,0 +1,49 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { Organization } from '../../../app/types';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+ organization: Organization;
+}
+
+export default function OrganizationNavigationMeta({ organization }: Props) {
+ return (
+ <div className="navbar-context-meta">
+ <div className="text-muted">
+ <strong>{translate('organization.key')}:</strong> {organization.key}
+ </div>
+ {organization.url != null && (
+ <div>
+ <p className="text-limited text-top">
+ <a
+ className="link-underline"
+ href={organization.url}
+ title={organization.url}
+ rel="nofollow">
+ {organization.url}
+ </a>
+ </p>
+ </div>
+ )}
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.tsx
index e4884f83545..41fea385334 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.tsx
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.tsx
@@ -22,40 +22,17 @@ import { shallow } from 'enzyme';
import OrganizationNavigation from '../OrganizationNavigation';
import { Visibility } from '../../../../app/types';
-jest.mock('../../../issues/utils', () => ({
- isMySet: () => false
-}));
-
-const organization = {
- key: 'foo',
- name: 'Foo',
- canAdmin: false,
- canDelete: false,
- projectVisibility: Visibility.Public
-};
-
-it('regular user', () => {
- expect(getWrapper()).toMatchSnapshot();
-});
-
-it('admin', () => {
+it('render', () => {
expect(
- getWrapper({ organization: { ...organization, canAdmin: true, canDelete: true } })
+ shallow(
+ <OrganizationNavigation
+ location={{ pathname: '/organizations/foo' }}
+ organization={{
+ key: 'foo',
+ name: 'Foo',
+ projectVisibility: Visibility.Public
+ }}
+ />
+ )
).toMatchSnapshot();
});
-
-it('undeletable org', () => {
- expect(
- getWrapper({ organization: { ...organization, canAdmin: true, canDelete: false } })
- ).toMatchSnapshot();
-});
-
-function getWrapper(props = {}) {
- return shallow(
- <OrganizationNavigation
- location={{ pathname: '/organizations/foo' }}
- organization={organization}
- {...props}
- />
- );
-}
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationAdministration-test.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationAdministration-test.tsx
new file mode 100644
index 00000000000..4bebf6b977e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationAdministration-test.tsx
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import OrganizationNavigationAdministration from '../OrganizationNavigationAdministration';
+import { Visibility } from '../../../../app/types';
+
+it('renders', () => {
+ const wrapper = shallow(
+ <OrganizationNavigationAdministration
+ location={{ pathname: '' }}
+ organization={{
+ key: 'foo',
+ name: 'Foo',
+ projectVisibility: Visibility.Public
+ }}
+ />
+ );
+ expect(wrapper.find('Dropdown').dive()).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationHeader-test.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationHeader-test.tsx
new file mode 100644
index 00000000000..bb9bd18b521
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationHeader-test.tsx
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import OrganizationNavigationHeader from '../OrganizationNavigationHeader';
+import { Visibility } from '../../../../app/types';
+
+it('renders', () => {
+ expect(
+ shallow(
+ <OrganizationNavigationHeader
+ organization={{
+ key: 'foo',
+ name: 'Foo',
+ projectVisibility: Visibility.Public
+ }}
+ organizations={[]}
+ />
+ )
+ ).toMatchSnapshot();
+});
+
+it('renders dropdown', () => {
+ const organizations = [
+ { isAdmin: true, key: 'org1', name: 'org1', projectVisibility: Visibility.Public },
+ { isAdmin: false, key: 'org2', name: 'org2', projectVisibility: Visibility.Public }
+ ];
+ const wrapper = shallow(
+ <OrganizationNavigationHeader
+ organization={{
+ key: 'foo',
+ name: 'Foo',
+ projectVisibility: Visibility.Public
+ }}
+ organizations={organizations}
+ />
+ );
+ expect(wrapper.find('Dropdown').dive()).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMenu-test.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMenu-test.tsx
new file mode 100644
index 00000000000..a5b266bcc2a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMenu-test.tsx
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import OrganizationNavigationMenu from '../OrganizationNavigationMenu';
+import { Visibility } from '../../../../app/types';
+
+it('renders', () => {
+ expect(
+ shallow(
+ <OrganizationNavigationMenu
+ location={{ pathname: '' }}
+ organization={{
+ key: 'foo',
+ name: 'Foo',
+ projectVisibility: Visibility.Public
+ }}
+ />
+ )
+ ).toMatchSnapshot();
+});
+
+it('renders for admin', () => {
+ expect(
+ shallow(
+ <OrganizationNavigationMenu
+ location={{ pathname: '' }}
+ organization={{
+ canAdmin: true,
+ key: 'foo',
+ name: 'Foo',
+ projectVisibility: Visibility.Public
+ }}
+ />
+ )
+ ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMeta-test.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMeta-test.tsx
new file mode 100644
index 00000000000..5b69f2c4853
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMeta-test.tsx
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import OrganizationNavigationMeta from '../OrganizationNavigationMeta';
+import { Visibility } from '../../../../app/types';
+
+it('renders', () => {
+ expect(
+ shallow(
+ <OrganizationNavigationMeta
+ organization={{
+ key: 'foo',
+ name: 'Foo',
+ projectVisibility: Visibility.Public
+ }}
+ />
+ )
+ ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap
index b9efc8fe2e3..eb04cc431fe 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap
@@ -1,523 +1,41 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`admin 1`] = `
+exports[`render 1`] = `
<ContextNavBar
height={72}
id="context-navigation"
>
- <div
- className="navbar-context-header"
- >
- <h1
- className="display-inline-block"
- >
- <OrganizationAvatar
- organization={
- Object {
- "canAdmin": true,
- "canDelete": true,
- "key": "foo",
- "name": "Foo",
- "projectVisibility": "public",
- }
- }
- />
- <Link
- className="link-base-color link-no-underline spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo"
- >
- Foo
- </Link>
- </h1>
- </div>
- <div
- className="navbar-context-meta"
- >
- <div
- className="text-muted"
- >
- <strong>
- organization.key
- :
- </strong>
-
- foo
- </div>
- </div>
- <NavBarTabs
- className="navbar-context-tabs"
- >
- <li>
- <Link
- className=""
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/projects"
- >
- projects.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/organizations/foo/issues",
- "query": Object {
- "resolved": "false",
- },
- }
- }
- >
- issues.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/quality_profiles"
- >
- quality_profiles.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/rules"
- >
- coding_rules.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/organizations/foo/quality_gates",
- }
- }
- >
- quality_gates.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/members"
- >
- organization.members.page
- </Link>
- </li>
- <li
- className="dropdown"
- >
- <a
- className="dropdown-toggle"
- data-toggle="dropdown"
- href="#"
- id="organization-navigation-admin"
- >
- layout.settings
-  
- <i
- className="icon-dropdown"
- />
- </a>
- <ul
- className="dropdown-menu"
- >
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/groups"
- >
- user_groups.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/permissions"
- >
- permissions.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/permission_templates"
- >
- permission_templates
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/projects_management"
- >
- projects_management
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/edit"
- >
- edit
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/delete"
- >
- delete
- </Link>
- </li>
- </ul>
- </li>
- </NavBarTabs>
-</ContextNavBar>
-`;
-
-exports[`regular user 1`] = `
-<ContextNavBar
- height={72}
- id="context-navigation"
->
- <div
- className="navbar-context-header"
- >
- <h1
- className="display-inline-block"
- >
- <OrganizationAvatar
- organization={
- Object {
- "canAdmin": false,
- "canDelete": false,
- "key": "foo",
- "name": "Foo",
- "projectVisibility": "public",
- }
- }
- />
- <Link
- className="link-base-color link-no-underline spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo"
- >
- Foo
- </Link>
- </h1>
- </div>
- <div
- className="navbar-context-meta"
- >
- <div
- className="text-muted"
- >
- <strong>
- organization.key
- :
- </strong>
-
- foo
- </div>
- </div>
- <NavBarTabs
- className="navbar-context-tabs"
- >
- <li>
- <Link
- className=""
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/projects"
- >
- projects.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/organizations/foo/issues",
- "query": Object {
- "resolved": "false",
- },
- }
- }
- >
- issues.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/quality_profiles"
- >
- quality_profiles.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/rules"
- >
- coding_rules.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/organizations/foo/quality_gates",
- }
- }
- >
- quality_gates.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/members"
- >
- organization.members.page
- </Link>
- </li>
- </NavBarTabs>
-</ContextNavBar>
-`;
-
-exports[`undeletable org 1`] = `
-<ContextNavBar
- height={72}
- id="context-navigation"
->
- <div
- className="navbar-context-header"
- >
- <h1
- className="display-inline-block"
- >
- <OrganizationAvatar
- organization={
- Object {
- "canAdmin": true,
- "canDelete": false,
- "key": "foo",
- "name": "Foo",
- "projectVisibility": "public",
- }
- }
- />
- <Link
- className="link-base-color link-no-underline spacer-left"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo"
- >
- Foo
- </Link>
- </h1>
- </div>
- <div
- className="navbar-context-meta"
- >
- <div
- className="text-muted"
- >
- <strong>
- organization.key
- :
- </strong>
-
- foo
- </div>
- </div>
- <NavBarTabs
- className="navbar-context-tabs"
- >
- <li>
- <Link
- className=""
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/projects"
- >
- projects.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/organizations/foo/issues",
- "query": Object {
- "resolved": "false",
- },
- }
- }
- >
- issues.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/quality_profiles"
- >
- quality_profiles.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/rules"
- >
- coding_rules.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to={
- Object {
- "pathname": "/organizations/foo/quality_gates",
- }
- }
- >
- quality_gates.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/members"
- >
- organization.members.page
- </Link>
- </li>
- <li
- className="dropdown"
- >
- <a
- className="dropdown-toggle"
- data-toggle="dropdown"
- href="#"
- id="organization-navigation-admin"
- >
- layout.settings
-  
- <i
- className="icon-dropdown"
- />
- </a>
- <ul
- className="dropdown-menu"
- >
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/groups"
- >
- user_groups.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/permissions"
- >
- permissions.page
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/permission_templates"
- >
- permission_templates
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/projects_management"
- >
- projects_management
- </Link>
- </li>
- <li>
- <Link
- activeClassName="active"
- onlyActiveOnIndex={false}
- style={Object {}}
- to="/organizations/foo/edit"
- >
- edit
- </Link>
- </li>
- </ul>
- </li>
- </NavBarTabs>
+ <Connect(OrganizationNavigationHeader)
+ organization={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "projectVisibility": "public",
+ }
+ }
+ />
+ <OrganizationNavigationMeta
+ organization={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "projectVisibility": "public",
+ }
+ }
+ />
+ <OrganizationNavigationMenu
+ location={
+ Object {
+ "pathname": "/organizations/foo",
+ }
+ }
+ organization={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "projectVisibility": "public",
+ }
+ }
+ />
</ContextNavBar>
`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap
new file mode 100644
index 00000000000..61f766d7cf7
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap
@@ -0,0 +1,71 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<li
+ className="dropdown"
+>
+ <a
+ className="dropdown-toggle"
+ href="#"
+ id="organization-navigation-admin"
+ onClick={[Function]}
+ >
+ layout.settings
+ <DropdownIcon />
+ </a>
+ <ul
+ className="dropdown-menu"
+ >
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/groups"
+ >
+ user_groups.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/permissions"
+ >
+ permissions.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/permission_templates"
+ >
+ permission_templates
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/projects_management"
+ >
+ projects_management
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/edit"
+ >
+ edit
+ </Link>
+ </li>
+ </ul>
+</li>
+`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationHeader-test.tsx.snap
new file mode 100644
index 00000000000..32683a0f5be
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationHeader-test.tsx.snap
@@ -0,0 +1,120 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<div
+ className="navbar-context-header"
+>
+ <h1
+ className="display-inline-block"
+ >
+ <OrganizationAvatar
+ organization={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "projectVisibility": "public",
+ }
+ }
+ />
+ <span
+ className="spacer-left"
+ >
+ Foo
+ </span>
+ </h1>
+</div>
+`;
+
+exports[`renders dropdown 1`] = `
+<div
+ className="organization-switch dropdown"
+>
+ <a
+ className="dropdown-toggle"
+ href="#"
+ onClick={[Function]}
+ >
+ Foo
+ <DropdownIcon
+ className="little-spacer-left"
+ />
+ </a>
+ <ul
+ className="dropdown-menu"
+ >
+ <li
+ key="org1"
+ >
+ <OrganizationLink
+ className="dropdown-item-flex"
+ organization={
+ Object {
+ "isAdmin": true,
+ "key": "org1",
+ "name": "org1",
+ "projectVisibility": "public",
+ }
+ }
+ >
+ <div>
+ <OrganizationAvatar
+ organization={
+ Object {
+ "isAdmin": true,
+ "key": "org1",
+ "name": "org1",
+ "projectVisibility": "public",
+ }
+ }
+ small={true}
+ />
+ <span
+ className="spacer-left"
+ >
+ org1
+ </span>
+ </div>
+ <span
+ className="outline-badge spacer-left"
+ >
+ admin
+ </span>
+ </OrganizationLink>
+ </li>
+ <li
+ key="org2"
+ >
+ <OrganizationLink
+ className="dropdown-item-flex"
+ organization={
+ Object {
+ "isAdmin": false,
+ "key": "org2",
+ "name": "org2",
+ "projectVisibility": "public",
+ }
+ }
+ >
+ <div>
+ <OrganizationAvatar
+ organization={
+ Object {
+ "isAdmin": false,
+ "key": "org2",
+ "name": "org2",
+ "projectVisibility": "public",
+ }
+ }
+ small={true}
+ />
+ <span
+ className="spacer-left"
+ >
+ org2
+ </span>
+ </div>
+ </OrganizationLink>
+ </li>
+ </ul>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenu-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenu-test.tsx.snap
new file mode 100644
index 00000000000..1177b7604b7
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenu-test.tsx.snap
@@ -0,0 +1,201 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<NavBarTabs
+ className="navbar-context-tabs"
+>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/projects"
+ >
+ projects.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/issues",
+ "query": Object {
+ "resolved": "false",
+ },
+ }
+ }
+ >
+ issues.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/quality_profiles"
+ >
+ quality_profiles.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/rules"
+ >
+ coding_rules.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/quality_gates",
+ }
+ }
+ >
+ quality_gates.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/members"
+ >
+ organization.members.page
+ </Link>
+ </li>
+ <OrganizationNavigationExtensions
+ location={
+ Object {
+ "pathname": "",
+ }
+ }
+ organization={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ "projectVisibility": "public",
+ }
+ }
+ />
+</NavBarTabs>
+`;
+
+exports[`renders for admin 1`] = `
+<NavBarTabs
+ className="navbar-context-tabs"
+>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/projects"
+ >
+ projects.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/issues",
+ "query": Object {
+ "resolved": "false",
+ },
+ }
+ }
+ >
+ issues.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/quality_profiles"
+ >
+ quality_profiles.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/rules"
+ >
+ coding_rules.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to={
+ Object {
+ "pathname": "/organizations/foo/quality_gates",
+ }
+ }
+ >
+ quality_gates.page
+ </Link>
+ </li>
+ <li>
+ <Link
+ activeClassName="active"
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/organizations/foo/members"
+ >
+ organization.members.page
+ </Link>
+ </li>
+ <OrganizationNavigationExtensions
+ location={
+ Object {
+ "pathname": "",
+ }
+ }
+ organization={
+ Object {
+ "canAdmin": true,
+ "key": "foo",
+ "name": "Foo",
+ "projectVisibility": "public",
+ }
+ }
+ />
+ <OrganizationNavigationAdministration
+ location={
+ Object {
+ "pathname": "",
+ }
+ }
+ organization={
+ Object {
+ "canAdmin": true,
+ "key": "foo",
+ "name": "Foo",
+ "projectVisibility": "public",
+ }
+ }
+ />
+</NavBarTabs>
+`;
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMeta-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMeta-test.tsx.snap
new file mode 100644
index 00000000000..3da71c25930
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMeta-test.tsx.snap
@@ -0,0 +1,18 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<div
+ className="navbar-context-meta"
+>
+ <div
+ className="text-muted"
+ >
+ <strong>
+ organization.key
+ :
+ </strong>
+
+ foo
+ </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/components/icons-components/DropdownIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/DropdownIcon.tsx
new file mode 100644
index 00000000000..20bc9629f31
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/icons-components/DropdownIcon.tsx
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+import * as React from 'react';
+import { IconProps } from './types';
+
+export default function DropdownIcon({ className, fill = 'currentColor', size = 16 }: IconProps) {
+ return (
+ <svg
+ className={className}
+ width={size}
+ height={size}
+ viewBox="0 0 16 16"
+ version="1.1"
+ xmlnsXlink="http://www.w3.org/1999/xlink"
+ xmlSpace="preserve">
+ <g transform="matrix(0.0273438,0,0,0.0273438,4.5,2.65625)">
+ <path
+ style={{ fill }}
+ d="M256,176C256,180.333 254.417,184.083 251.25,187.25L139.25,299.25C136.083,302.417 132.333,304 128,304C123.667,304 119.917,302.417 116.75,299.25L4.75,187.25C1.583,184.083 0,180.333 0,176C0,171.667 1.583,167.917 4.75,164.75C7.917,161.583 11.667,160 16,160L240,160C244.333,160 248.083,161.583 251.25,164.75C254.417,167.917 256,171.667 256,176Z"
+ />
+ </g>
+ </svg>
+ );
+}