aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2017-11-23 16:05:14 +0100
committerStas Vilchik <stas.vilchik@sonarsource.com>2017-12-11 18:00:33 +0100
commitb5825706ab56c92dfae7e12d91af4891e363e03b (patch)
tree3711cb5e469be80d887c749564667b3517ed8a53 /server
parent36685ff3e20875421162206c34aabb31d5b21fdb (diff)
downloadsonarqube-b5825706ab56c92dfae7e12d91af4891e363e03b.tar.gz
sonarqube-b5825706ab56c92dfae7e12d91af4891e363e03b.zip
SONAR-10080 turn Projects to My Projects
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.js5
-rw-r--r--server/sonar-web/src/main/js/app/types.ts16
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/routes.js13
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx29
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.tsx44
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/App.tsx75
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx44
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelectorContainer.tsx38
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/FavoriteFilterContainer.tsx23
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx67
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.tsx33
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganization.tsx31
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganizationContainer.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx22
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx22
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx20
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx19
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.tsx33
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguagesFilter-test.tsx22
-rw-r--r--server/sonar-web/src/main/js/apps/projects/routes.ts38
-rw-r--r--server/sonar-web/src/main/js/components/lazyLoad.tsx62
-rw-r--r--server/sonar-web/src/main/js/store/languages/reducer.ts (renamed from server/sonar-web/src/main/js/store/languages/reducer.js)10
-rw-r--r--server/sonar-web/src/main/js/store/withCurrentUser.tsx (renamed from server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.tsx)30
39 files changed, 533 insertions, 308 deletions
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 (
<li>
<Link to="/projects" activeClassName="active">
- {translate('projects.page')}
+ {this.props.sonarCloud ? translate('my_projects') : translate('projects.page')}
</Link>
</li>
);
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 (
- <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}
+ />
);
}
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<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()}
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<StateProps, any, any>(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<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);
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<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} />;
}
}
}
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<StateProps>(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<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;
}
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 <AllProjects isFavorite={true} {...props} />;
+ return <AllProjectsContainer isFavorite={true} {...props} />;
}
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 (
<header className="page-header projects-topbar-items">
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 (
<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>
);
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<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 {
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<StateProps>(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) {
/>
)}
<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']} />}
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<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>
+ );
}
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<StateProps>(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) {
/>
)}
<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']} />}
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) {
</span>
</div>
<div className="project-card-measure-label">
- <ProjectCardLanguages distribution={measures['ncloc_language_distribution']} />
+ <ProjectCardLanguagesContainer
+ distribution={measures['ncloc_language_distribution']}
+ />
</div>
</div>
</div>
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(
<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,
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<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 } } }
+ );
}
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(<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();
});
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(
<PageHeader
+ currentUser={{ isLoggedIn: false }}
loading={false}
onPerspectiveChange={jest.fn()}
onSortChange={jest.fn()}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx
index b6ba776c0da..e19492bd40e 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx
+++ b/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();
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(<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();
});
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`] = `
<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"
/>
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`] = `
<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 {
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.tsx.snap
index e293ca6efc9..e53e1df3fe9 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.tsx.snap
+++ b/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",
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.tsx.snap
index 7fdfbd65dbe..115f01c7533 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.tsx.snap
+++ b/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",
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap
index ed38e592644..74ef1f08cfe 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap
+++ b/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>
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<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)}
/>
);
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<StateProps>(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(<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();
});
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<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} />;
+ }
+ };
+}
diff --git a/server/sonar-web/src/main/js/store/languages/reducer.js b/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
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 (
- <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);
}