]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10186 Switch between organizations from the header
authorStas Vilchik <stas.vilchik@sonarsource.com>
Mon, 11 Dec 2017 16:59:17 +0000 (17:59 +0100)
committerStas Vilchik <stas.vilchik@sonarsource.com>
Tue, 2 Jan 2018 09:38:10 +0000 (10:38 +0100)
19 files changed:
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.css [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationExtensions.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeaderContainer.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenu.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.tsx
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationAdministration-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationHeader-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMenu-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMeta-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationHeader-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenu-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMeta-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/components/icons-components/DropdownIcon.tsx [new file with mode: 0644]

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 (file)
index 0000000..f61bbd3
--- /dev/null
@@ -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);
+}
index 4cffcc47d80a8900bf18e6a1fd749a0f6b53a04f..b57d98405179a40bfabceb85e670b7bb1b6d53fb 100644 (file)
  * 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 (file)
index 0000000..98d50b1
--- /dev/null
@@ -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 (file)
index 0000000..097fcba
--- /dev/null
@@ -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 (file)
index 0000000..afa4332
--- /dev/null
@@ -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 (file)
index 0000000..c94996e
--- /dev/null
@@ -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 (file)
index 0000000..048c9f3
--- /dev/null
@@ -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 (file)
index 0000000..b720223
--- /dev/null
@@ -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>
+  );
+}
index e4884f83545c1469c60b5d93ed9b48aa3a3fedc9..41fea385334898212d3bb92faba345f0871b39e7 100644 (file)
@@ -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 (file)
index 0000000..4bebf6b
--- /dev/null
@@ -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 (file)
index 0000000..bb9bd18
--- /dev/null
@@ -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 (file)
index 0000000..a5b266b
--- /dev/null
@@ -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 (file)
index 0000000..5b69f2c
--- /dev/null
@@ -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();
+});
index b9efc8fe2e30d1dabae7c8fb7e1bab2476efb872..eb04cc431fe1023942caa70cbef67a9ca1c056ac 100644 (file)
 // 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 (file)
index 0000000..61f766d
--- /dev/null
@@ -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 (file)
index 0000000..32683a0
--- /dev/null
@@ -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 (file)
index 0000000..1177b76
--- /dev/null
@@ -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 (file)
index 0000000..3da71c2
--- /dev/null
@@ -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 (file)
index 0000000..20bc962
--- /dev/null
@@ -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>
+  );
+}