瀏覽代碼

SONAR-10080 turn Projects to My Projects

tags/7.0-RC1
Stas Vilchik 6 年之前
父節點
當前提交
b5825706ab
共有 40 個檔案被更改,包括 534 行新增308 行删除
  1. 3
    2
      server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js
  2. 16
    0
      server/sonar-web/src/main/js/app/types.ts
  3. 6
    11
      server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx
  4. 1
    12
      server/sonar-web/src/main/js/apps/organizations/routes.js
  5. 22
    7
      server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
  6. 44
    0
      server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx
  7. 0
    75
      server/sonar-web/src/main/js/apps/projects/components/App.tsx
  8. 27
    17
      server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx
  9. 38
    0
      server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelectorContainer.tsx
  10. 3
    6
      server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx
  11. 23
    0
      server/sonar-web/src/main/js/apps/projects/components/FavoriteFilterContainer.tsx
  12. 2
    2
      server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.tsx
  13. 3
    2
      server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx
  14. 11
    4
      server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx
  15. 29
    38
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx
  16. 33
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.tsx
  17. 4
    2
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx
  18. 11
    20
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganization.tsx
  19. 32
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganizationContainer.tsx
  20. 4
    2
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.tsx
  21. 4
    2
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx
  22. 17
    5
      server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
  23. 16
    6
      server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx
  24. 9
    11
      server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx
  25. 1
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.tsx
  26. 16
    3
      server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx
  27. 5
    5
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx
  28. 4
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap
  29. 4
    4
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.tsx.snap
  30. 1
    1
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.tsx.snap
  31. 1
    1
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.tsx.snap
  32. 2
    2
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap
  33. 5
    9
      server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.tsx
  34. 33
    0
      server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.tsx
  35. 11
    11
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguagesFilter-test.tsx
  36. 11
    27
      server/sonar-web/src/main/js/apps/projects/routes.ts
  37. 62
    0
      server/sonar-web/src/main/js/components/lazyLoad.tsx
  38. 7
    3
      server/sonar-web/src/main/js/store/languages/reducer.ts
  39. 12
    18
      server/sonar-web/src/main/js/store/withCurrentUser.tsx
  40. 1
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 3
- 2
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 (
<li>
<Link to="/projects" activeClassName="active">
{translate('projects.page')}
{this.props.sonarCloud ? translate('my_projects') : translate('projects.page')}
</Link>
</li>
);

+ 16
- 0
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;
}

+ 6
- 11
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 (
<div id="projects-page">
<App>
<AllProjects
isFavorite={false}
location={props.location}
organization={props.organization}
/>
</App>
</div>
<AllProjectsContainer
isFavorite={false}
location={props.location}
organization={props.organization}
/>
);
}

+ 1
- 12
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',

