]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10423 display home page selector (#3065)
authorStas Vilchik <stas.vilchik@sonarsource.com>
Thu, 15 Feb 2018 16:20:18 +0000 (17:20 +0100)
committerStas Vilchik <stas.vilchik@sonarsource.com>
Fri, 2 Mar 2018 12:17:32 +0000 (13:17 +0100)
13 files changed:
server/sonar-web/src/main/js/app/components/Landing.tsx
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx
server/sonar-web/src/main/js/app/types.ts
server/sonar-web/src/main/js/apps/issues/components/App.js
server/sonar-web/src/main/js/apps/issues/components/PageActions.js
server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx
server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMeta-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
server/sonar-web/src/main/js/components/controls/HomePageSelect.tsx
server/sonar-web/src/main/js/components/controls/__tests__/HomePageSelect-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/HomePageSelect-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/urls.ts

index ed401366a98bb06efd9a1077fa7b3b6c8bb10cc4..bf257783c72eb8fcbaffc7972219342989340137 100644 (file)
@@ -37,7 +37,7 @@ class Landing extends React.PureComponent<Props> {
   componentDidMount() {
     const { currentUser, onSonarCloud } = this.props;
     if (isLoggedIn(currentUser)) {
-      if (onSonarCloud && currentUser.homepage) {
+      if (currentUser.homepage) {
         const homepage = getHomePageUrl(currentUser.homepage);
         this.context.router.replace(homepage);
       } else {
index 42ab6e0937838115c90eca6d304469d626d868c4..9bee1a224fed9c2416dfe78d593a969dfc18b265 100644 (file)
  */
 import * as React from 'react';
 import { connect } from 'react-redux';
-import { Branch, Component, CurrentUser, isLoggedIn, HomePageType } from '../../../types';
+import { Branch, Component, CurrentUser, isLoggedIn, HomePageType, HomePage } from '../../../types';
 import BranchStatus from '../../../../components/common/BranchStatus';
 import DateTimeFormatter from '../../../../components/intl/DateTimeFormatter';
 import Favorite from '../../../../components/controls/Favorite';
 import HomePageSelect from '../../../../components/controls/HomePageSelect';
 import Tooltip from '../../../../components/controls/Tooltip';
-import { isShortLivingBranch } from '../../../../helpers/branches';
+import {
+  isShortLivingBranch,
+  isLongLivingBranch,
+  getBranchName
+} from '../../../../helpers/branches';
 import { translate } from '../../../../helpers/l10n';
 import { getCurrentUser } from '../../../../store/rootReducer';
 
@@ -41,6 +45,20 @@ interface Props extends StateProps {
 export function ComponentNavMeta({ branch, component, currentUser }: Props) {
   const shortBranch = isShortLivingBranch(branch);
   const mainBranch = !branch || branch.isMain;
+  const longBranch = isLongLivingBranch(branch);
+
+  let currentPage: HomePage | undefined;
+  if (component.qualifier === 'VW' || component.qualifier === 'SVW') {
+    currentPage = { type: HomePageType.Portfolio, component: component.key };
+  } else if (component.qualifier === 'APP') {
+    currentPage = { type: HomePageType.Application, component: component.key };
+  } else if (component.qualifier === 'TRK') {
+    currentPage = {
+      type: HomePageType.Project,
+      component: component.key,
+      branch: getBranchName(branch)
+    };
+  }
 
   return (
     <div className="navbar-context-meta">
@@ -51,26 +69,27 @@ export function ComponentNavMeta({ branch, component, currentUser }: Props) {
       )}
       {component.version &&
         !shortBranch && (
-          <Tooltip overlay={`${translate('version')} ${component.version}`} mouseEnterDelay={0.5}>
+          <Tooltip mouseEnterDelay={0.5} overlay={`${translate('version')} ${component.version}`}>
             <div className="spacer-left text-limited">
               {translate('version')} {component.version}
             </div>
           </Tooltip>
         )}
-      {isLoggedIn(currentUser) &&
-        mainBranch && (
-          <div className="navbar-context-meta-secondary">
+      {isLoggedIn(currentUser) && (
+        <div className="navbar-context-meta-secondary">
+          {mainBranch && (
             <Favorite
               component={component.key}
               favorite={Boolean(component.isFavorite)}
               qualifier={component.qualifier}
             />
-            <HomePageSelect
-              className="spacer-left"
-              currentPage={{ type: HomePageType.Project, parameter: component.key }}
-            />
-          </div>
-        )}
+          )}
+          {(mainBranch || longBranch) &&
+            currentPage !== undefined && (
+              <HomePageSelect className="spacer-left" currentPage={currentPage} />
+            )}
+        </div>
+      )}
       {shortBranch && (
         <div className="navbar-context-meta-secondary">
           <BranchStatus branch={branch!} />
index c2fe0d91b1cb807b25ef1292e5185d3f551228da..252f6c135a2e6d04f35361d233f6a10014e584fb 100644 (file)
@@ -130,16 +130,27 @@ export interface Group {
   name: string;
 }
 
-export interface HomePage {
-  parameter?: string;
-  type: HomePageType;
-}
+export type HomePage =
+  | { type: HomePageType.Application; component: string }
+  | { type: HomePageType.Issues }
+  | { type: HomePageType.MyIssues }
+  | { type: HomePageType.MyProjects }
+  | { type: HomePageType.Organization; organization: string }
+  | { type: HomePageType.Portfolio; component: string }
+  | { type: HomePageType.Portfolios }
+  | { type: HomePageType.Project; branch: string | undefined; component: string }
+  | { type: HomePageType.Projects };
 
 export enum HomePageType {
-  Project = 'PROJECT',
-  Organization = 'ORGANIZATION',
+  Application = 'APPLICATION',
+  Issues = 'ISSUES',
+  MyIssues = 'MY_ISSUES',
   MyProjects = 'MY_PROJECTS',
-  MyIssues = 'MY_ISSUES'
+  Organization = 'ORGANIZATION',
+  Portfolio = 'PORTFOLIO',
+  Portfolios = 'PORTFOLIOS',
+  Project = 'PROJECT',
+  Projects = 'PROJECTS'
 }
 
 export interface IdentityProvider {
@@ -155,7 +166,12 @@ export function isLoggedIn(user: CurrentUser): user is LoggedInUser {
 }
 
 export function isSameHomePage(a: HomePage, b: HomePage) {
-  return a.type === b.type && a.parameter === b.parameter;
+  return (
+    a.type === b.type &&
+    (a as any).branch === (b as any).branch &&
+    (a as any).component === (b as any).component &&
+    (a as any).organization === (b as any).organization
+  );
 }
 
 export interface LightComponent {
index a6068133e9430e56fffe10b46f347ad069bc1a32..edd359159add207405d98566d628e9f3a8b6efa3 100644 (file)
@@ -55,7 +55,6 @@ import {
   CurrentUser
 } from '../utils'; */
 import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication';
-import { isLoggedIn } from '../../../app/types';
 import ListFooter from '../../../components/controls/ListFooter';
 import EmptySearch from '../../../components/common/EmptySearch';
 import FiltersHeader from '../../../components/common/FiltersHeader';
@@ -932,14 +931,13 @@ export default class App extends React.PureComponent {
                 ) : (
                   <PageActions
                     canSetHome={
-                      this.props.onSonarCloud &&
-                      isLoggedIn(this.props.currentUser) &&
-                      this.props.myIssues &&
                       !this.props.organization &&
-                      !this.props.component
+                      !this.props.component &&
+                      (!this.props.onSonarCloud || this.props.myIssues)
                     }
                     loading={this.state.loading}
                     onReload={this.handleReload}
+                    onSonarCloud={this.props.onSonarCloud}
                     paging={paging}
                     selectedIndex={selectedIndex}
                   />
index 328b61679ae1fb249c89c7f27749407a6086f888..b194b24fecf2b91211d9ae0a763bec58638409f3 100644 (file)
@@ -32,6 +32,7 @@ type Props = {|
   canSetHome: bool,
   loading: boolean,
   onReload: () => void,
+  onSonarCloud: bool,
   paging: ?Paging,
   selectedIndex: ?number
 |};
@@ -77,7 +78,9 @@ export default class PageActions extends React.PureComponent {
         {this.props.canSetHome && (
           <HomePageSelect
             className="huge-spacer-left"
-            currentPage={{ type: HomePageType.MyIssues }}
+            currentPage={{
+              type: this.props.onSonarCloud ? HomePageType.MyIssues : HomePageType.Issues
+            }}
           />
         )}
       </div>
index f9097f7b6e9c06a5b892fe79d50abe62cb4f52d8..ba47200eb0ef4278061d2d58d22cf3538fd84712 100644 (file)
@@ -39,8 +39,8 @@ export function OrganizationNavigationMeta({ onSonarCloud, organization }: Props
         <a
           className="spacer-right text-limited"
           href={organization.url}
-          title={organization.url}
-          rel="nofollow">
+          rel="nofollow"
+          title={organization.url}>
           {organization.url}
         </a>
       )}
@@ -50,7 +50,7 @@ export function OrganizationNavigationMeta({ onSonarCloud, organization }: Props
       {onSonarCloud && (
         <div className="navbar-context-meta-secondary">
           <HomePageSelect
-            currentPage={{ type: HomePageType.Organization, parameter: organization.key }}
+            currentPage={{ type: HomePageType.Organization, organization: organization.key }}
           />
         </div>
       )}
index 207f8fe1faf27d4656e89e075940e0a249897c9a..f70dd85964215bbeb9c7e5ed6815b0a28773c9db 100644 (file)
@@ -20,7 +20,7 @@ exports[`renders 1`] = `
     <Connect(HomePageSelect)
       currentPage={
         Object {
-          "parameter": "foo",
+          "organization": "foo",
           "type": "ORGANIZATION",
         }
       }
index cbc15e22bada429b43d4a6a88c9d606b79f68209..c0aeb6f835b9893aad4085297a3eb7090e1052db 100644 (file)
@@ -50,6 +50,7 @@ export default function PageHeader(props: Props) {
   const { loading, total, projects, currentUser, view } = props;
   const limitReached = projects != null && total != null && projects.length < total;
   const defaultOption = isLoggedIn(currentUser) ? 'name' : 'analysis_date';
+  const showHomePageSelect = !props.onSonarCloud || props.isFavorite;
 
   return (
     <header className="page-header projects-topbar-items">
@@ -101,10 +102,12 @@ export default function PageHeader(props: Props) {
         )}
       </div>
 
-      {props.isFavorite && (
+      {showHomePageSelect && (
         <HomePageSelect
           className="huge-spacer-left"
-          currentPage={{ type: HomePageType.MyProjects }}
+          currentPage={
+            props.onSonarCloud ? { type: HomePageType.MyProjects } : { type: HomePageType.Projects }
+          }
         />
       )}
     </header>
index 0f745872f115f7feda489d7e4388dd526f465132..5941322b8aefb1a8b0ac1be1aee4b94817a49638 100644 (file)
@@ -37,6 +37,14 @@ exports[`should render correctly 1`] = `
       projects._projects
     </span>
   </div>
+  <Connect(HomePageSelect)
+    className="huge-spacer-left"
+    currentPage={
+      Object {
+        "type": "PROJECTS",
+      }
+    }
+  />
 </header>
 `;
 
@@ -80,6 +88,14 @@ exports[`should render correctly while loading 1`] = `
       projects._projects
     </span>
   </div>
+  <Connect(HomePageSelect)
+    className="huge-spacer-left"
+    currentPage={
+      Object {
+        "type": "PROJECTS",
+      }
+    }
+  />
 </header>
 `;
 
@@ -120,6 +136,14 @@ exports[`should render disabled sorting options for visualizations 1`] = `
   <div
     className="projects-topbar-item is-last"
   />
+  <Connect(HomePageSelect)
+    className="huge-spacer-left"
+    currentPage={
+      Object {
+        "type": "PROJECTS",
+      }
+    }
+  />
 </header>
 `;
 
index f534362e128102e21af5e3f647b0bab3550fdb9a..cf84c84e776995552f920e6dadbebddb5e1ffc0f 100644 (file)
@@ -24,12 +24,11 @@ import Tooltip from './Tooltip';
 import HomeIcon from '../icons-components/HomeIcon';
 import { CurrentUser, isLoggedIn, HomePage, isSameHomePage } from '../../app/types';
 import { translate } from '../../helpers/l10n';
-import { getCurrentUser, getGlobalSettingValue } from '../../store/rootReducer';
+import { getCurrentUser } from '../../store/rootReducer';
 import { setHomePage } from '../../store/users/actions';
 
 interface StateProps {
   currentUser: CurrentUser;
-  onSonarCloud: boolean;
 }
 
 interface DispatchProps {
@@ -49,9 +48,9 @@ class HomePageSelect extends React.PureComponent<Props> {
   };
 
   render() {
-    const { currentPage, currentUser, onSonarCloud } = this.props;
+    const { currentPage, currentUser } = this.props;
 
-    if (!isLoggedIn(currentUser) || !onSonarCloud) {
+    if (!isLoggedIn(currentUser)) {
       return null;
     }
 
@@ -82,14 +81,9 @@ class HomePageSelect extends React.PureComponent<Props> {
   }
 }
 
-const mapStateToProps = (state: any): StateProps => {
-  const sonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled');
-
-  return {
-    currentUser: getCurrentUser(state),
-    onSonarCloud: Boolean(sonarCloudSetting && sonarCloudSetting.value === 'true')
-  };
-};
+const mapStateToProps = (state: any): StateProps => ({
+  currentUser: getCurrentUser(state)
+});
 
 const mapDispatchToProps: DispatchProps = { setHomePage };
 
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/HomePageSelect-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/HomePageSelect-test.tsx
new file mode 100644 (file)
index 0000000..4d5af63
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
+import HomePageSelect from '../HomePageSelect';
+import { setHomePage } from '../../../api/users';
+import { HomePageType, HomePage, LoggedInUser } from '../../../app/types';
+import { click } from '../../../helpers/testUtils';
+import rootReducer, { getCurrentUser } from '../../../store/rootReducer';
+import configureStore from '../../../store/utils/configureStore';
+
+jest.mock('../../../api/users', () => ({
+  setHomePage: jest.fn(() => Promise.resolve())
+}));
+
+const homepage: HomePage = { type: HomePageType.Projects };
+
+it('should render unchecked', () => {
+  const store = configureStore(rootReducer, { users: { currentUser: { isLoggedIn: true } } });
+  expect(getWrapper(homepage, store)).toMatchSnapshot();
+});
+
+it('should render checked', () => {
+  const store = configureStore(rootReducer, {
+    users: { currentUser: { isLoggedIn: true, homepage } }
+  });
+  expect(getWrapper(homepage, store)).toMatchSnapshot();
+});
+
+it('should set new home page', async () => {
+  const store = configureStore(rootReducer, { users: { currentUser: { isLoggedIn: true } } });
+  const wrapper = getWrapper(homepage, store);
+  click(wrapper.find('a'));
+  await new Promise(setImmediate);
+  const currentUser = getCurrentUser(store.getState()) as LoggedInUser;
+  expect(currentUser.homepage).toEqual(homepage);
+  expect(setHomePage).toBeCalledWith(homepage);
+});
+
+it('should not render for anonymous', () => {
+  const store = configureStore(rootReducer, { users: { currentUser: { isLoggedIn: false } } });
+  expect(getWrapper(homepage, store).type()).toBeNull();
+});
+
+function getWrapper(currentPage: HomePage, store: any) {
+  return shallow(<HomePageSelect currentPage={currentPage} />, {
+    context: { store }
+  }).dive();
+}
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/HomePageSelect-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/HomePageSelect-test.tsx.snap
new file mode 100644 (file)
index 0000000..4ccff84
--- /dev/null
@@ -0,0 +1,33 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render checked 1`] = `
+<Tooltip
+  overlay="homepage.current"
+  placement="left"
+>
+  <span
+    className="display-inline-block"
+  >
+    <HomeIcon
+      filled={true}
+    />
+  </span>
+</Tooltip>
+`;
+
+exports[`should render unchecked 1`] = `
+<Tooltip
+  overlay="homepage.check"
+  placement="left"
+>
+  <a
+    className="link-no-underline display-inline-block"
+    href="#"
+    onClick={[Function]}
+  >
+    <HomeIcon
+      filled={false}
+    />
+  </a>
+</Tooltip>
+`;
index 2bde3a1184e9ca3e9f3d9693c917699d3dff8d9c..ae1257247613774e8c7ca29f8f1024c4cadf6a38 100644 (file)
@@ -48,6 +48,10 @@ export function getProjectUrl(key: string, branch?: string): Location {
   return { pathname: '/dashboard', query: { id: key, branch } };
 }
 
+export function getPortfolioUrl(key: string): Location {
+  return { pathname: '/portfolio', query: { id: key } };
+}
+
 export function getComponentBackgroundTaskUrl(componentKey: string, status?: string): Location {
   return { pathname: '/project/background_tasks', query: { id: componentKey, status } };
 }
@@ -178,12 +182,19 @@ export function getOrganizationUrl(organization: string) {
 
 export function getHomePageUrl(homepage: HomePage) {
   switch (homepage.type) {
+    case HomePageType.Application:
+      return getProjectUrl(homepage.component);
     case HomePageType.Project:
-      return getProjectUrl(homepage.parameter!);
+      return getProjectUrl(homepage.component, homepage.branch);
     case HomePageType.Organization:
-      return getOrganizationUrl(homepage.parameter!);
+      return getOrganizationUrl(homepage.organization);
+    case HomePageType.Portfolio:
+      return getPortfolioUrl(homepage.component);
+    case HomePageType.Portfolios:
+      return '/portfolios';
     case HomePageType.MyProjects:
       return '/projects';
+    case HomePageType.Issues:
     case HomePageType.MyIssues:
       return { pathname: '/issues', query: { resolved: 'false' } };
   }