From f9b743b8b4c974d5c08829c98e7dc5c52f52eed1 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Thu, 15 Feb 2018 17:20:18 +0100 Subject: [PATCH] SONAR-10423 display home page selector (#3065) --- .../src/main/js/app/components/Landing.tsx | 2 +- .../nav/component/ComponentNavMeta.tsx | 43 ++++++++---- server/sonar-web/src/main/js/app/types.ts | 32 ++++++--- .../src/main/js/apps/issues/components/App.js | 8 +-- .../js/apps/issues/components/PageActions.js | 5 +- .../navigation/OrganizationNavigationMeta.tsx | 6 +- .../OrganizationNavigationMeta-test.tsx.snap | 2 +- .../apps/projects/components/PageHeader.tsx | 7 +- .../__snapshots__/PageHeader-test.tsx.snap | 24 +++++++ .../js/components/controls/HomePageSelect.tsx | 18 ++--- .../__tests__/HomePageSelect-test.tsx | 66 +++++++++++++++++++ .../HomePageSelect-test.tsx.snap | 33 ++++++++++ server/sonar-web/src/main/js/helpers/urls.ts | 15 ++++- 13 files changed, 214 insertions(+), 47 deletions(-) create mode 100644 server/sonar-web/src/main/js/components/controls/__tests__/HomePageSelect-test.tsx create mode 100644 server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/HomePageSelect-test.tsx.snap diff --git a/server/sonar-web/src/main/js/app/components/Landing.tsx b/server/sonar-web/src/main/js/app/components/Landing.tsx index ed401366a98..bf257783c72 100644 --- a/server/sonar-web/src/main/js/app/components/Landing.tsx +++ b/server/sonar-web/src/main/js/app/components/Landing.tsx @@ -37,7 +37,7 @@ class Landing extends React.PureComponent { 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 { diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx index 42ab6e09378..9bee1a224fe 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx @@ -19,13 +19,17 @@ */ 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 (
@@ -51,26 +69,27 @@ export function ComponentNavMeta({ branch, component, currentUser }: Props) { )} {component.version && !shortBranch && ( - +
{translate('version')} {component.version}
)} - {isLoggedIn(currentUser) && - mainBranch && ( -
+ {isLoggedIn(currentUser) && ( +
+ {mainBranch && ( - -
- )} + )} + {(mainBranch || longBranch) && + currentPage !== undefined && ( + + )} +
+ )} {shortBranch && (
diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index c2fe0d91b1c..252f6c135a2 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -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 { diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.js b/server/sonar-web/src/main/js/apps/issues/components/App.js index a6068133e94..edd359159ad 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/App.js +++ b/server/sonar-web/src/main/js/apps/issues/components/App.js @@ -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 { ) : ( diff --git a/server/sonar-web/src/main/js/apps/issues/components/PageActions.js b/server/sonar-web/src/main/js/apps/issues/components/PageActions.js index 328b61679ae..b194b24fecf 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/PageActions.js +++ b/server/sonar-web/src/main/js/apps/issues/components/PageActions.js @@ -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 && ( )}
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 index f9097f7b6e9..ba47200eb0e 100644 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx @@ -39,8 +39,8 @@ export function OrganizationNavigationMeta({ onSonarCloud, organization }: Props + rel="nofollow" + title={organization.url}> {organization.url} )} @@ -50,7 +50,7 @@ export function OrganizationNavigationMeta({ onSonarCloud, organization }: Props {onSonarCloud && (
)} 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 index 207f8fe1faf..f70dd859642 100644 --- 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 @@ -20,7 +20,7 @@ exports[`renders 1`] = ` @@ -101,10 +102,12 @@ export default function PageHeader(props: Props) { )}
- {props.isFavorite && ( + {showHomePageSelect && ( )} diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap index 0f745872f11..5941322b8ae 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap @@ -37,6 +37,14 @@ exports[`should render correctly 1`] = ` projects._projects + `; @@ -80,6 +88,14 @@ exports[`should render correctly while loading 1`] = ` projects._projects + `; @@ -120,6 +136,14 @@ exports[`should render disabled sorting options for visualizations 1`] = `
+ `; diff --git a/server/sonar-web/src/main/js/components/controls/HomePageSelect.tsx b/server/sonar-web/src/main/js/components/controls/HomePageSelect.tsx index f534362e128..cf84c84e776 100644 --- a/server/sonar-web/src/main/js/components/controls/HomePageSelect.tsx +++ b/server/sonar-web/src/main/js/components/controls/HomePageSelect.tsx @@ -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 { }; 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 { } } -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 index 00000000000..4d5af634c91 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/HomePageSelect-test.tsx @@ -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(, { + 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 index 00000000000..4ccff84cbdc --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/HomePageSelect-test.tsx.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render checked 1`] = ` + + + + + +`; + +exports[`should render unchecked 1`] = ` + + + + + +`; diff --git a/server/sonar-web/src/main/js/helpers/urls.ts b/server/sonar-web/src/main/js/helpers/urls.ts index 2bde3a1184e..ae125724761 100644 --- a/server/sonar-web/src/main/js/helpers/urls.ts +++ b/server/sonar-web/src/main/js/helpers/urls.ts @@ -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' } }; } -- 2.39.5