+ 22
- 7
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<Props, State> {
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<Props, State> {

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<Props, State> {

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<Props, State> {
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<Props, State> {
<div className="layout-page-header-panel-inner layout-page-main-header-inner">
<div className="layout-page-main-inner">
<PageHeader
currentUser={this.context.currentUser}
currentUser={this.props.currentUser}
isFavorite={this.props.isFavorite}
loading={this.state.loading}
onPerspectiveChange={this.handlePerspectiveChange}
@@ -268,7 +283,7 @@ export default class AllProjects extends React.PureComponent<Props, State> {
<div className="layout-page-main-inner">
{this.state.projects && (
<Visualizations
displayOrganizations={!this.props.organization && !!this.context.organizationsEnabled}
displayOrganizations={!this.props.organization && this.props.organizationsEnabled}
projects={this.state.projects}
sort={this.state.query.sort}
total={this.state.total}
@@ -299,7 +314,7 @@ export default class AllProjects extends React.PureComponent<Props, State> {

render() {
return (
<div className="layout-page projects-page">
<div className="layout-page projects-page" id="projects-page">
<Helmet title={translate('projects.page')} />

{this.renderSide()}

+ 44
- 0
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<StateProps, any, any>(stateToProps)(lazyLoad(() => import('./AllProjects')));

+ 0
- 75
server/sonar-web/src/main/js/apps/projects/components/App.tsx 查看文件

@@ -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<Props> {
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 <div id="projects-page">{this.props.children}</div>;
}
}

const mapStateToProps = (state: any) => ({
currentUser: getCurrentUser(state),
languages: getLanguages(state),
organizationsEnabled: areThereCustomOrganizations(state)
});

export default connect<any, any, any>(mapStateToProps)(App);

+ 27
- 17
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<Props, State> {
static contextTypes = {
currentUser: PropTypes.object.isRequired,
router: PropTypes.object.isRequired
};

@@ -44,22 +46,26 @@ export default class DefaultPageSelector extends React.PureComponent<Props, Stat
}

componentDidMount() {
this.defineIfShouldBeRedirected();
if (!this.props.onSonarCloud) {
this.defineIfShouldBeRedirected();
}
}

componentDidUpdate(prevProps: Props) {
if (prevProps.location !== this.props.location) {
this.defineIfShouldBeRedirected();
} else if (this.state.shouldBeRedirected === true) {
this.context.router.replace({ ...this.props.location, pathname: '/projects/favorite' });
} else if (this.state.shouldForceSorting != null) {
this.context.router.replace({
...this.props.location,
query: {
...this.props.location.query,
sort: this.state.shouldForceSorting
}
});
if (!this.props.onSonarCloud) {
if (prevProps.location !== this.props.location) {
this.defineIfShouldBeRedirected();
} else if (this.state.shouldBeRedirected === true) {
this.context.router.replace({ ...this.props.location, pathname: '/projects/favorite' });
} else if (this.state.shouldForceSorting != null) {
this.context.router.replace({
...this.props.location,
query: {
...this.props.location.query,
sort: this.state.shouldForceSorting
}
});
}
}
}

@@ -67,7 +73,7 @@ export default class DefaultPageSelector extends React.PureComponent<Props, Stat
if (Object.keys(this.props.location.query).length > 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<Props, Stat
}

render() {
if (this.props.onSonarCloud) {
return <AllProjectsContainer isFavorite={true} location={this.props.location} />;
}

const { shouldBeRedirected, shouldForceSorting } = this.state;
if (shouldBeRedirected == null || shouldBeRedirected === true || shouldForceSorting != null) {
return null;
} else {
return <AllProjects isFavorite={false} location={this.props.location} />;
return <AllProjectsContainer isFavorite={false} location={this.props.location} />;
}
}
}

+ 38
- 0
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<StateProps>(stateToProps)(DefaultPageSelector);

+ 3
- 6
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<Props> {
static contextTypes = {
currentUser: PropTypes.object.isRequired
};

handleSaveFavorite = () => {
if (!this.props.organization) {
saveFavorite();
@@ -47,7 +44,7 @@ export default class FavoriteFilter extends React.PureComponent<Props> {
};

render() {
if (!this.context.currentUser.isLoggedIn) {
if (!isLoggedIn(this.props.currentUser)) {
return null;
}


+ 23
- 0
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);

+ 2
- 2
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 <AllProjects isFavorite={true} {...props} />;
return <AllProjectsContainer isFavorite={true} {...props} />;
}

+ 3
- 2
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 (
<header className="page-header projects-topbar-items">

+ 11
- 4
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 (
<div>
<FavoriteFilter query={linkQuery} organization={organization} />
{props.showFavoriteFilter && (
<FavoriteFilterContainer query={linkQuery} organization={organization} />
)}

<div className="projects-facets-header clearfix">
{isFiltered && (
@@ -156,7 +159,11 @@ export default function PageSidebar(props: Props) {
value={query.new_lines}
/>
]}
<LanguagesFilter {...facetProps} facet={facets && facets.languages} value={query.languages} />
<LanguagesFilterContainer
{...facetProps}
facet={facets && facets.languages}
value={query.languages}
/>
<TagsFilter {...facetProps} facet={facets && facets.tags} value={query.tags} />
</div>
);

+ 29
- 38
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<Props> {
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 = (
<span>
{finalLanguages.map(language => (
<span key={language}>
{language}
<br />
</span>
))}
</span>
);
const tooltip = (
<span>
{finalLanguages.map(language => (
<span key={language}>
{language}
<br />
</span>
))}
</span>
);

const languagesText =
finalLanguages.slice(0, 2).join(', ') + (finalLanguages.length > 2 ? ', ...' : '');
const languagesText =
finalLanguages.slice(0, 2).join(', ') + (finalLanguages.length > 2 ? ', ...' : '');

return (
<div className="project-card-languages">
<Tooltip placement="bottom" overlay={tooltip}>
<span>{languagesText}</span>
</Tooltip>
</div>
);
}
return (
<div className="project-card-languages">
<Tooltip placement="bottom" overlay={tooltip}>
<span>{languagesText}</span>
</Tooltip>
</div>
);
}

function getLanguageName(languages: Languages, key: string): string {

+ 33
- 0
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<StateProps>(stateToProps)(ProjectCardLanguages);

+ 4
- 2
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) {
/>
)}
<h2 className="project-card-name">
{!organization && <ProjectCardOrganization organization={project.organization} />}
{!organization && (
<ProjectCardOrganizationContainer organization={project.organization} />
)}
<Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link>
</h2>
{project.analysisDate && <ProjectCardQualityGate status={measures!['alert_status']} />}

+ 11
- 20
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<Props> {
static contextTypes = {
organizationsEnabled: PropTypes.bool
};

render() {
const { organization } = this.props;
const { organizationsEnabled } = this.context;

if (!organization || !organizationsEnabled) {
return null;
}

return (
<span className="text-normal">
<OrganizationLink organization={organization}>{organization.name}</OrganizationLink>
<span className="slash-separator" />
</span>
);
export default function ProjectCardOrganization({ organization, organizationsEnabled }: Props) {
if (!organization || !organizationsEnabled) {
return null;
}

return (
<span className="text-normal">
<OrganizationLink organization={organization}>{organization.name}</OrganizationLink>
<span className="slash-separator" />
</span>
);
}

+ 32
- 0
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<StateProps>(stateToProps)(ProjectCardOrganization);

+ 4
- 2
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) {
/>
)}
<h2 className="project-card-name">
{!organization && <ProjectCardOrganization organization={project.organization} />}
{!organization && (
<ProjectCardOrganizationContainer organization={project.organization} />
)}
<Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link>
</h2>
{project.analysisDate && <ProjectCardQualityGate status={measures['alert_status']} />}

+ 4
- 2
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) {
</span>
</div>
<div className="project-card-measure-label">
<ProjectCardLanguages distribution={measures['ncloc_language_distribution']} />
<ProjectCardLanguagesContainer
distribution={measures['ncloc_language_distribution']}
/>
</div>
</div>
</div>

+ 17
- 5
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(
<AllProjects
currentUser={{ isLoggedIn: true }}
fetchProjects={jest.fn()}
isFavorite={false}
location={{ pathname: '/projects', query: {} }}
{...props}
/>,
{ 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<Props> = {},
push: Function = jest.fn(),
replace: Function = jest.fn()
) {
const wrapper = shallow(
<AllProjects isFavorite={false} location={{ pathname: '/projects', query: {} }} {...props} />,
{ context: { currentUser: { isLoggedIn: true }, router: { push, replace } } }
<AllProjects
currentUser={{ isLoggedIn: true }}
isFavorite={false}
location={{ pathname: '/projects', query: {} }}
onSonarCloud={false}
organizationsEnabled={false}
{...props}
/>,
{ context: { router: { push, replace } } }
);
wrapper.setState({
loading: false,

+ 16
- 6
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<any>;
@@ -84,8 +85,17 @@ it('fetches favorites', () => {
});
});

function mountRender(user: any = { isLoggedIn: true }, query: any = {}, replace: any = jest.fn()) {
return mount(<DefaultPageSelector location={{ pathname: '/projects', query }} />, {
context: { currentUser: user, router: { replace } }
});
function mountRender(
currentUser: CurrentUser = { isLoggedIn: true },
query: any = {},
replace: any = jest.fn()
) {
return mount(
<DefaultPageSelector
currentUser={currentUser}
location={{ pathname: '/projects', query }}
onSonarCloud={false}
/>,
{ context: { router: { replace } } }
);
}

+ 9
- 11
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(<FavoriteFilter query={query} />, { context: { currentUser } })).toMatchSnapshot();
expect(shallow(<FavoriteFilter currentUser={currentUser} query={query} />)).toMatchSnapshot();
});

it('saves last selection', () => {
const wrapper = shallow(<FavoriteFilter query={query} />, { context: { currentUser } });
const wrapper = shallow(<FavoriteFilter currentUser={currentUser} query={query} />);
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(<FavoriteFilter organization={{ key: 'org' }} query={query} />, {
context: { currentUser }
})
shallow(
<FavoriteFilter currentUser={currentUser} organization={{ key: 'org' }} query={query} />
)
).toMatchSnapshot();
});

it('does not save last selection with organization', () => {
const wrapper = shallow(<FavoriteFilter organization={{ key: 'org' }} query={query} />, {
context: { currentUser }
});
const wrapper = shallow(
<FavoriteFilter currentUser={currentUser} organization={{ key: 'org' }} query={query} />
);
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(<FavoriteFilter query={query} />, {
context: { currentUser: { isLoggedIn: false } }
}).type()
shallow(<FavoriteFilter currentUser={{ isLoggedIn: false }} query={query} />).type()
).toBeNull();
});

+ 1
- 0
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(
<PageHeader
currentUser={{ isLoggedIn: false }}
loading={false}
onPerspectiveChange={jest.fn()}
onSortChange={jest.fn()}

+ 16
- 3
server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx 查看文件

@@ -23,14 +23,26 @@ import PageSidebar from '../PageSidebar';

it('should render correctly', () => {
const sidebar = shallow(
<PageSidebar query={{ size: '3' }} view="overall" visualization="risk" isFavorite={true} />
<PageSidebar
isFavorite={true}
query={{ size: '3' }}
showFavoriteFilter={true}
view="overall"
visualization="risk"
/>
);
expect(sidebar).toMatchSnapshot();
});

it('should render `leak` view correctly', () => {
const sidebar = shallow(
<PageSidebar query={{ view: 'leak' }} view="leak" visualization="risk" isFavorite={false} />
<PageSidebar
isFavorite={false}
query={{ view: 'leak' }}
showFavoriteFilter={true}
view="leak"
visualization="risk"
/>
);
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(
<PageSidebar
isFavorite={false}
query={{ view: 'visualizations', visualization: 'bugs' }}
showFavoriteFilter={true}
view="visualizations"
visualization="bugs"
isFavorite={false}
/>
);
expect(sidebar.find('.projects-facets-reset').exists()).toBeFalsy();

+ 5
- 5
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx 查看文件

@@ -28,26 +28,26 @@ const languages = {

it('renders', () => {
expect(
shallow(<ProjectCardLanguages distribution="java=137;js=15" />, { context: { languages } })
shallow(<ProjectCardLanguages distribution="java=137;js=15" languages={languages} />)
).toMatchSnapshot();
});

it('sorts languages', () => {
expect(
shallow(<ProjectCardLanguages distribution="java=13;js=152" />, { context: { languages } })
shallow(<ProjectCardLanguages distribution="java=13;js=152" languages={languages} />)
).toMatchSnapshot();
});

it('handles unknown languages', () => {
expect(
shallow(<ProjectCardLanguages distribution="java=13;cpp=18" />, { context: { languages } })
shallow(<ProjectCardLanguages distribution="java=13;cpp=18" languages={languages} />)
).toMatchSnapshot();

expect(
shallow(<ProjectCardLanguages distribution="java=13;<null>=18" />, { context: { languages } })
shallow(<ProjectCardLanguages distribution="java=13;<null>=18" languages={languages} />)
).toMatchSnapshot();
});

it('does not render', () => {
expect(shallow(<ProjectCardLanguages />, { context: { languages } }).type()).toBeNull();
expect(shallow(<ProjectCardLanguages languages={languages} />).type()).toBeNull();
});

+ 4
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap 查看文件

@@ -3,6 +3,7 @@
exports[`renders 1`] = `
<div
className="layout-page projects-page"
id="projects-page"
>
<HelmetWrapper
defer={true}
@@ -51,6 +52,7 @@ exports[`renders 1`] = `
"visualization": undefined,
}
}
showFavoriteFilter={true}
view="overall"
visualization="risk"
/>
@@ -174,6 +176,7 @@ exports[`renders 1`] = `
exports[`renders 2`] = `
<div
className="layout-page projects-page"
id="projects-page"
>
<HelmetWrapper
defer={true}
@@ -204,6 +207,7 @@ exports[`renders 2`] = `
"view": "visualizations",
}
}
showFavoriteFilter={true}
view="visualizations"
visualization="risk"
/>

+ 4
- 4
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`] = `
<div>
<FavoriteFilter
<Connect(FavoriteFilter)
query={
Object {
"view": "leak",
@@ -101,7 +101,7 @@ exports[`should render \`leak\` view correctly 1`] = `
}
}
/>
<LanguagesFilter
<Connect(LanguagesFilter)
isFavorite={false}
query={
Object {
@@ -122,7 +122,7 @@ exports[`should render \`leak\` view correctly 1`] = `

exports[`should render correctly 1`] = `
<div>
<FavoriteFilter />
<Connect(FavoriteFilter) />
<div
className="projects-facets-header clearfix"
>
@@ -210,7 +210,7 @@ exports[`should render correctly 1`] = `
}
value="3"
/>
<LanguagesFilter
<Connect(LanguagesFilter)
isFavorite={true}
query={
Object {

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.tsx.snap 查看文件

@@ -11,7 +11,7 @@ exports[`should display the leak measures and quality gate 1`] = `
<h2
className="project-card-name"
>
<ProjectCardOrganization
<Connect(ProjectCardOrganization)
organization={
Object {
"key": "org",

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.tsx.snap 查看文件

@@ -11,7 +11,7 @@ exports[`should display the overall measures and quality gate 1`] = `
<h2
className="project-card-name"
>
<ProjectCardOrganization
<Connect(ProjectCardOrganization)
organization={
Object {
"key": "org",

+ 2
- 2
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap 查看文件

@@ -280,7 +280,7 @@ exports[`should render correctly with all data 1`] = `
<div
className="project-card-measure-label"
>
<ProjectCardLanguages />
<Connect(ProjectCardLanguages) />
</div>
</div>
</div>
@@ -320,7 +320,7 @@ exports[`should render ncloc correctly 1`] = `
<div
className="project-card-measure-label"
>
<ProjectCardLanguages />
<Connect(ProjectCardLanguages) />
</div>
</div>
</div>

+ 5
- 9
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<Props> {
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<Props> {
renderOption = (option: string) => (
<SearchableFilterOption
optionKey={option}
option={getLanguageByKey(this.context.languages, option)}
option={getLanguageByKey(this.props.languages, option)}
/>
);


+ 33
- 0
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<StateProps>(stateToProps)(LanguagesFilter);

+ 11
- 11
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(<LanguagesFilter query={{ languages: null }} facet={languagesFacet} />, {
context: { languages }
});
const wrapper = shallow(
<LanguagesFilter facet={languagesFacet} languages={languages} query={{ languages: null }} />
);
expect(wrapper).toMatchSnapshot();
});

it('should render the languages facet with the selected languages', () => {
const wrapper = shallow(
<LanguagesFilter
query={{ languages: ['java', 'cs'] }}
value={['java', 'cs']}
facet={languagesFacet}
isFavorite={true}
/>,
{ 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(
<LanguagesFilter
query={{ languages: ['java', 'g'] }}
value={['java', 'g']}
facet={{ ...languagesFacet, g: 1 }}
isFavorite={true}
/>,
{ context: { languages: manyLanguages } }
languages={manyLanguages}
query={{ languages: ['java', 'g'] }}
value={['java', 'g']}
/>
);
expect(wrapper).toMatchSnapshot();
});

+ 11
- 27
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;

+ 62
- 0
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<any, State> {
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 <Component {...this.props} />;
}
};
}

server/sonar-web/src/main/js/store/languages/reducer.js → 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];

server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.tsx → 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 (
<div id="projects-page">
<App>
<AllProjects
isFavorite={true}
location={props.location}
organization={props.organization}
/>
</App>
</div>
);
export function withCurrentUser<P extends StateProps>(Component: React.ComponentClass<P>) {
function mapStateToProps(state: any): StateProps {
return { currentUser: getCurrentUser(state) };
}

return connect<StateProps>(mapStateToProps)(Component);
}

+ 1
- 0
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

Loading…
取消
儲存