From b5825706ab56c92dfae7e12d91af4891e363e03b Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Thu, 23 Nov 2017 16:05:14 +0100 Subject: [PATCH] SONAR-10080 turn Projects to My Projects --- .../components/nav/global/GlobalNavMenu.js | 5 +- server/sonar-web/src/main/js/app/types.ts | 16 ++++ .../components/OrganizationProjects.tsx | 17 ++--- .../src/main/js/apps/organizations/routes.js | 13 +--- .../apps/projects/components/AllProjects.tsx | 29 +++++-- .../components/AllProjectsContainer.tsx | 44 +++++++++++ .../main/js/apps/projects/components/App.tsx | 75 ------------------- .../components/DefaultPageSelector.tsx | 44 ++++++----- .../DefaultPageSelectorContainer.tsx | 38 ++++++++++ .../projects/components/FavoriteFilter.tsx | 9 +-- .../components/FavoriteFilterContainer.tsx | 23 ++++++ .../components/FavoriteProjectsContainer.tsx | 4 +- .../apps/projects/components/PageHeader.tsx | 5 +- .../apps/projects/components/PageSidebar.tsx | 15 +++- .../components/ProjectCardLanguages.tsx | 67 +++++++---------- .../ProjectCardLanguagesContainer.tsx | 33 ++++++++ .../projects/components/ProjectCardLeak.tsx | 6 +- .../components/ProjectCardOrganization.tsx | 31 +++----- .../ProjectCardOrganizationContainer.tsx | 32 ++++++++ .../components/ProjectCardOverall.tsx | 6 +- .../components/ProjectCardOverallMeasures.tsx | 6 +- .../components/__tests__/AllProjects-test.tsx | 22 ++++-- .../__tests__/DefaultPageSelector-test.tsx | 22 ++++-- .../__tests__/FavoriteFilter-test.tsx | 20 +++-- .../components/__tests__/PageHeader-test.tsx | 1 + .../components/__tests__/PageSidebar-test.tsx | 19 ++++- .../__tests__/ProjectCardLanguages-test.tsx | 10 +-- .../__snapshots__/AllProjects-test.tsx.snap | 4 + .../__snapshots__/PageSidebar-test.tsx.snap | 8 +- .../ProjectCardLeak-test.tsx.snap | 2 +- .../ProjectCardOverall-test.tsx.snap | 2 +- .../ProjectCardOverallMeasures-test.tsx.snap | 4 +- .../apps/projects/filters/LanguagesFilter.tsx | 14 ++-- .../filters/LanguagesFilterContainer.tsx | 33 ++++++++ .../__tests__/LanguagesFilter-test.tsx | 22 +++--- .../src/main/js/apps/projects/routes.ts | 38 +++------- .../src/main/js/components/lazyLoad.tsx | 62 +++++++++++++++ .../languages/{reducer.js => reducer.ts} | 10 ++- .../withCurrentUser.tsx} | 30 +++----- .../resources/org/sonar/l10n/core.properties | 1 + 40 files changed, 534 insertions(+), 308 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projects/components/App.tsx create mode 100644 server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelectorContainer.tsx create mode 100644 server/sonar-web/src/main/js/apps/projects/components/FavoriteFilterContainer.tsx create mode 100644 server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.tsx create mode 100644 server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganizationContainer.tsx create mode 100644 server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.tsx create mode 100644 server/sonar-web/src/main/js/components/lazyLoad.tsx rename server/sonar-web/src/main/js/store/languages/{reducer.js => reducer.ts} (79%) rename server/sonar-web/src/main/js/{apps/organizations/components/OrganizationFavoriteProjects.tsx => store/withCurrentUser.tsx} (60%) diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js index 2a1d784aa1c..02b3ab3ce11 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js @@ -30,7 +30,8 @@ export default class GlobalNavMenu extends React.PureComponent { currentUser: PropTypes.object.isRequired, location: PropTypes.shape({ pathname: PropTypes.string.isRequired - }).isRequired + }).isRequired, + sonarCloud: PropTypes.bool }; static defaultProps = { @@ -46,7 +47,7 @@ export default class GlobalNavMenu extends React.PureComponent { return (
  • - {translate('projects.page')} + {this.props.sonarCloud ? translate('my_projects') : translate('projects.page')}
  • ); diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 73239aeb430..f18764601e4 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -133,3 +133,19 @@ export enum Visibility { Public = 'public', Private = 'private' } + +export interface CurrentUser { + isLoggedIn: boolean; + showOnboardingTutorial?: boolean; +} + +export interface LoggedInUser extends CurrentUser { + avatar?: string; + email?: string; + isLoggedIn: true; + name: string; +} + +export function isLoggedIn(user: CurrentUser): user is LoggedInUser { + return user.isLoggedIn; +} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx index 07f220f0d96..65169afa93e 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx +++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx @@ -18,8 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import App from '../../projects/components/App'; -import AllProjects from '../../projects/components/AllProjects'; +import AllProjectsContainer from '../../projects/components/AllProjectsContainer'; interface Props { location: { pathname: string; query: { [x: string]: string } }; @@ -28,14 +27,10 @@ interface Props { export default function OrganizationProjects(props: Props) { return ( -
    - - - -
    + ); } diff --git a/server/sonar-web/src/main/js/apps/organizations/routes.js b/server/sonar-web/src/main/js/apps/organizations/routes.js index 072bb955202..b0010c5e416 100644 --- a/server/sonar-web/src/main/js/apps/organizations/routes.js +++ b/server/sonar-web/src/main/js/apps/organizations/routes.js @@ -21,7 +21,6 @@ import OrganizationPageContainer from './components/OrganizationPage'; import OrganizationPageExtension from '../../app/components/extensions/OrganizationPageExtension'; import OrganizationContainer from './components/OrganizationContainer'; import OrganizationProjects from './components/OrganizationProjects'; -import OrganizationFavoriteProjects from './components/OrganizationFavoriteProjects'; import OrganizationRules from './components/OrganizationRules'; import OrganizationAdminContainer from './components/OrganizationAdmin'; import OrganizationEdit from './components/OrganizationEdit'; @@ -51,17 +50,7 @@ const routes = [ { path: 'projects', component: OrganizationContainer, - childRoutes: [ - { - indexRoute: { - component: OrganizationProjects - } - }, - { - path: 'favorite', - component: OrganizationFavoriteProjects - } - ] + childRoutes: [{ indexRoute: { component: OrganizationProjects } }] }, { path: 'issues', diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx index cd5f60bd1eb..fb4e108e3db 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx @@ -24,6 +24,7 @@ import PageHeader from './PageHeader'; import ProjectsList from './ProjectsList'; import PageSidebar from './PageSidebar'; import Visualizations from '../visualizations/Visualizations'; +import { CurrentUser, isLoggedIn } from '../../../app/types'; import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; import ListFooter from '../../../components/controls/ListFooter'; import { translate } from '../../../helpers/l10n'; @@ -34,10 +35,13 @@ import { Project, Facets } from '../types'; import { fetchProjects, parseSorting, SORTING_SWITCH } from '../utils'; import { parseUrlQuery, Query } from '../query'; -interface Props { +export interface Props { + currentUser: CurrentUser; isFavorite: boolean; location: { pathname: string; query: { [x: string]: string } }; + onSonarCloud: boolean; organization?: { key: string }; + organizationsEnabled: boolean; } interface State { @@ -53,8 +57,6 @@ export default class AllProjects extends React.PureComponent { mounted: boolean; static contextTypes = { - currentUser: PropTypes.object.isRequired, - organizationsEnabled: PropTypes.bool, router: PropTypes.object.isRequired }; @@ -65,7 +67,13 @@ export default class AllProjects extends React.PureComponent { componentDidMount() { this.mounted = true; - if (this.props.isFavorite && !this.context.currentUser.isLoggedIn) { + + const html = document.querySelector('html'); + if (html) { + html.classList.add('dashboard-page'); + } + + if (this.props.isFavorite && !isLoggedIn(this.props.currentUser)) { handleRequiredAuthentication(); return; } @@ -84,6 +92,12 @@ export default class AllProjects extends React.PureComponent { componentWillUnmount() { this.mounted = false; + + const html = document.querySelector('html'); + if (html) { + html.classList.remove('dashboard-page'); + } + const footer = document.getElementById('footer'); if (footer) { footer.classList.remove('page-footer-with-sidebar'); @@ -231,6 +245,7 @@ export default class AllProjects extends React.PureComponent { isFavorite={this.props.isFavorite} organization={this.props.organization} query={this.state.query} + showFavoriteFilter={!this.props.onSonarCloud} view={this.getView()} visualization={this.getVisualization()} /> @@ -245,7 +260,7 @@ export default class AllProjects extends React.PureComponent {
    {
    {this.state.projects && ( { render() { return ( -
    +
    {this.renderSide()} diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx new file mode 100644 index 00000000000..5eb37454226 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx @@ -0,0 +1,44 @@ +/* + * 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 { connect } from 'react-redux'; +import { CurrentUser } from '../../../app/types'; +import { lazyLoad } from '../../../components/lazyLoad'; +import { + getCurrentUser, + areThereCustomOrganizations, + getGlobalSettingValue +} from '../../../store/rootReducer'; + +interface StateProps { + currentUser: CurrentUser; + onSonarCloud: boolean; + organizationsEnabled: boolean; +} + +const stateToProps = (state: any) => { + const onSonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled'); + return { + currentUser: getCurrentUser(state), + onSonarCloud: Boolean(onSonarCloudSetting && onSonarCloudSetting.value === 'true'), + organizationsEnabled: areThereCustomOrganizations(state) + }; +}; + +export default connect(stateToProps)(lazyLoad(() => import('./AllProjects'))); diff --git a/server/sonar-web/src/main/js/apps/projects/components/App.tsx b/server/sonar-web/src/main/js/apps/projects/components/App.tsx deleted file mode 100644 index 0e605b910e8..00000000000 --- a/server/sonar-web/src/main/js/apps/projects/components/App.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 { connect } from 'react-redux'; -import * as PropTypes from 'prop-types'; -import { - getCurrentUser, - getLanguages, - areThereCustomOrganizations -} from '../../../store/rootReducer'; - -interface Props { - currentUser: { isLoggedIn: boolean }; - languages: { [key: string]: { key: string; name: string } }; - organizationsEnabled: boolean; -} - -class App extends React.PureComponent { - static childContextTypes = { - currentUser: PropTypes.object.isRequired, - languages: PropTypes.object.isRequired, - organizationsEnabled: PropTypes.bool - }; - - getChildContext() { - return { - currentUser: this.props.currentUser, - languages: this.props.languages, - organizationsEnabled: this.props.organizationsEnabled - }; - } - - componentDidMount() { - const elem = document.querySelector('html'); - if (elem) { - elem.classList.add('dashboard-page'); - } - } - - componentWillUnmount() { - const elem = document.querySelector('html'); - if (elem) { - elem.classList.remove('dashboard-page'); - } - } - - render() { - return
    {this.props.children}
    ; - } -} - -const mapStateToProps = (state: any) => ({ - currentUser: getCurrentUser(state), - languages: getLanguages(state), - organizationsEnabled: areThereCustomOrganizations(state) -}); - -export default connect(mapStateToProps)(App); diff --git a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx index c5d4197f1be..56d148f82b2 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx @@ -19,12 +19,15 @@ */ import * as React from 'react'; import * as PropTypes from 'prop-types'; -import AllProjects from './AllProjects'; +import AllProjectsContainer from './AllProjectsContainer'; import { isFavoriteSet, isAllSet } from '../../../helpers/storage'; import { searchProjects } from '../../../api/components'; +import { CurrentUser, isLoggedIn } from '../../../app/types'; interface Props { + currentUser: CurrentUser; location: { pathname: string; query: { [x: string]: string } }; + onSonarCloud: boolean; } interface State { @@ -34,7 +37,6 @@ interface State { export default class DefaultPageSelector extends React.PureComponent { static contextTypes = { - currentUser: PropTypes.object.isRequired, router: PropTypes.object.isRequired }; @@ -44,22 +46,26 @@ export default class DefaultPageSelector extends React.PureComponent 0) { // show ALL projects when there are some filters this.setState({ shouldBeRedirected: false, shouldForceSorting: undefined }); - } else if (!this.context.currentUser.isLoggedIn) { + } else if (!isLoggedIn(this.props.currentUser)) { // show ALL projects if user is anonymous if (!this.props.location.query || !this.props.location.query.sort) { // force default sorting to last analysis date @@ -92,11 +98,15 @@ export default class DefaultPageSelector extends React.PureComponent; + } + const { shouldBeRedirected, shouldForceSorting } = this.state; if (shouldBeRedirected == null || shouldBeRedirected === true || shouldForceSorting != null) { return null; } else { - return ; + return ; } } } diff --git a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelectorContainer.tsx b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelectorContainer.tsx new file mode 100644 index 00000000000..d2c993f8bdc --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelectorContainer.tsx @@ -0,0 +1,38 @@ +/* + * 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 { connect } from 'react-redux'; +import DefaultPageSelector from './DefaultPageSelector'; +import { CurrentUser } from '../../../app/types'; +import { getCurrentUser, getGlobalSettingValue } from '../../../store/rootReducer'; + +interface StateProps { + currentUser: CurrentUser; + onSonarCloud: boolean; +} + +const stateToProps = (state: any) => { + const onSonarCloudSetting = getGlobalSettingValue(state, 'sonar.sonarcloud.enabled'); + return { + currentUser: getCurrentUser(state), + onSonarCloud: Boolean(onSonarCloudSetting && onSonarCloudSetting.value === 'true') + }; +}; + +export default connect(stateToProps)(DefaultPageSelector); diff --git a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx index 25c9d92209b..83ccd1a9176 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx @@ -18,22 +18,19 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as PropTypes from 'prop-types'; import { IndexLink, Link } from 'react-router'; import { translate } from '../../../helpers/l10n'; +import { CurrentUser, isLoggedIn } from '../../../app/types'; import { saveAll, saveFavorite } from '../../../helpers/storage'; import { RawQuery } from '../../../helpers/query'; interface Props { + currentUser: CurrentUser; organization?: { key: string }; query?: RawQuery; } export default class FavoriteFilter extends React.PureComponent { - static contextTypes = { - currentUser: PropTypes.object.isRequired - }; - handleSaveFavorite = () => { if (!this.props.organization) { saveFavorite(); @@ -47,7 +44,7 @@ export default class FavoriteFilter extends React.PureComponent { }; render() { - if (!this.context.currentUser.isLoggedIn) { + if (!isLoggedIn(this.props.currentUser)) { return null; } diff --git a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilterContainer.tsx b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilterContainer.tsx new file mode 100644 index 00000000000..260072166bb --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilterContainer.tsx @@ -0,0 +1,23 @@ +/* + * 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 FavoriteFilter from './FavoriteFilter'; +import { withCurrentUser } from '../../../store/withCurrentUser'; + +export default withCurrentUser(FavoriteFilter); diff --git a/server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.tsx b/server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.tsx index 1bfa07b70f2..bc104756b11 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.tsx @@ -18,8 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import AllProjects from './AllProjects'; +import AllProjectsContainer from './AllProjectsContainer'; export default function FavoriteProjectsContainer(props: any) { - return ; + return ; } diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx index c8de3ad3906..8b401d2a627 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx @@ -23,12 +23,13 @@ import SearchFilterContainer from '../filters/SearchFilterContainer'; import Tooltip from '../../../components/controls/Tooltip'; import PerspectiveSelect from './PerspectiveSelect'; import ProjectsSortingSelect from './ProjectsSortingSelect'; +import { CurrentUser, isLoggedIn } from '../../../app/types'; import { translate } from '../../../helpers/l10n'; import { RawQuery } from '../../../helpers/query'; import { Project } from '../types'; interface Props { - currentUser?: { isLoggedIn: boolean }; + currentUser: CurrentUser; isFavorite?: boolean; loading: boolean; onPerspectiveChange: (x: { view: string; visualization?: string }) => void; @@ -45,7 +46,7 @@ interface Props { export default function PageHeader(props: Props) { const { loading, total, projects, currentUser, view } = props; const limitReached = projects != null && total != null && projects.length < total; - const defaultOption = currentUser && currentUser.isLoggedIn ? 'name' : 'analysis_date'; + const defaultOption = isLoggedIn(currentUser) ? 'name' : 'analysis_date'; return (
    diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx b/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx index 6da09bf1157..52315fbefa5 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx @@ -20,8 +20,8 @@ import * as React from 'react'; import { Link } from 'react-router'; import { flatMap } from 'lodash'; -import FavoriteFilter from './FavoriteFilter'; -import LanguagesFilter from '../filters/LanguagesFilter'; +import FavoriteFilterContainer from './FavoriteFilterContainer'; +import LanguagesFilterContainer from '../filters/LanguagesFilterContainer'; import CoverageFilter from '../filters/CoverageFilter'; import DuplicationsFilter from '../filters/DuplicationsFilter'; import MaintainabilityFilter from '../filters/MaintainabilityFilter'; @@ -45,6 +45,7 @@ interface Props { isFavorite: boolean; organization?: { key: string }; query: RawQuery; + showFavoriteFilter: boolean; view: string; visualization: string; } @@ -71,7 +72,9 @@ export default function PageSidebar(props: Props) { return (
    - + {props.showFavoriteFilter && ( + + )}
    {isFiltered && ( @@ -156,7 +159,11 @@ export default function PageSidebar(props: Props) { value={query.new_lines} /> ]} - +
    ); diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx index 277a3b6eec8..76f581dd53a 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx @@ -18,56 +18,47 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as PropTypes from 'prop-types'; import { sortBy } from 'lodash'; import Tooltip from '../../../components/controls/Tooltip'; import { translate } from '../../../helpers/l10n'; - -interface Languages { - [key: string]: { key: string; name: string }; -} +import { Languages } from '../../../store/languages/reducer'; interface Props { distribution?: string; + languages: Languages; } -export default class ProjectCardLanguages extends React.PureComponent { - static contextTypes = { - languages: PropTypes.object.isRequired - }; - - render() { - if (this.props.distribution === undefined) { - return null; - } +export default function ProjectCardLanguages({ distribution, languages }: Props) { + if (distribution === undefined) { + return null; + } - const parsedLanguages = this.props.distribution.split(';').map(item => item.split('=')); - const finalLanguages = sortBy(parsedLanguages, l => -1 * Number(l[1])).map(l => - getLanguageName(this.context.languages, l[0]) - ); + const parsedLanguages = distribution.split(';').map(item => item.split('=')); + const finalLanguages = sortBy(parsedLanguages, l => -1 * Number(l[1])).map(l => + getLanguageName(languages, l[0]) + ); - const tooltip = ( - - {finalLanguages.map(language => ( - - {language} -
    -
    - ))} -
    - ); + const tooltip = ( + + {finalLanguages.map(language => ( + + {language} +
    +
    + ))} +
    + ); - const languagesText = - finalLanguages.slice(0, 2).join(', ') + (finalLanguages.length > 2 ? ', ...' : ''); + const languagesText = + finalLanguages.slice(0, 2).join(', ') + (finalLanguages.length > 2 ? ', ...' : ''); - return ( -
    - - {languagesText} - -
    - ); - } + return ( +
    + + {languagesText} + +
    + ); } function getLanguageName(languages: Languages, key: string): string { diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.tsx new file mode 100644 index 00000000000..8dbdca9f24a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.tsx @@ -0,0 +1,33 @@ +/* +* 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 { connect } from 'react-redux'; +import ProjectCardLanguages from './ProjectCardLanguages'; +import { Languages } from '../../../store/languages/reducer'; +import { getLanguages } from '../../../store/rootReducer'; + +interface StateProps { + languages: Languages; +} + +const stateToProps = (state: any) => ({ + languages: getLanguages(state) +}); + +export default connect(stateToProps)(ProjectCardLanguages); diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx index d8ce816507c..8a5b4f6d216 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx @@ -23,7 +23,7 @@ import DateFromNow from '../../../components/intl/DateFromNow'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import ProjectCardQualityGate from './ProjectCardQualityGate'; import ProjectCardLeakMeasures from './ProjectCardLeakMeasures'; -import ProjectCardOrganization from './ProjectCardOrganization'; +import ProjectCardOrganizationContainer from './ProjectCardOrganizationContainer'; import Favorite from '../../../components/controls/Favorite'; import TagsList from '../../../components/tags/TagsList'; import PrivateBadge from '../../../components/common/PrivateBadge'; @@ -52,7 +52,9 @@ export default function ProjectCardLeak({ organization, project }: Props) { /> )}

    - {!organization && } + {!organization && ( + + )} {project.name}

    {project.analysisDate && } diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganization.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganization.tsx index ce8c98ffd81..db72f6cedc4 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganization.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganization.tsx @@ -18,31 +18,22 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as PropTypes from 'prop-types'; import OrganizationLink from '../../../components/ui/OrganizationLink'; interface Props { organization?: { key: string; name: string }; + organizationsEnabled: boolean; } -export default class ProjectCardOrganization extends React.PureComponent { - static contextTypes = { - organizationsEnabled: PropTypes.bool - }; - - render() { - const { organization } = this.props; - const { organizationsEnabled } = this.context; - - if (!organization || !organizationsEnabled) { - return null; - } - - return ( - - {organization.name} - - - ); +export default function ProjectCardOrganization({ organization, organizationsEnabled }: Props) { + if (!organization || !organizationsEnabled) { + return null; } + + return ( + + {organization.name} + + + ); } diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganizationContainer.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganizationContainer.tsx new file mode 100644 index 00000000000..099ee541cba --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganizationContainer.tsx @@ -0,0 +1,32 @@ +/* +* 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 { connect } from 'react-redux'; +import ProjectCardOrganization from './ProjectCardOrganization'; +import { areThereCustomOrganizations } from '../../../store/rootReducer'; + +interface StateProps { + organizationsEnabled: boolean; +} + +const stateToProps = (state: any) => ({ + organizationsEnabled: areThereCustomOrganizations(state) +}); + +export default connect(stateToProps)(ProjectCardOrganization); diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.tsx index 7dcae1fe7a2..d8ecbe86f66 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.tsx @@ -22,7 +22,7 @@ import { Link } from 'react-router'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import ProjectCardQualityGate from './ProjectCardQualityGate'; import ProjectCardOverallMeasures from './ProjectCardOverallMeasures'; -import ProjectCardOrganization from './ProjectCardOrganization'; +import ProjectCardOrganizationContainer from './ProjectCardOrganizationContainer'; import Favorite from '../../../components/controls/Favorite'; import TagsList from '../../../components/tags/TagsList'; import PrivateBadge from '../../../components/common/PrivateBadge'; @@ -51,7 +51,9 @@ export default function ProjectCardOverall({ organization, project }: Props) { /> )}

    - {!organization && } + {!organization && ( + + )} {project.name}

    {project.analysisDate && } diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx index 5bba45518c1..0fd1b4048ef 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import ProjectCardLanguages from './ProjectCardLanguages'; +import ProjectCardLanguagesContainer from './ProjectCardLanguagesContainer'; import Measure from '../../../components/measure/Measure'; import Rating from '../../../components/ui/Rating'; import CoverageRating from '../../../components/ui/CoverageRating'; @@ -152,7 +152,9 @@ export default function ProjectCardOverallMeasures({ measures }: Props) {
    - +
    diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx index 8a12762240d..f3a02bcc1a7 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx @@ -20,7 +20,7 @@ /* eslint-disable import/order */ import * as React from 'react'; import { mount, shallow } from 'enzyme'; -import AllProjects from '../AllProjects'; +import AllProjects, { Props } from '../AllProjects'; import { getView, saveSort, saveView, saveVisualization } from '../../../../helpers/storage'; jest.mock('../ProjectsList', () => ({ @@ -168,19 +168,31 @@ it('changes perspective to risk visualization', () => { function mountRender(props: any = {}, push: Function = jest.fn(), replace: Function = jest.fn()) { return mount( , - { context: { currentUser: { isLoggedIn: true }, router: { push, replace } } } + { context: { router: { push, replace } } } ); } -function shallowRender(props: any = {}, push: Function = jest.fn(), replace: Function = jest.fn()) { +function shallowRender( + props: Partial = {}, + push: Function = jest.fn(), + replace: Function = jest.fn() +) { const wrapper = shallow( - , - { context: { currentUser: { isLoggedIn: true }, router: { push, replace } } } + , + { context: { router: { push, replace } } } ); wrapper.setState({ loading: false, diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx index b5d80b02f7e..19e0932dd3a 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx @@ -18,9 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* eslint-disable import/first, import/order */ -jest.mock('../AllProjects', () => ({ +jest.mock('../AllProjectsContainer', () => ({ // eslint-disable-next-line - default: function AllProjects() { + default: function AllProjectsContainer() { return null; } })); @@ -37,6 +37,7 @@ jest.mock('../../../../api/components', () => ({ import * as React from 'react'; import { mount } from 'enzyme'; import DefaultPageSelector from '../DefaultPageSelector'; +import { CurrentUser } from '../../../../app/types'; import { doAsync } from '../../../../helpers/testUtils'; const isFavoriteSet = require('../../../../helpers/storage').isFavoriteSet as jest.Mock; @@ -84,8 +85,17 @@ it('fetches favorites', () => { }); }); -function mountRender(user: any = { isLoggedIn: true }, query: any = {}, replace: any = jest.fn()) { - return mount(, { - context: { currentUser: user, router: { replace } } - }); +function mountRender( + currentUser: CurrentUser = { isLoggedIn: true }, + query: any = {}, + replace: any = jest.fn() +) { + return mount( + , + { context: { router: { replace } } } + ); } diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx index 5b235e0a193..3f90ede583f 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx @@ -38,11 +38,11 @@ beforeEach(() => { }); it('renders for logged in user', () => { - expect(shallow(, { context: { currentUser } })).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); }); it('saves last selection', () => { - const wrapper = shallow(, { context: { currentUser } }); + const wrapper = shallow(); click(wrapper.find('#favorite-projects')); expect(saveFavorite).toBeCalled(); click(wrapper.find('#all-projects')); @@ -51,16 +51,16 @@ it('saves last selection', () => { it('handles organization', () => { expect( - shallow(, { - context: { currentUser } - }) + shallow( + + ) ).toMatchSnapshot(); }); it('does not save last selection with organization', () => { - const wrapper = shallow(, { - context: { currentUser } - }); + const wrapper = shallow( + + ); click(wrapper.find('#favorite-projects')); expect(saveFavorite).not.toBeCalled(); click(wrapper.find('#all-projects')); @@ -69,8 +69,6 @@ it('does not save last selection with organization', () => { it('does not render for anonymous', () => { expect( - shallow(, { - context: { currentUser: { isLoggedIn: false } } - }).type() + shallow().type() ).toBeNull(); }); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.tsx index 86ef27cc3a5..279297daf13 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.tsx @@ -70,6 +70,7 @@ it('should render switch the default sorting option for anonymous users', () => function shallowRender(props?: {}) { return shallow( { const sidebar = shallow( - + ); expect(sidebar).toMatchSnapshot(); }); it('should render `leak` view correctly', () => { const sidebar = shallow( - + ); expect(sidebar).toMatchSnapshot(); }); @@ -38,10 +50,11 @@ it('should render `leak` view correctly', () => { it('reset function should work correctly with view and visualizations', () => { const sidebar = shallow( ); expect(sidebar.find('.projects-facets-reset').exists()).toBeFalsy(); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx index c81dffce5e4..ec986854e76 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx @@ -28,26 +28,26 @@ const languages = { it('renders', () => { expect( - shallow(, { context: { languages } }) + shallow() ).toMatchSnapshot(); }); it('sorts languages', () => { expect( - shallow(, { context: { languages } }) + shallow() ).toMatchSnapshot(); }); it('handles unknown languages', () => { expect( - shallow(, { context: { languages } }) + shallow() ).toMatchSnapshot(); expect( - shallow(, { context: { languages } }) + shallow() ).toMatchSnapshot(); }); it('does not render', () => { - expect(shallow(, { context: { languages } }).type()).toBeNull(); + expect(shallow().type()).toBeNull(); }); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap index ef7225c06eb..14e6af9b4e5 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap @@ -3,6 +3,7 @@ exports[`renders 1`] = `
    @@ -174,6 +176,7 @@ exports[`renders 1`] = ` exports[`renders 2`] = `
    diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.tsx.snap index 78961fde19f..cabe4555b86 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.tsx.snap @@ -25,7 +25,7 @@ exports[`reset function should work correctly with view and visualizations 1`] = exports[`should render \`leak\` view correctly 1`] = `
    - - - +
    @@ -210,7 +210,7 @@ exports[`should render correctly 1`] = ` } value="3" /> - - - - +
    @@ -320,7 +320,7 @@ exports[`should render ncloc correctly 1`] = `
    - +
    diff --git a/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.tsx index 385425c48f6..fce64b5de4c 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.tsx +++ b/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.tsx @@ -18,19 +18,19 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as PropTypes from 'prop-types'; import { difference, sortBy } from 'lodash'; import Filter from './Filter'; import FilterHeader from './FilterHeader'; import SearchableFilterFooter from './SearchableFilterFooter'; import SearchableFilterOption from './SearchableFilterOption'; -import { getLanguageByKey } from '../../../store/languages/reducer'; +import { getLanguageByKey, Languages } from '../../../store/languages/reducer'; import { translate } from '../../../helpers/l10n'; import { Facet } from '../types'; interface Props { facet?: Facet; isFavorite?: boolean; + languages: Languages; maxFacetValue?: number; organization?: { key: string }; property?: string; @@ -41,18 +41,14 @@ interface Props { const LIST_SIZE = 10; export default class LanguagesFilter extends React.Component { - static contextTypes = { - languages: PropTypes.object.isRequired - }; - getSearchOptions = () => { - let languageKeys = Object.keys(this.context.languages); + let languageKeys = Object.keys(this.props.languages); if (this.props.facet) { languageKeys = difference(languageKeys, Object.keys(this.props.facet)); } return languageKeys .slice(0, LIST_SIZE) - .map(key => ({ label: this.context.languages[key].name, value: key })); + .map(key => ({ label: this.props.languages[key].name, value: key })); }; getSortedOptions = (facet: Facet = {}) => @@ -63,7 +59,7 @@ export default class LanguagesFilter extends React.Component { renderOption = (option: string) => ( ); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.tsx b/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.tsx new file mode 100644 index 00000000000..f6de2415a99 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.tsx @@ -0,0 +1,33 @@ +/* +* 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 { connect } from 'react-redux'; +import LanguagesFilter from './LanguagesFilter'; +import { Languages } from '../../../store/languages/reducer'; +import { getLanguages } from '../../../store/rootReducer'; + +interface StateProps { + languages: Languages; +} + +const stateToProps = (state: any) => ({ + languages: getLanguages(state) +}); + +export default connect(stateToProps)(LanguagesFilter); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguagesFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguagesFilter-test.tsx index 27ea25d17dd..ff0bf4f8b37 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguagesFilter-test.tsx +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguagesFilter-test.tsx @@ -33,21 +33,21 @@ const languages = { const languagesFacet = { java: 39, cs: 4, js: 1 }; it('should render the languages without the ones in the facet', () => { - const wrapper = shallow(, { - context: { languages } - }); + const wrapper = shallow( + + ); expect(wrapper).toMatchSnapshot(); }); it('should render the languages facet with the selected languages', () => { const wrapper = shallow( , - { context: { languages } } + languages={languages} + query={{ languages: ['java', 'cs'] }} + value={['java', 'cs']} + /> ); expect(wrapper).toMatchSnapshot(); expect(wrapper.find('Filter').shallow()).toMatchSnapshot(); @@ -68,12 +68,12 @@ it('should render maximum 10 languages in the searchbox results', () => { }; const wrapper = shallow( , - { context: { languages: manyLanguages } } + languages={manyLanguages} + query={{ languages: ['java', 'g'] }} + value={['java', 'g']} + /> ); expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/projects/routes.ts b/server/sonar-web/src/main/js/apps/projects/routes.ts index c6f3df309f5..8d260c75965 100644 --- a/server/sonar-web/src/main/js/apps/projects/routes.ts +++ b/server/sonar-web/src/main/js/apps/projects/routes.ts @@ -17,37 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { RouterState, IndexRouteProps, RouteComponent, RedirectFunction } from 'react-router'; +import { RouterState, RedirectFunction } from 'react-router'; +import DefaultPageSelectorContainer from './components/DefaultPageSelectorContainer'; +import FavoriteProjectsContainer from './components/FavoriteProjectsContainer'; import { saveAll } from '../../helpers/storage'; const routes = [ + { indexRoute: { component: DefaultPageSelectorContainer } }, { - getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { - import('./components/App').then(i => callback(null, i.default)); - }, - childRoutes: [ - { - getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { - import('./components/DefaultPageSelector').then(i => - callback(null, { component: i.default }) - ); - } - }, - { - path: 'all', - onEnter(_: RouterState, replace: RedirectFunction) { - saveAll(); - replace('/projects'); - } - }, - { - path: 'favorite', - getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { - import('./components/FavoriteProjectsContainer').then(i => callback(null, i.default)); - } - } - ] - } + path: 'all', + onEnter(_: RouterState, replace: RedirectFunction) { + saveAll(); + replace('/projects'); + } + }, + { path: 'favorite', component: FavoriteProjectsContainer } ]; export default routes; diff --git a/server/sonar-web/src/main/js/components/lazyLoad.tsx b/server/sonar-web/src/main/js/components/lazyLoad.tsx new file mode 100644 index 00000000000..dfc83a1e939 --- /dev/null +++ b/server/sonar-web/src/main/js/components/lazyLoad.tsx @@ -0,0 +1,62 @@ +/* + * 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'; + +interface Loader { + (): Promise<{ default: React.ComponentClass }>; +} + +export function lazyLoad(loader: Loader) { + interface State { + Component?: React.ComponentClass; + } + + // use `React.Component`, not `React.PureComponent` to always re-render + // and let the child component decide if it needs to change + return class LazyLoader extends React.Component { + mounted: boolean; + state: State = {}; + + componentDidMount() { + this.mounted = true; + loader().then(i => this.receiveComponent(i.default), () => {}); + } + + componentWillUnmount() { + this.mounted = false; + } + + receiveComponent = (Component: React.ComponentClass) => { + if (this.mounted) { + this.setState({ Component }); + } + }; + + render() { + const { Component } = this.state; + + if (!Component) { + return null; + } + + return ; + } + }; +} diff --git a/server/sonar-web/src/main/js/store/languages/reducer.js b/server/sonar-web/src/main/js/store/languages/reducer.ts similarity index 79% rename from server/sonar-web/src/main/js/store/languages/reducer.js rename to server/sonar-web/src/main/js/store/languages/reducer.ts index 52177241e61..a137ef7c0b5 100644 --- a/server/sonar-web/src/main/js/store/languages/reducer.js +++ b/server/sonar-web/src/main/js/store/languages/reducer.ts @@ -20,7 +20,11 @@ import { keyBy } from 'lodash'; import { RECEIVE_LANGUAGES } from './actions'; -const reducer = (state = {}, action = {}) => { +export interface Languages { + [key: string]: { key: string; name: string }; +} + +const reducer = (state: Languages = {}, action: any = {}) => { if (action.type === RECEIVE_LANGUAGES) { return keyBy(action.languages, 'key'); } @@ -30,6 +34,6 @@ const reducer = (state = {}, action = {}) => { export default reducer; -export const getLanguages = state => state; +export const getLanguages = (state: Languages) => state; -export const getLanguageByKey = (state, key) => state[key]; +export const getLanguageByKey = (state: Languages, key: string) => state[key]; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.tsx b/server/sonar-web/src/main/js/store/withCurrentUser.tsx similarity index 60% rename from server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.tsx rename to server/sonar-web/src/main/js/store/withCurrentUser.tsx index d2413620662..1a2d815e5a8 100644 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.tsx +++ b/server/sonar-web/src/main/js/store/withCurrentUser.tsx @@ -1,7 +1,7 @@ /* * SonarQube * Copyright (C) 2009-2017 SonarSource SA - * mailto:info AT sonarsource DOT com + * 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 @@ -17,25 +17,19 @@ * 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 * as React from 'react'; -import App from '../../projects/components/App'; -import AllProjects from '../../projects/components/AllProjects'; +import { getCurrentUser } from './rootReducer'; +import { CurrentUser } from '../app/types'; -interface Props { - location: { pathname: string; query: { [x: string]: string } }; - organization: { key: string }; +interface StateProps { + currentUser: CurrentUser; } -export default function OrganizationFavoriteProjects(props: Props) { - return ( -
    - - - -
    - ); +export function withCurrentUser

    (Component: React.ComponentClass

    ) { + function mapStateToProps(state: any): StateProps { + return { currentUser: getCurrentUser(state) }; + } + + return connect(mapStateToProps)(Component); } diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index f12b6c3bcb0..671127dca10 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -97,6 +97,7 @@ more_x={0} more more_actions=More Actions my_favorite=My Favorite my_favorites=My Favorites +my_projects=My Projects name=Name navigation=Navigation never=Never -- 2.39.5