aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2017-08-30 18:26:07 +0200
committerStas Vilchik <stas.vilchik@sonarsource.com>2017-09-13 13:53:58 +0200
commit1ca69cefbef27f8f772650de4af6e6018ef85351 (patch)
tree1571f956340061c3dc0a326f8e023bb0ea111fc3 /server/sonar-web/src
parent361d9c3e6c2f2dbf10b1412789bed2095f25edcc (diff)
downloadsonarqube-1ca69cefbef27f8f772650de4af6e6018ef85351.tar.gz
sonarqube-1ca69cefbef27f8f772650de4af6e6018ef85351.zip
avoid using redux in projects app
Diffstat (limited to 'server/sonar-web/src')
-rw-r--r--server/sonar-web/src/main/js/api/components.ts27
-rw-r--r--server/sonar-web/src/main/js/api/measures.ts19
-rw-r--r--server/sonar-web/src/main/js/api/organizations.ts18
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.js60
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.tsx (renamed from server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.ts)29
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.js61
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx (renamed from server/sonar-web/src/main/js/apps/projects/store/stateDuck.js)39
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx137
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/App.tsx37
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/FavoriteFilterContainer.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.tsx (renamed from server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.ts)7
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx22
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx105
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCard.tsx (renamed from server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.ts)23
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.tsx54
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx62
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx36
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganization.tsx48
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.tsx35
-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/ProjectsList.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.ts29
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.ts40
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx105
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx12
-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.tsx13
-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__/ProjectCardLeak-test.tsx26
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsList-test.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap68
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.tsx.snap13
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.tsx.snap8
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.tsx.snap8
-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/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap20
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/Filter.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.ts33
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.tsx19
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.ts34
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/TagsFilterContainer.ts32
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/__tests__/IssuesFilter-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguagesFilter-test.tsx37
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/CoverageFilter-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/DuplicationsFilter-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/IssuesFilter-test.tsx.snap3
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewLinesFilter-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/QualityGateFilter-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SizeFilter-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/query.ts256
-rw-r--r--server/sonar-web/src/main/js/apps/projects/store/actions.js240
-rw-r--r--server/sonar-web/src/main/js/apps/projects/store/facetsDuck.js88
-rw-r--r--server/sonar-web/src/main/js/apps/projects/store/projectsDuck.js51
-rw-r--r--server/sonar-web/src/main/js/apps/projects/store/reducer.js34
-rw-r--r--server/sonar-web/src/main/js/apps/projects/store/utils.js271
-rw-r--r--server/sonar-web/src/main/js/apps/projects/types.ts15
-rw-r--r--server/sonar-web/src/main/js/apps/projects/utils.ts273
-rw-r--r--server/sonar-web/src/main/js/apps/projects/visualizations/Visualizations.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/visualizations/VisualizationsContainer.ts49
-rw-r--r--server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Risk-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/SimpleBubbleChart-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/components/controls/Favorite.tsx (renamed from server/sonar-web/src/main/js/components/controls/Favorite.js)35
-rw-r--r--server/sonar-web/src/main/js/components/controls/FavoriteBase.tsx (renamed from server/sonar-web/src/main/js/components/controls/FavoriteBase.js)38
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/Favorite-test.tsx (renamed from server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.ts)12
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.tsx (renamed from server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.js)14
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Favorite-test.tsx.snap9
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/FavoriteBase-test.tsx.snap (renamed from server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/FavoriteBase-test.js.snap)2
-rw-r--r--server/sonar-web/src/main/js/helpers/measures.ts2
-rw-r--r--server/sonar-web/src/main/js/store/components/actions.js32
-rw-r--r--server/sonar-web/src/main/js/store/components/reducer.js51
-rw-r--r--server/sonar-web/src/main/js/store/measures/actions.js42
-rw-r--r--server/sonar-web/src/main/js/store/measures/reducer.js67
-rw-r--r--server/sonar-web/src/main/js/store/rootReducer.js24
89 files changed, 1424 insertions, 1782 deletions
diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts
index 361713b815d..42e3b05ea6e 100644
--- a/server/sonar-web/src/main/js/api/components.ts
+++ b/server/sonar-web/src/main/js/api/components.ts
@@ -150,7 +150,32 @@ export function getMyProjects(data: RequestData): Promise<any> {
return getJSON(url, data);
}
-export function searchProjects(data: RequestData): Promise<any> {
+export interface Paging {
+ pageIndex: number;
+ pageSize: number;
+ total: number;
+}
+
+export interface Component {
+ organization: string;
+ id: string;
+ key: string;
+ name: string;
+ isFavorite?: boolean;
+ analysisDate?: string;
+ tags: string[];
+ visibility: string;
+ leakPeriodDate?: string;
+}
+
+export interface Facet {
+ property: string;
+ values: Array<{ val: string; count: number }>;
+}
+
+export function searchProjects(
+ data: RequestData
+): Promise<{ components: Component[]; facets: Facet[]; paging: Paging }> {
const url = '/api/components/search_projects';
return getJSON(url, data);
}
diff --git a/server/sonar-web/src/main/js/api/measures.ts b/server/sonar-web/src/main/js/api/measures.ts
index fd4f5259d2f..df194d04a34 100644
--- a/server/sonar-web/src/main/js/api/measures.ts
+++ b/server/sonar-web/src/main/js/api/measures.ts
@@ -38,9 +38,24 @@ export function getMeasuresAndMeta(
return getJSON('/api/measures/component', data);
}
-export function getMeasuresForProjects(projectKeys: string[], metricKeys: string[]): Promise<any> {
+export interface Period {
+ index: number;
+ value: string;
+}
+
+export interface Measure {
+ component: string;
+ metric: string;
+ periods?: Period[];
+ value?: string;
+}
+
+export function getMeasuresForProjects(
+ projectKeys: string[],
+ metricKeys: string[]
+): Promise<Measure[]> {
return getJSON('/api/measures/search', {
projectKeys: projectKeys.join(),
metricKeys: metricKeys.join()
- });
+ }).then(r => r.measures);
}
diff --git a/server/sonar-web/src/main/js/api/organizations.ts b/server/sonar-web/src/main/js/api/organizations.ts
index d16eb61e07e..408f6b65cb2 100644
--- a/server/sonar-web/src/main/js/api/organizations.ts
+++ b/server/sonar-web/src/main/js/api/organizations.ts
@@ -20,7 +20,23 @@
import { getJSON, post, postJSON, RequestData } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
-export function getOrganizations(organizations?: string[]): Promise<any> {
+interface GetOrganizationsResponse {
+ organizations: Array<{
+ avatar?: string;
+ description?: string;
+ guarded: boolean;
+ key: string;
+ name: string;
+ url?: string;
+ }>;
+ paging: {
+ pageIndex: number;
+ pageSize: number;
+ total: number;
+ };
+}
+
+export function getOrganizations(organizations?: string[]): Promise<GetOrganizationsResponse> {
const data: RequestData = {};
if (organizations) {
Object.assign(data, { organizations: organizations.join() });
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.js
deleted file mode 100644
index ef55c9023d0..00000000000
--- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.js
+++ /dev/null
@@ -1,60 +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.
- */
-// @flow
-import React from 'react';
-import FavoriteProjectsContainer from '../../projects/components/FavoriteProjectsContainer';
-
-export default class OrganizationFavoriteProjects extends React.PureComponent {
- /*:: props: {
- children?: React.Element<*>,
- currentUser: { isLoggedIn: boolean },
- location: Object,
- organization: {
- key: string
- }
- };
-*/
-
- componentDidMount() {
- const html = document.querySelector('html');
- if (html) {
- html.classList.add('dashboard-page');
- }
- }
-
- componentWillUnmount() {
- const html = document.querySelector('html');
- if (html) {
- html.classList.remove('dashboard-page');
- }
- }
-
- render() {
- return (
- <div id="projects-page">
- <FavoriteProjectsContainer
- currentUser={this.props.currentUser}
- location={this.props.location}
- organization={this.props.organization}
- />
- </div>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.ts b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.tsx
index fc3ad9992c6..d2413620662 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.ts
+++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationFavoriteProjects.tsx
@@ -17,14 +17,25 @@
* 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 AllProjects from './AllProjects';
-import { getCurrentUser } from '../../../store/rootReducer';
-import { fetchProjects } from '../store/actions';
+import * as React from 'react';
+import App from '../../projects/components/App';
+import AllProjects from '../../projects/components/AllProjects';
-const mapStateToProps = (state: any) => ({
- isFavorite: true,
- currentUser: getCurrentUser(state)
-});
+interface Props {
+ location: { pathname: string; query: { [x: string]: string } };
+ organization: { key: string };
+}
-export default connect<any, any, any>(mapStateToProps, { fetchProjects })(AllProjects);
+export default function OrganizationFavoriteProjects(props: Props) {
+ return (
+ <div id="projects-page">
+ <App>
+ <AllProjects
+ isFavorite={true}
+ location={props.location}
+ organization={props.organization}
+ />
+ </App>
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.js
deleted file mode 100644
index a9ef3fca16f..00000000000
--- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.js
+++ /dev/null
@@ -1,61 +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.
- */
-// @flow
-import React from 'react';
-import AllProjectsContainer from '../../projects/components/AllProjectsContainer';
-
-export default class OrganizationProjects extends React.PureComponent {
- /*:: props: {
- children?: React.Element<*>,
- currentUser: { isLoggedIn: boolean },
- location: Object,
- organization: {
- key: string
- }
- };
-*/
-
- componentDidMount() {
- const html = document.querySelector('html');
- if (html) {
- html.classList.add('dashboard-page');
- }
- }
-
- componentWillUnmount() {
- const html = document.querySelector('html');
- if (html) {
- html.classList.remove('dashboard-page');
- }
- }
-
- render() {
- return (
- <div id="projects-page">
- <AllProjectsContainer
- currentUser={this.props.currentUser}
- isFavorite={false}
- location={this.props.location}
- organization={this.props.organization}
- />
- </div>
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/store/stateDuck.js b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx
index 2b1e5ad297c..07f220f0d96 100644
--- a/server/sonar-web/src/main/js/apps/projects/store/stateDuck.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx
@@ -17,24 +17,25 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { createValue } from '../../../store/utils/generalReducers';
+import * as React from 'react';
+import App from '../../projects/components/App';
+import AllProjects from '../../projects/components/AllProjects';
-export const actions = {
- UPDATE_STATE: 'projects/UPDATE_STATE'
-};
+interface Props {
+ location: { pathname: string; query: { [x: string]: string } };
+ organization: { key: string };
+}
-export const updateState = changes => ({
- type: actions.UPDATE_STATE,
- changes
-});
-
-export default createValue(
- // should update
- (state, action) => action.type === actions.UPDATE_STATE,
- // should reset
- () => false,
- // get next value
- (state, action) => ({ ...state, ...action.changes }),
- // default value
- {}
-);
+export default function OrganizationProjects(props: Props) {
+ return (
+ <div id="projects-page">
+ <App>
+ <AllProjects
+ isFavorite={false}
+ location={props.location}
+ organization={props.organization}
+ />
+ </App>
+ </div>
+ );
+}
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 bca7874cba0..d0c700dbc1b 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
@@ -20,38 +20,46 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
import Helmet from 'react-helmet';
-import PageHeaderContainer from './PageHeaderContainer';
-import ProjectsListContainer from './ProjectsListContainer';
-import ProjectsListFooterContainer from './ProjectsListFooterContainer';
+import PageHeader from './PageHeader';
+import ProjectsList from './ProjectsList';
import PageSidebar from './PageSidebar';
-import VisualizationsContainer from '../visualizations/VisualizationsContainer';
-import { parseUrlQuery } from '../store/utils';
+import Visualizations from '../visualizations/Visualizations';
+import ListFooter from '../../../components/controls/ListFooter';
import { translate } from '../../../helpers/l10n';
-import * as utils from '../utils';
import * as storage from '../../../helpers/storage';
import { RawQuery } from '../../../helpers/query';
import '../styles.css';
+import { Project, Facets } from '../types';
+import { fetchProjects, parseSorting, SORTING_SWITCH } from '../utils';
+import { parseUrlQuery, Query } from '../query';
interface Props {
isFavorite: boolean;
- location: { pathname: string; query: RawQuery };
- fetchProjects: (query: RawQuery, isFavorite: boolean, organization?: {}) => Promise<any>;
+ location: { pathname: string; query: { [x: string]: string } };
organization?: { key: string };
- currentUser?: { isLoggedIn: boolean };
}
interface State {
- query: RawQuery;
+ facets?: Facets;
+ loading: boolean;
+ pageIndex?: number;
+ projects?: Project[];
+ query: Query;
+ total?: number;
}
export default class AllProjects extends React.PureComponent<Props, State> {
- state: State = { query: {} };
+ mounted: boolean;
+ state: State = { loading: true, query: {} };
static contextTypes = {
+ currentUser: PropTypes.object.isRequired,
+ organizationsEnabled: PropTypes.bool,
router: PropTypes.object.isRequired
};
componentDidMount() {
+ this.mounted = true;
this.handleQueryChange(true);
const footer = document.getElementById('footer');
footer && footer.classList.add('page-footer-with-sidebar');
@@ -64,6 +72,7 @@ export default class AllProjects extends React.PureComponent<Props, State> {
}
componentWillUnmount() {
+ this.mounted = false;
const footer = document.getElementById('footer');
footer && footer.classList.remove('page-footer-with-sidebar');
}
@@ -74,7 +83,53 @@ export default class AllProjects extends React.PureComponent<Props, State> {
getSort = () => this.state.query.sort || 'name';
- isFiltered = () => Object.keys(this.state.query).some(key => this.state.query[key] != null);
+ isFiltered = () => Object.values(this.state.query).some(value => value != undefined);
+
+ stopLoading = () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ };
+
+ fetchProjects = (query: any) => {
+ this.setState({ loading: true, query });
+ fetchProjects(
+ query,
+ this.props.isFavorite,
+ this.props.organization && this.props.organization.key
+ ).then(response => {
+ if (this.mounted) {
+ this.setState({
+ facets: response.facets,
+ loading: false,
+ pageIndex: 1,
+ projects: response.projects,
+ total: response.total
+ });
+ }
+ }, this.stopLoading);
+ };
+
+ fetchMoreProjects = () => {
+ const { pageIndex, projects, query } = this.state;
+ if (pageIndex && projects && query) {
+ this.setState({ loading: true });
+ fetchProjects(
+ query,
+ this.props.isFavorite,
+ this.props.organization && this.props.organization.key,
+ pageIndex + 1
+ ).then(response => {
+ if (this.mounted) {
+ this.setState({
+ loading: false,
+ pageIndex: pageIndex + 1,
+ projects: [...projects, ...response.projects]
+ });
+ }
+ }, this.stopLoading);
+ }
+ };
getSavedOptions = () => {
const options: {
@@ -106,9 +161,9 @@ export default class AllProjects extends React.PureComponent<Props, State> {
if (this.state.query.view === 'leak' || view === 'leak') {
if (this.state.query.sort) {
- const sort = utils.parseSorting(this.state.query.sort);
- if (utils.SORTING_SWITCH[sort.sortValue]) {
- query.sort = (sort.sortDesc ? '-' : '') + utils.SORTING_SWITCH[sort.sortValue];
+ const sort = parseSorting(this.state.query.sort);
+ if (SORTING_SWITCH[sort.sortValue]) {
+ query.sort = (sort.sortDesc ? '-' : '') + SORTING_SWITCH[sort.sortValue];
}
}
this.context.router.push({ pathname: this.props.location.pathname, query });
@@ -136,8 +191,7 @@ export default class AllProjects extends React.PureComponent<Props, State> {
if (initialMount && !this.isFiltered() && savedOptionsSet) {
this.context.router.replace({ pathname: this.props.location.pathname, query: savedOptions });
} else {
- this.setState({ query });
- this.props.fetchProjects(query, this.props.isFavorite, this.props.organization);
+ this.fetchProjects(query);
}
}
@@ -159,6 +213,7 @@ export default class AllProjects extends React.PureComponent<Props, State> {
<div className="layout-page-side-inner">
<div className="layout-page-filters">
<PageSidebar
+ facets={this.state.facets}
isFavorite={this.props.isFavorite}
organization={this.props.organization}
query={this.state.query}
@@ -175,14 +230,17 @@ export default class AllProjects extends React.PureComponent<Props, State> {
<div className="layout-page-header-panel layout-page-main-header">
<div className="layout-page-header-panel-inner layout-page-main-header-inner">
<div className="layout-page-main-inner">
- <PageHeaderContainer
- query={this.state.query}
+ <PageHeader
+ currentUser={this.context.currentUser}
isFavorite={this.props.isFavorite}
- organization={this.props.organization}
+ loading={this.state.loading}
onPerspectiveChange={this.handlePerspectiveChange}
onSortChange={this.handleSortChange}
+ organization={this.props.organization}
+ projects={this.state.projects}
+ query={this.state.query}
selectedSort={this.getSort()}
- currentUser={this.props.currentUser}
+ total={this.state.total}
view={this.getView()}
visualization={this.getVisualization()}
/>
@@ -194,23 +252,32 @@ export default class AllProjects extends React.PureComponent<Props, State> {
renderMain = () =>
this.getView() === 'visualizations' ? (
<div className="layout-page-main-inner">
- <VisualizationsContainer
- sort={this.state.query.sort}
- visualization={this.getVisualization()}
- />
+ {this.state.projects && (
+ <Visualizations
+ displayOrganizations={!this.props.organization && !!this.context.organizationsEnabled}
+ projects={this.state.projects}
+ sort={this.state.query.sort}
+ total={this.state.total}
+ visualization={this.getVisualization()}
+ />
+ )}
</div>
) : (
<div className="layout-page-main-inner">
- <ProjectsListContainer
- isFavorite={this.props.isFavorite}
- isFiltered={this.isFiltered()}
- organization={this.props.organization}
- cardType={this.getView()}
- />
- <ProjectsListFooterContainer
- query={this.state.query}
- isFavorite={this.props.isFavorite}
- organization={this.props.organization}
+ {this.state.projects && (
+ <ProjectsList
+ cardType={this.getView()}
+ isFavorite={this.props.isFavorite}
+ isFiltered={this.isFiltered()}
+ organization={this.props.organization}
+ projects={this.state.projects}
+ />
+ )}
+ <ListFooter
+ count={this.state.projects != undefined ? this.state.projects.length : 0}
+ loadMore={this.fetchMoreProjects}
+ ready={!this.state.loading}
+ total={this.state.total != undefined ? this.state.total : 0}
/>
</div>
);
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
index c3ebbadffc8..0e605b910e8 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/App.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/App.tsx
@@ -18,8 +18,35 @@
* 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
+ };
+ }
-export default class App extends React.PureComponent {
componentDidMount() {
const elem = document.querySelector('html');
if (elem) {
@@ -38,3 +65,11 @@ export default class App extends React.PureComponent {
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 f8e73ba7fad..c5d4197f1be 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
@@ -18,16 +18,13 @@
* 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 AllProjectsContainer from './AllProjectsContainer';
-import { getCurrentUser } from '../../../store/rootReducer';
+import AllProjects from './AllProjects';
import { isFavoriteSet, isAllSet } from '../../../helpers/storage';
import { searchProjects } from '../../../api/components';
interface Props {
- currentUser: { isLoggedIn: boolean };
- location: { query: { [x: string]: string } };
+ location: { pathname: string; query: { [x: string]: string } };
}
interface State {
@@ -35,8 +32,9 @@ interface State {
shouldForceSorting?: string;
}
-class DefaultPageSelector extends React.PureComponent<Props, State> {
+export default class DefaultPageSelector extends React.PureComponent<Props, State> {
static contextTypes = {
+ currentUser: PropTypes.object.isRequired,
router: PropTypes.object.isRequired
};
@@ -69,7 +67,7 @@ class DefaultPageSelector extends React.PureComponent<Props, State> {
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.props.currentUser.isLoggedIn) {
+ } else if (!this.context.currentUser.isLoggedIn) {
// show ALL projects if user is anonymous
if (!this.props.location.query || !this.props.location.query.sort) {
// force default sorting to last analysis date
@@ -98,21 +96,7 @@ class DefaultPageSelector extends React.PureComponent<Props, State> {
if (shouldBeRedirected == null || shouldBeRedirected === true || shouldForceSorting != null) {
return null;
} else {
- return (
- <AllProjectsContainer
- isFavorite={false}
- location={this.props.location}
- currentUser={this.props.currentUser}
- />
- );
+ return <AllProjects isFavorite={false} location={this.props.location} />;
}
}
}
-
-const mapStateToProps = (state: any) => ({
- currentUser: getCurrentUser(state)
-});
-
-export default connect<any, any, any>(mapStateToProps)(DefaultPageSelector);
-
-export const UnconnectedDefaultPageSelector = 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 372d0613c3e..25c9d92209b 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,18 +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 { IndexLink, Link } from 'react-router';
import { translate } from '../../../helpers/l10n';
import { saveAll, saveFavorite } from '../../../helpers/storage';
import { RawQuery } from '../../../helpers/query';
interface Props {
- user: { isLoggedIn?: boolean };
organization?: { key: string };
- query: RawQuery;
+ query?: RawQuery;
}
export default class FavoriteFilter extends React.PureComponent<Props> {
+ static contextTypes = {
+ currentUser: PropTypes.object.isRequired
+ };
+
handleSaveFavorite = () => {
if (!this.props.organization) {
saveFavorite();
@@ -43,7 +47,7 @@ export default class FavoriteFilter extends React.PureComponent<Props> {
};
render() {
- if (!this.props.user.isLoggedIn) {
+ if (!this.context.currentUser.isLoggedIn) {
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
deleted file mode 100644
index 515fbf0ae8c..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilterContainer.tsx
+++ /dev/null
@@ -1,28 +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 { connect } from 'react-redux';
-import FavoriteFilter from './FavoriteFilter';
-import { getCurrentUser } from '../../../store/rootReducer';
-
-const mapStateToProps = (state: any) => ({
- user: getCurrentUser(state)
-});
-
-export default connect<any, any, any>(mapStateToProps)(FavoriteFilter);
diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.ts b/server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.tsx
index 83d5f49ba66..1bfa07b70f2 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.ts
+++ b/server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.tsx
@@ -17,8 +17,9 @@
* 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 AllProjects from './AllProjects';
-import { fetchProjects } from '../store/actions';
-export default connect<null, any, any>(null, { fetchProjects })(AllProjects);
+export default function FavoriteProjectsContainer(props: any) {
+ return <AllProjects 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 f9fd94bd3dd..264cb863e52 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
@@ -25,25 +25,26 @@ import PerspectiveSelect from './PerspectiveSelect';
import ProjectsSortingSelect from './ProjectsSortingSelect';
import { translate } from '../../../helpers/l10n';
import { RawQuery } from '../../../helpers/query';
+import { Project } from '../types';
interface Props {
currentUser?: { isLoggedIn: boolean };
isFavorite?: boolean;
+ loading: boolean;
onPerspectiveChange: (x: { view: string; visualization?: string }) => void;
+ onSortChange: (sort: string, desc: boolean) => void;
organization?: { key: string };
- projects: Array<any>;
- projectsAppState: { loading: boolean; total?: number };
+ projects?: Project[];
query: RawQuery;
- onSortChange: (sort: string, desc: boolean) => void;
selectedSort: string;
+ total?: number;
view: string;
visualization?: string;
}
export default function PageHeader(props: Props) {
- const { projectsAppState, projects, currentUser, view } = props;
- const limitReached =
- projects != null && projectsAppState.total != null && projects.length < projectsAppState.total;
+ const { loading, total, projects, currentUser, view } = props;
+ const limitReached = projects != null && total != null && projects.length < total;
const defaultOption = currentUser && currentUser.isLoggedIn ? 'name' : 'analysis_date';
return (
@@ -86,14 +87,13 @@ export default function PageHeader(props: Props) {
<div
className={classNames('projects-topbar-item', 'is-last', {
- 'is-loading': props.projectsAppState.loading
+ 'is-loading': loading
})}>
- {!!props.projectsAppState.loading && <i className="spinner spacer-right" />}
+ {loading && <i className="spinner spacer-right" />}
- {props.projectsAppState.total != null && (
+ {total != null && (
<span>
- <strong id="projects-total">{props.projectsAppState.total}</strong>{' '}
- {translate('projects._projects')}
+ <strong id="projects-total">{total}</strong> {translate('projects._projects')}
</span>
)}
</div>
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 e1d6e6dddb0..6da09bf1157 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
@@ -19,8 +19,9 @@
*/
import * as React from 'react';
import { Link } from 'react-router';
-import FavoriteFilterContainer from './FavoriteFilterContainer';
-import LanguagesFilterContainer from '../filters/LanguagesFilterContainer';
+import { flatMap } from 'lodash';
+import FavoriteFilter from './FavoriteFilter';
+import LanguagesFilter from '../filters/LanguagesFilter';
import CoverageFilter from '../filters/CoverageFilter';
import DuplicationsFilter from '../filters/DuplicationsFilter';
import MaintainabilityFilter from '../filters/MaintainabilityFilter';
@@ -34,11 +35,13 @@ import QualityGateFilter from '../filters/QualityGateFilter';
import ReliabilityFilter from '../filters/ReliabilityFilter';
import SecurityFilter from '../filters/SecurityFilter';
import SizeFilter from '../filters/SizeFilter';
-import TagsFilterContainer from '../filters/TagsFilterContainer';
+import TagsFilter from '../filters/TagsFilter';
import { translate } from '../../../helpers/l10n';
import { RawQuery } from '../../../helpers/query';
+import { Facets } from '../types';
interface Props {
+ facets?: Facets;
isFavorite: boolean;
organization?: { key: string };
query: RawQuery;
@@ -47,14 +50,15 @@ interface Props {
}
export default function PageSidebar(props: Props) {
- const { query, isFavorite, organization, view, visualization } = props;
+ const { facets, query, isFavorite, organization, view, visualization } = props;
const isFiltered = Object.keys(query)
.filter(key => !['view', 'visualization', 'sort'].includes(key))
.some(key => query[key] != null);
const isLeakView = view === 'leak';
const basePathName = organization ? `/organizations/${organization.key}/projects` : '/projects';
const pathname = basePathName + (isFavorite ? '/favorite' : '');
- const facetProps = { query, isFavorite, organization };
+ const maxFacetValue = getMaxFacetValue(facets);
+ const facetProps = { isFavorite, maxFacetValue, organization, query };
let linkQuery: RawQuery | undefined = undefined;
if (view !== 'overall') {
@@ -67,7 +71,7 @@ export default function PageSidebar(props: Props) {
return (
<div>
- <FavoriteFilterContainer query={linkQuery} organization={organization} />
+ <FavoriteFilter query={linkQuery} organization={organization} />
<div className="projects-facets-header clearfix">
{isFiltered && (
@@ -80,25 +84,84 @@ export default function PageSidebar(props: Props) {
<h3>{translate('filters')}</h3>
</div>
- <QualityGateFilter {...facetProps} />
+ <QualityGateFilter {...facetProps} facet={facets && facets.gate} value={query.gate} />
{!isLeakView && [
- <ReliabilityFilter key="reliability" {...facetProps} />,
- <SecurityFilter key="security" {...facetProps} />,
- <MaintainabilityFilter key="maintainability" {...facetProps} />,
- <CoverageFilter key="coverage" {...facetProps} />,
- <DuplicationsFilter key="duplications" {...facetProps} />,
- <SizeFilter key="size" {...facetProps} />
+ <ReliabilityFilter
+ key="reliability"
+ {...facetProps}
+ facet={facets && facets.reliability}
+ value={query.reliability}
+ />,
+ <SecurityFilter
+ key="security"
+ {...facetProps}
+ facet={facets && facets.security}
+ value={query.security}
+ />,
+ <MaintainabilityFilter
+ key="maintainability"
+ {...facetProps}
+ facet={facets && facets.maintainability}
+ value={query.maintainability}
+ />,
+ <CoverageFilter
+ key="coverage"
+ {...facetProps}
+ facet={facets && facets.coverage}
+ value={query.coverage}
+ />,
+ <DuplicationsFilter
+ key="duplications"
+ {...facetProps}
+ facet={facets && facets.duplications}
+ value={query.duplications}
+ />,
+ <SizeFilter key="size" {...facetProps} facet={facets && facets.size} value={query.size} />
]}
{isLeakView && [
- <NewReliabilityFilter key="new_reliability" {...facetProps} />,
- <NewSecurityFilter key="new_security" {...facetProps} />,
- <NewMaintainabilityFilter key="new_maintainability" {...facetProps} />,
- <NewCoverageFilter key="new_coverage" {...facetProps} />,
- <NewDuplicationsFilter key="new_duplications" {...facetProps} />,
- <NewLinesFilter key="new_size" {...facetProps} />
+ <NewReliabilityFilter
+ key="new_reliability"
+ {...facetProps}
+ facet={facets && facets.new_reliability}
+ value={query.new_reliability}
+ />,
+ <NewSecurityFilter
+ key="new_security"
+ {...facetProps}
+ facet={facets && facets.new_security}
+ value={query.new_security}
+ />,
+ <NewMaintainabilityFilter
+ key="new_maintainability"
+ {...facetProps}
+ facet={facets && facets.new_maintainability}
+ value={query.new_maintainability}
+ />,
+ <NewCoverageFilter
+ key="new_coverage"
+ {...facetProps}
+ facet={facets && facets.new_coverage}
+ value={query.new_coverage}
+ />,
+ <NewDuplicationsFilter
+ key="new_duplications"
+ {...facetProps}
+ facet={facets && facets.new_duplications}
+ value={query.new_duplications}
+ />,
+ <NewLinesFilter
+ key="new_lines"
+ {...facetProps}
+ facet={facets && facets.new_lines}
+ value={query.new_lines}
+ />
]}
- <LanguagesFilterContainer {...facetProps} />
- <TagsFilterContainer {...facetProps} />
+ <LanguagesFilter {...facetProps} facet={facets && facets.languages} value={query.languages} />
+ <TagsFilter {...facetProps} facet={facets && facets.tags} value={query.tags} />
</div>
);
}
+
+function getMaxFacetValue(facets?: Facets) {
+ return facets && Math.max(...flatMap(Object.values(facets), facet => Object.values(facet)));
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.ts b/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.tsx
index 428e43364d3..8ef9f193b32 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.ts
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.tsx
@@ -17,13 +17,20 @@
* 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 PageHeader from './PageHeader';
-import { getProjects, getProjectsAppState } from '../../../store/rootReducer';
+import * as React from 'react';
+import ProjectCardLeak from './ProjectCardLeak';
+import ProjectCardOverall from './ProjectCardOverall';
+import { Project } from '../types';
-const mapStateToProps = (state: any) => ({
- projects: getProjects(state),
- projectsAppState: getProjectsAppState(state)
-});
+interface Props {
+ organization?: { key: string };
+ project: Project;
+ type?: string;
+}
-export default connect<any, any, any>(mapStateToProps)(PageHeader);
+export default function ProjectCard(props: Props) {
+ if (props.type === 'leak') {
+ return <ProjectCardLeak {...props} />;
+ }
+ return <ProjectCardOverall {...props} />;
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.tsx
deleted file mode 100644
index 429d7abe1ae..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.tsx
+++ /dev/null
@@ -1,54 +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 ProjectCardLeak from './ProjectCardLeak';
-import ProjectCardOverall from './ProjectCardOverall';
-import { getComponent, getComponentMeasures } from '../../../store/rootReducer';
-
-interface Props {
- measures?: { [key: string]: string };
- organization?: { key: string };
- project?: {
- analysisDate?: string;
- key: string;
- leakPeriodDate?: string;
- name: string;
- tags: Array<string>;
- isFavorite?: boolean;
- organization?: string;
- visibility?: string;
- };
- type?: string;
-}
-
-function ProjectCard(props: Props) {
- if (props.type === 'leak') {
- return <ProjectCardLeak {...props} />;
- }
- return <ProjectCardOverall {...props} />;
-}
-
-const mapStateToProps = (state: any, ownProps: any) => ({
- project: getComponent(state, ownProps.projectKey),
- measures: getComponentMeasures(state, ownProps.projectKey)
-});
-
-export default connect(mapStateToProps)(ProjectCard);
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 e676d7378f0..4bf823864e0 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,6 +18,7 @@
* 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';
@@ -28,40 +29,45 @@ interface Languages {
interface Props {
distribution?: string;
- languages: Languages;
}
-export default function ProjectCardLanguages({ distribution, languages }: Props) {
- if (distribution == undefined) {
- return null;
- }
+export default class ProjectCardLanguages extends React.PureComponent<Props> {
+ static contextTypes = {
+ languages: PropTypes.object.isRequired
+ };
+
+ render() {
+ if (this.props.distribution == undefined) {
+ return null;
+ }
- const parsedLanguages = distribution.split(';').map(item => item.split('='));
- const finalLanguages = sortBy(parsedLanguages, l => -1 * Number(l[1])).map(l =>
- getLanguageName(languages, l[0])
- );
+ 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 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/ProjectCardLeak.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx
index aefda0b2ac2..f0e8fca7d54 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
@@ -24,37 +24,25 @@ import DateFromNow from '../../../components/intl/DateFromNow';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import ProjectCardQualityGate from './ProjectCardQualityGate';
import ProjectCardLeakMeasures from './ProjectCardLeakMeasures';
-import FavoriteContainer from '../../../components/controls/FavoriteContainer';
-import Organization from '../../../components/shared/Organization';
+import ProjectCardOrganization from './ProjectCardOrganization';
+import Favorite from '../../../components/controls/Favorite';
import TagsList from '../../../components/tags/TagsList';
import PrivateBadge from '../../../components/common/PrivateBadge';
import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { Project } from '../types';
interface Props {
- measures?: { [key: string]: string };
organization?: { key: string };
- project?: {
- analysisDate?: string;
- key: string;
- leakPeriodDate?: string;
- name: string;
- tags: Array<string>;
- isFavorite?: boolean;
- organization?: string;
- visibility?: string;
- };
+ project: Project;
}
-export default function ProjectCardLeak({ measures, organization, project }: Props) {
- if (project == undefined) {
- return null;
- }
+export default function ProjectCardLeak({ organization, project }: Props) {
+ const { measures } = project;
const isProjectAnalyzed = project.analysisDate != null;
const isPrivate = project.visibility === 'private';
const hasLeakPeriodStart = project.leakPeriodDate != undefined;
const hasTags = project.tags.length > 0;
- const showOrganization = organization == undefined && project.organization != undefined;
// check for particular measures because only some measures can be loaded
// if coming from visualizations tab
@@ -69,14 +57,14 @@ export default function ProjectCardLeak({ measures, organization, project }: Pro
<div data-key={project.key} className={className}>
<div className="boxed-group-header clearfix">
{project.isFavorite != null && (
- <FavoriteContainer className="spacer-right" componentKey={project.key} />
+ <Favorite
+ className="spacer-right"
+ component={project.key}
+ favorite={project.isFavorite}
+ />
)}
<h2 className="project-card-name">
- {showOrganization && (
- <span className="text-normal">
- <Organization organizationKey={project.organization} />
- </span>
- )}
+ {!organization && <ProjectCardOrganization organization={project.organization} />}
<Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link>
</h2>
{displayQualityGate && <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
new file mode 100644
index 00000000000..ce8c98ffd81
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOrganization.tsx
@@ -0,0 +1,48 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 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';
+import * as PropTypes from 'prop-types';
+import OrganizationLink from '../../../components/ui/OrganizationLink';
+
+interface Props {
+ organization?: { key: string; name: string };
+}
+
+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>
+ );
+ }
+}
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 c52c09a3ff6..75f0f8375d0 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
@@ -23,35 +23,24 @@ import { Link } from 'react-router';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import ProjectCardQualityGate from './ProjectCardQualityGate';
import ProjectCardOverallMeasures from './ProjectCardOverallMeasures';
-import FavoriteContainer from '../../../components/controls/FavoriteContainer';
-import Organization from '../../../components/shared/Organization';
+import ProjectCardOrganization from './ProjectCardOrganization';
+import Favorite from '../../../components/controls/Favorite';
import TagsList from '../../../components/tags/TagsList';
import PrivateBadge from '../../../components/common/PrivateBadge';
import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { Project } from '../types';
interface Props {
- measures?: { [key: string]: string };
organization?: { key: string };
- project?: {
- analysisDate?: string;
- key: string;
- name: string;
- tags: Array<string>;
- isFavorite?: boolean;
- organization?: string;
- visibility?: string;
- };
+ project: Project;
}
-export default function ProjectCardOverall({ measures, organization, project }: Props) {
- if (project == undefined) {
- return null;
- }
+export default function ProjectCardOverall({ organization, project }: Props) {
+ const { measures } = project;
const isProjectAnalyzed = project.analysisDate != undefined;
const isPrivate = project.visibility === 'private';
const hasTags = project.tags.length > 0;
- const showOrganization = organization == undefined && project.organization != undefined;
// check for particular measures because only some measures can be loaded
// if coming from visualizations tab
@@ -69,14 +58,14 @@ export default function ProjectCardOverall({ measures, organization, project }:
<div data-key={project.key} className={className}>
<div className="boxed-group-header clearfix">
{project.isFavorite != undefined && (
- <FavoriteContainer className="spacer-right" componentKey={project.key} />
+ <Favorite
+ className="spacer-right"
+ component={project.key}
+ favorite={project.isFavorite}
+ />
)}
<h2 className="project-card-name">
- {showOrganization && (
- <span className="text-normal">
- <Organization organizationKey={project.organization} />
- </span>
- )}
+ {!organization && <ProjectCardOrganization organization={project.organization} />}
<Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link>
</h2>
{displayQualityGate && <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 4c5a242a638..15c2565cc31 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 ProjectCardLanguagesContainer from './ProjectCardLanguagesContainer';
+import ProjectCardLanguages from './ProjectCardLanguages';
import Measure from '../../../components/measure/Measure';
import Rating from '../../../components/ui/Rating';
import CoverageRating from '../../../components/ui/CoverageRating';
@@ -125,9 +125,7 @@ export default function ProjectCardOverallMeasures({ measures }: Props) {
/>
</div>
<div className="project-card-measure-label">
- <ProjectCardLanguagesContainer
- distribution={measures['ncloc_language_distribution']}
- />
+ <ProjectCardLanguages distribution={measures['ncloc_language_distribution']} />
</div>
</div>
</div>
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx
index 08db1a07322..6636307001a 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx
@@ -18,17 +18,18 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import ProjectCardContainer from './ProjectCardContainer';
+import ProjectCard from './ProjectCard';
import NoFavoriteProjects from './NoFavoriteProjects';
import EmptyInstance from './EmptyInstance';
import EmptySearch from '../../../components/common/EmptySearch';
+import { Project } from '../types';
interface Props {
cardType?: string;
isFavorite: boolean;
isFiltered: boolean;
organization?: { key: string };
- projects?: string[];
+ projects: Project[];
}
export default class ProjectsList extends React.PureComponent<Props> {
@@ -45,17 +46,13 @@ export default class ProjectsList extends React.PureComponent<Props> {
render() {
const { projects } = this.props;
- if (projects == undefined) {
- return null;
- }
-
return (
<div className="projects-list">
{projects.length > 0 ? (
- projects.map(projectKey => (
- <ProjectCardContainer
- key={projectKey}
- projectKey={projectKey}
+ projects.map(project => (
+ <ProjectCard
+ key={project.key}
+ project={project}
organization={this.props.organization}
type={this.props.cardType}
/>
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.ts b/server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.ts
deleted file mode 100644
index 76c02ecfc10..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.ts
+++ /dev/null
@@ -1,29 +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 { connect } from 'react-redux';
-import ProjectsList from './ProjectsList';
-import { getProjects, getProjectsAppState } from '../../../store/rootReducer';
-
-const mapStateToProps = (state: any) => ({
- projects: getProjects(state),
- total: getProjectsAppState(state).total
-});
-
-export default connect<any, any, any>(mapStateToProps)(ProjectsList);
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.ts b/server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.ts
deleted file mode 100644
index fb296456692..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.ts
+++ /dev/null
@@ -1,40 +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 { connect } from 'react-redux';
-import { getProjects, getProjectsAppState } from '../../../store/rootReducer';
-import { fetchMoreProjects } from '../store/actions';
-import ListFooter from '../../../components/controls/ListFooter';
-
-const mapStateToProps = (state: any) => {
- const projects = getProjects(state);
- const appState = getProjectsAppState(state);
- return {
- count: projects != null ? projects.length : 0,
- total: appState.total != null ? appState.total : 0,
- ready: !appState.loading
- };
-};
-
-const mapDispatchToProps = (dispatch: any, ownProps: any) => ({
- loadMore: () =>
- dispatch(fetchMoreProjects(ownProps.query, ownProps.isFavorite, ownProps.organization))
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(ListFooter);
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 fd16bdac37d..5afb5f49874 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
@@ -17,19 +17,14 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-jest.mock('../ProjectsListContainer', () => ({
- default: function ProjectsListContainer() {
+jest.mock('../ProjectsList', () => ({
+ default: function ProjectsList() {
return null;
}
}));
-jest.mock('../ProjectsListFooterContainer', () => ({
- default: function ProjectsListFooterContainer() {
- return null;
- }
-}));
-jest.mock('../PageHeaderContainer', () => ({
- default: function PageHeaderContainer() {
+jest.mock('../PageHeader', () => ({
+ default: function PageHeader() {
return null;
}
}));
@@ -40,6 +35,12 @@ jest.mock('../PageSidebar', () => ({
}
}));
+jest.mock('../../utils', () => {
+ const utils = require.requireActual('../../utils');
+ utils.fetchProjects = jest.fn(() => Promise.resolve({ projects: [] }));
+ return utils;
+});
+
jest.mock('../../../../helpers/storage', () => ({
getSort: () => null,
getView: jest.fn(() => null),
@@ -54,51 +55,46 @@ import { mount, shallow } from 'enzyme';
import AllProjects from '../AllProjects';
import { getView, saveSort, saveView, saveVisualization } from '../../../../helpers/storage';
+const fetchProjects = require('../../utils').fetchProjects as jest.Mock<any>;
+
beforeEach(() => {
(getView as jest.Mock<any>).mockImplementation(() => null);
(saveSort as jest.Mock<any>).mockClear();
(saveView as jest.Mock<any>).mockClear();
(saveVisualization as jest.Mock<any>).mockClear();
+ fetchProjects.mockClear();
});
it('renders', () => {
- const wrapper = shallow(
- <AllProjects
- fetchProjects={jest.fn()}
- isFavorite={false}
- location={{ pathname: '/projects', query: {} }}
- />,
- { context: { router: {} } }
- );
+ const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
wrapper.setState({ query: { view: 'visualizations' } });
expect(wrapper).toMatchSnapshot();
});
it('fetches projects', () => {
- const fetchProjects = jest.fn();
- mountRender({ fetchProjects });
+ mountRender();
expect(fetchProjects).lastCalledWith(
{
- coverage: null,
- duplications: null,
- gate: null,
- languages: null,
- maintainability: null,
- new_coverage: null,
- new_duplications: null,
- new_lines: null,
- new_maintainability: null,
- new_reliability: null,
- new_security: null,
- reliability: null,
- search: null,
- security: null,
- size: null,
- sort: null,
- tags: null,
+ coverage: undefined,
+ duplications: undefined,
+ gate: undefined,
+ languages: undefined,
+ maintainability: undefined,
+ new_coverage: undefined,
+ new_duplications: undefined,
+ new_lines: undefined,
+ new_maintainability: undefined,
+ new_reliability: undefined,
+ new_security: undefined,
+ reliability: undefined,
+ search: undefined,
+ security: undefined,
+ size: undefined,
+ sort: undefined,
+ tags: undefined,
view: undefined,
- visualization: null
+ visualization: undefined
},
false,
undefined
@@ -114,16 +110,16 @@ it('redirects to the saved search', () => {
it('changes sort', () => {
const push = jest.fn();
- const wrapper = mountRender({}, push);
- wrapper.find('PageHeaderContainer').prop<Function>('onSortChange')('size', false);
+ const wrapper = shallowRender({}, push);
+ wrapper.find('PageHeader').prop<Function>('onSortChange')('size', false);
expect(push).lastCalledWith({ pathname: '/projects', query: { sort: 'size' } });
expect(saveSort).lastCalledWith('size');
});
it('changes perspective to leak', () => {
const push = jest.fn();
- const wrapper = mountRender({}, push);
- wrapper.find('PageHeaderContainer').prop<Function>('onPerspectiveChange')({ view: 'leak' });
+ const wrapper = shallowRender({}, push);
+ wrapper.find('PageHeader').prop<Function>('onPerspectiveChange')({ view: 'leak' });
expect(push).lastCalledWith({
pathname: '/projects',
query: { view: 'leak', visualization: undefined }
@@ -135,11 +131,9 @@ it('changes perspective to leak', () => {
it('updates sorting when changing perspective from leak', () => {
const push = jest.fn();
- const wrapper = mountRender(
- { location: { pathname: '/projects', query: { sort: 'new_coverage', view: 'leak' } } },
- push
- );
- wrapper.find('PageHeaderContainer').prop<Function>('onPerspectiveChange')({
+ const wrapper = shallowRender({}, push);
+ wrapper.setState({ query: { sort: 'new_coverage', view: 'leak' } });
+ wrapper.find('PageHeader').prop<Function>('onPerspectiveChange')({
view: undefined
});
expect(push).lastCalledWith({
@@ -153,8 +147,8 @@ it('updates sorting when changing perspective from leak', () => {
it('changes perspective to risk visualization', () => {
const push = jest.fn();
- const wrapper = mountRender({}, push);
- wrapper.find('PageHeaderContainer').prop<Function>('onPerspectiveChange')({
+ const wrapper = shallowRender({}, push);
+ wrapper.find('PageHeader').prop<Function>('onPerspectiveChange')({
view: 'visualizations',
visualization: 'risk'
});
@@ -175,6 +169,19 @@ function mountRender(props: any = {}, push: Function = jest.fn(), replace: Funct
location={{ pathname: '/projects', query: {} }}
{...props}
/>,
- { context: { router: { push, replace } } }
+ { context: { currentUser: { isLoggedIn: true }, router: { push, replace } } }
);
}
+
+function shallowRender(props: any = {}, 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 } } }
+ );
+ wrapper.setState({
+ loading: false,
+ projects: [{ key: 'foo', measures: {}, name: 'Foo' }],
+ total: 0
+ });
+ return wrapper;
+}
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 a7af9fce1e6..ce03b94e0b3 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
@@ -17,8 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-jest.mock('../AllProjectsContainer', () => ({
- default: function AllProjectsContainer() {
+jest.mock('../AllProjects', () => ({
+ default: function AllProjects() {
return null;
}
}));
@@ -34,7 +34,7 @@ jest.mock('../../../../api/components', () => ({
import * as React from 'react';
import { mount } from 'enzyme';
-import { UnconnectedDefaultPageSelector } from '../DefaultPageSelector';
+import DefaultPageSelector from '../DefaultPageSelector';
import { doAsync } from '../../../../helpers/testUtils';
const isFavoriteSet = require('../../../../helpers/storage').isFavoriteSet as jest.Mock<any>;
@@ -55,7 +55,7 @@ it('shows all projects with existing filter', () => {
it('shows all projects sorted by analysis date for anonymous', () => {
const replace = jest.fn();
mountRender({ isLoggedIn: false }, undefined, replace);
- expect(replace).lastCalledWith({ query: { sort: '-analysis_date' } });
+ expect(replace).lastCalledWith({ pathname: '/projects', query: { sort: '-analysis_date' } });
});
it('shows favorite projects', () => {
@@ -83,7 +83,7 @@ it('fetches favorites', () => {
});
function mountRender(user: any = { isLoggedIn: true }, query: any = {}, replace: any = jest.fn()) {
- return mount(<UnconnectedDefaultPageSelector currentUser={user} location={{ query }} />, {
- context: { router: { replace } }
+ return mount(<DefaultPageSelector location={{ pathname: '/projects', query }} />, {
+ context: { currentUser: user, 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 49e81f03b90..b7971fb416a 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
@@ -28,7 +28,7 @@ import FavoriteFilter from '../FavoriteFilter';
import { saveAll, saveFavorite } from '../../../../helpers/storage';
import { click } from '../../../../helpers/testUtils';
-const user = { isLoggedIn: true };
+const currentUser = { isLoggedIn: true };
const query = { size: 1 };
beforeEach(() => {
@@ -37,11 +37,11 @@ beforeEach(() => {
});
it('renders for logged in user', () => {
- expect(shallow(<FavoriteFilter query={query} user={user} />)).toMatchSnapshot();
+ expect(shallow(<FavoriteFilter query={query} />, { context: { currentUser } })).toMatchSnapshot();
});
it('saves last selection', () => {
- const wrapper = shallow(<FavoriteFilter query={query} user={user} />);
+ const wrapper = shallow(<FavoriteFilter query={query} />, { context: { currentUser } });
click(wrapper.find('#favorite-projects'));
expect(saveFavorite).toBeCalled();
click(wrapper.find('#all-projects'));
@@ -50,14 +50,16 @@ it('saves last selection', () => {
it('handles organization', () => {
expect(
- shallow(<FavoriteFilter organization={{ key: 'org' }} query={query} user={user} />)
+ shallow(<FavoriteFilter organization={{ key: 'org' }} query={query} />, {
+ context: { currentUser }
+ })
).toMatchSnapshot();
});
it('does not save last selection with organization', () => {
- const wrapper = shallow(
- <FavoriteFilter organization={{ key: 'org' }} query={query} user={user} />
- );
+ const wrapper = shallow(<FavoriteFilter organization={{ key: 'org' }} query={query} />, {
+ context: { currentUser }
+ });
click(wrapper.find('#favorite-projects'));
expect(saveFavorite).not.toBeCalled();
click(wrapper.find('#all-projects'));
@@ -65,5 +67,7 @@ it('does not save last selection with organization', () => {
});
it('does not render for anonymous', () => {
- expect(shallow(<FavoriteFilter query={query} user={{ isLoggedIn: false }} />)).toMatchSnapshot();
+ expect(
+ shallow(<FavoriteFilter query={query} />, { context: { currentUser: { isLoggedIn: false } } })
+ ).toMatchSnapshot();
});
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 48e9a86b3b2..86ef27cc3a5 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
@@ -26,12 +26,12 @@ it('should render correctly', () => {
});
it('should render correctly while loading', () => {
- expect(shallowRender({ projectsAppState: { loading: true, total: 2 } })).toMatchSnapshot();
+ expect(shallowRender({ loading: true, total: 2 })).toMatchSnapshot();
});
it('should not render projects total', () => {
expect(
- shallowRender({ projectsAppState: {} })
+ shallowRender({ total: undefined })
.find('#projects-total')
.exists()
).toBeFalsy();
@@ -41,7 +41,7 @@ it('should render disabled sorting options for visualizations', () => {
expect(
shallowRender({
open: true,
- projectsAppState: {},
+ total: undefined,
view: 'visualizations',
visualization: 'coverage'
})
@@ -53,7 +53,6 @@ it('should render switch the default sorting option for anonymous users', () =>
shallowRender({
currentUser: { isLoggedIn: true },
open: true,
- projectsAppState: {},
visualization: 'risk'
}).find('ProjectsSortingSelect')
).toMatchSnapshot();
@@ -62,22 +61,22 @@ it('should render switch the default sorting option for anonymous users', () =>
shallowRender({
currentUser: { isLoggedIn: false },
open: true,
- projectsAppState: {},
view: 'leak',
visualization: 'risk'
}).find('ProjectsSortingSelect')
).toMatchSnapshot();
});
-function shallowRender(props?: any) {
+function shallowRender(props?: {}) {
return shallow(
<PageHeader
+ loading={false}
onPerspectiveChange={jest.fn()}
onSortChange={jest.fn()}
projects={[]}
- projectsAppState={{ loading: false, total: 12 }}
query={{ search: 'test' }}
selectedSort="size"
+ total={12}
view="overall"
{...props}
/>
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 76b724f3bce..cca1d63feb1 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" languages={languages} />)
+ shallow(<ProjectCardLanguages distribution="java=137;js=15" />, { context: { languages } })
).toMatchSnapshot();
});
it('sorts languages', () => {
expect(
- shallow(<ProjectCardLanguages distribution="java=13;js=152" languages={languages} />)
+ shallow(<ProjectCardLanguages distribution="java=13;js=152" />, { context: { languages } })
).toMatchSnapshot();
});
it('handles unknown languages', () => {
expect(
- shallow(<ProjectCardLanguages distribution="java=13;cpp=18" languages={languages} />)
+ shallow(<ProjectCardLanguages distribution="java=13;cpp=18" />, { context: { languages } })
).toMatchSnapshot();
expect(
- shallow(<ProjectCardLanguages distribution="java=13;<null>=18" languages={languages} />)
+ shallow(<ProjectCardLanguages distribution="java=13;<null>=18" />, { context: { languages } })
).toMatchSnapshot();
});
it('does not render', () => {
- expect(shallow(<ProjectCardLanguages languages={languages} />)).toMatchSnapshot();
+ expect(shallow(<ProjectCardLanguages />, { context: { languages } })).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.tsx
index 41b383216dc..8a9a75cf1cd 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.tsx
@@ -21,13 +21,6 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import ProjectCardLeak from '../ProjectCardLeak';
-const PROJECT = {
- analysisDate: '2017-01-01',
- leakPeriodDate: '2016-12-01',
- key: 'foo',
- name: 'Foo',
- tags: []
-};
const MEASURES = {
alert_status: 'OK',
reliability_rating: '1.0',
@@ -35,8 +28,19 @@ const MEASURES = {
new_bugs: '12'
};
+const PROJECT = {
+ analysisDate: '2017-01-01',
+ leakPeriodDate: '2016-12-01',
+ key: 'foo',
+ measures: MEASURES,
+ name: 'Foo',
+ organization: { key: 'org', name: 'org' },
+ tags: [],
+ visibility: 'public'
+};
+
it('should display analysis date and leak start date', () => {
- const card = shallow(<ProjectCardLeak measures={MEASURES} project={PROJECT} />);
+ const card = shallow(<ProjectCardLeak project={PROJECT} />);
expect(card.find('.project-card-dates').exists()).toBeTruthy();
expect(card.find('.project-card-dates').find('DateFromNow')).toHaveLength(1);
expect(card.find('.project-card-dates').find('DateTimeFormatter')).toHaveLength(1);
@@ -44,14 +48,14 @@ it('should display analysis date and leak start date', () => {
it('should not display analysis date or leak start date', () => {
const project = { ...PROJECT, analysisDate: undefined };
- const card = shallow(<ProjectCardLeak measures={MEASURES} project={project} />);
+ const card = shallow(<ProjectCardLeak project={project} />);
expect(card.find('.project-card-dates').exists()).toBeFalsy();
});
it('should display loading', () => {
const measures = { alert_status: 'OK', reliability_rating: '1.0', sqale_rating: '1.0' };
expect(
- shallow(<ProjectCardLeak measures={measures} project={PROJECT} />)
+ shallow(<ProjectCardLeak project={{ ...PROJECT, measures }} />)
.find('.boxed-group')
.hasClass('boxed-group-loading')
).toBeTruthy();
@@ -76,5 +80,5 @@ it('should private badge', () => {
});
it('should display the leak measures and quality gate', () => {
- expect(shallow(<ProjectCardLeak measures={MEASURES} project={PROJECT} />)).toMatchSnapshot();
+ expect(shallow(<ProjectCardLeak project={PROJECT} />)).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.tsx
index a1dcfb897b8..7a3e3bb3baf 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.tsx
@@ -21,12 +21,6 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import ProjectCardOverall from '../ProjectCardOverall';
-const PROJECT = {
- analysisDate: '2017-01-01',
- key: 'foo',
- name: 'Foo',
- tags: []
-};
const MEASURES = {
alert_status: 'OK',
reliability_rating: '1.0',
@@ -34,14 +28,24 @@ const MEASURES = {
new_bugs: '12'
};
+const PROJECT = {
+ analysisDate: '2017-01-01',
+ key: 'foo',
+ measures: MEASURES,
+ name: 'Foo',
+ organization: { key: 'org', name: 'org' },
+ tags: [],
+ visibility: 'public'
+};
+
it('should display analysis date (and not leak period) when defined', () => {
expect(
- shallow(<ProjectCardOverall measures={{}} project={PROJECT} />)
+ shallow(<ProjectCardOverall project={PROJECT} />)
.find('.project-card-dates')
.exists()
).toBeTruthy();
expect(
- shallow(<ProjectCardOverall measures={{}} project={{ ...PROJECT, analysisDate: undefined }} />)
+ shallow(<ProjectCardOverall project={{ ...PROJECT, analysisDate: undefined }} />)
.find('.project-card-dates')
.exists()
).toBeFalsy();
@@ -49,12 +53,12 @@ it('should display analysis date (and not leak period) when defined', () => {
it('should display loading', () => {
expect(
- shallow(<ProjectCardOverall project={PROJECT} />)
+ shallow(<ProjectCardOverall project={{ ...PROJECT, measures: {} }} />)
.find('.boxed-group')
.hasClass('boxed-group-loading')
).toBeTruthy();
expect(
- shallow(<ProjectCardOverall measures={{ sqale_rating: '1.0' }} project={PROJECT} />)
+ shallow(<ProjectCardOverall project={{ ...PROJECT, measures: { sqale_rating: '1.0' } }} />)
.find('.boxed-group')
.hasClass('boxed-group-loading')
).toBeTruthy();
@@ -63,7 +67,7 @@ it('should display loading', () => {
it('should not display the quality gate', () => {
const project = { ...PROJECT, analysisDate: undefined };
expect(
- shallow(<ProjectCardOverall measures={MEASURES} project={project} />)
+ shallow(<ProjectCardOverall project={project} />)
.find('ProjectCardOverallQualityGate')
.exists()
).toBeFalsy();
@@ -88,5 +92,5 @@ it('should private badge', () => {
});
it('should display the overall measures and quality gate', () => {
- expect(shallow(<ProjectCardOverall measures={MEASURES} project={PROJECT} />)).toMatchSnapshot();
+ expect(shallow(<ProjectCardOverall project={PROJECT} />)).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsList-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsList-test.tsx
index d15a866c1cb..4ad9127939c 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsList-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsList-test.tsx
@@ -25,10 +25,6 @@ it('renders', () => {
expect(shallowRender()).toMatchSnapshot();
});
-it('does not render without projects', () => {
- expect(shallow(<ProjectsList isFavorite={false} isFiltered={false} />)).toMatchSnapshot();
-});
-
it('renders different types of "no projects"', () => {
expect(shallowRender({ projects: [] })).toMatchSnapshot();
expect(shallowRender({ projects: [], isFiltered: true })).toMatchSnapshot();
@@ -41,7 +37,7 @@ function shallowRender(props?: any) {
cardType="overall"
isFavorite={false}
isFiltered={false}
- projects={['foo', 'bar']}
+ projects={[{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }]}
{...props}
/>
);
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 70ce07e9dab..2561502915e 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
@@ -47,12 +47,28 @@ exports[`renders 1`] = `
<div
className="layout-page-main-inner"
>
- <PageHeaderContainer
+ <PageHeader
+ currentUser={
+ Object {
+ "isLoggedIn": true,
+ }
+ }
isFavorite={false}
+ loading={false}
onPerspectiveChange={[Function]}
onSortChange={[Function]}
+ projects={
+ Array [
+ Object {
+ "key": "foo",
+ "measures": Object {},
+ "name": "Foo",
+ },
+ ]
+ }
query={Object {}}
selectedSort="name"
+ total={0}
view="overall"
visualization="risk"
/>
@@ -62,14 +78,25 @@ exports[`renders 1`] = `
<div
className="layout-page-main-inner"
>
- <ProjectsListContainer
+ <ProjectsList
cardType="overall"
isFavorite={false}
isFiltered={false}
+ projects={
+ Array [
+ Object {
+ "key": "foo",
+ "measures": Object {},
+ "name": "Foo",
+ },
+ ]
+ }
/>
- <ProjectsListFooterContainer
- isFavorite={false}
- query={Object {}}
+ <ListFooter
+ count={1}
+ loadMore={[Function]}
+ ready={true}
+ total={0}
/>
</div>
</div>
@@ -127,16 +154,32 @@ exports[`renders 2`] = `
<div
className="layout-page-main-inner"
>
- <PageHeaderContainer
+ <PageHeader
+ currentUser={
+ Object {
+ "isLoggedIn": true,
+ }
+ }
isFavorite={false}
+ loading={false}
onPerspectiveChange={[Function]}
onSortChange={[Function]}
+ projects={
+ Array [
+ Object {
+ "key": "foo",
+ "measures": Object {},
+ "name": "Foo",
+ },
+ ]
+ }
query={
Object {
"view": "visualizations",
}
}
selectedSort="name"
+ total={0}
view="visualizations"
visualization="risk"
/>
@@ -146,7 +189,18 @@ exports[`renders 2`] = `
<div
className="layout-page-main-inner"
>
- <Connect(Visualizations)
+ <Visualizations
+ displayOrganizations={false}
+ projects={
+ Array [
+ Object {
+ "key": "foo",
+ "measures": Object {},
+ "name": "Foo",
+ },
+ ]
+ }
+ total={0}
visualization="risk"
/>
</div>
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 aa067968a43..5cd3d236fb0 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
@@ -27,7 +27,7 @@ exports[`reset function should work correctly with view and visualizations 2`] =
exports[`should render \`leak\` view correctly 1`] = `
<div>
- <Connect(FavoriteFilter)
+ <FavoriteFilter
query={
Object {
"view": "leak",
@@ -97,7 +97,7 @@ exports[`should render \`leak\` view correctly 1`] = `
}
}
/>
- <Connect(LanguagesFilter)
+ <LanguagesFilter
isFavorite={false}
query={
Object {
@@ -105,7 +105,7 @@ exports[`should render \`leak\` view correctly 1`] = `
}
}
/>
- <Connect(TagsFilter)
+ <TagsFilter
isFavorite={false}
query={
Object {
@@ -118,7 +118,7 @@ exports[`should render \`leak\` view correctly 1`] = `
exports[`should render correctly 1`] = `
<div>
- <Connect(FavoriteFilter) />
+ <FavoriteFilter />
<div
className="projects-facets-header clearfix"
>
@@ -198,8 +198,9 @@ exports[`should render correctly 1`] = `
"size": "3",
}
}
+ value="3"
/>
- <Connect(LanguagesFilter)
+ <LanguagesFilter
isFavorite={true}
query={
Object {
@@ -207,7 +208,7 @@ exports[`should render correctly 1`] = `
}
}
/>
- <Connect(TagsFilter)
+ <TagsFilter
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 cc6ae61aca6..e293ca6efc9 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,6 +11,14 @@ exports[`should display the leak measures and quality gate 1`] = `
<h2
className="project-card-name"
>
+ <ProjectCardOrganization
+ organization={
+ Object {
+ "key": "org",
+ "name": "org",
+ }
+ }
+ />
<Link
onlyActiveOnIndex={false}
style={Object {}}
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 243bf08788d..7fdfbd65dbe 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,6 +11,14 @@ exports[`should display the overall measures and quality gate 1`] = `
<h2
className="project-card-name"
>
+ <ProjectCardOrganization
+ organization={
+ Object {
+ "key": "org",
+ "name": "org",
+ }
+ }
+ />
<Link
onlyActiveOnIndex={false}
style={Object {}}
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 d16171e7345..d1c011628f1 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
@@ -240,7 +240,7 @@ exports[`should render correctly with all data 1`] = `
<div
className="project-card-measure-label"
>
- <Connect(ProjectCardLanguages) />
+ <ProjectCardLanguages />
</div>
</div>
</div>
@@ -281,7 +281,7 @@ exports[`should render ncloc correctly 1`] = `
<div
className="project-card-measure-label"
>
- <Connect(ProjectCardLanguages) />
+ <ProjectCardLanguages />
</div>
</div>
</div>
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap
index 64edfbdf4da..b5a2c7729c5 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap
@@ -1,17 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`does not render without projects 1`] = `null`;
-
exports[`renders 1`] = `
<div
className="projects-list"
>
- <Connect(ProjectCard)
- projectKey="foo"
+ <ProjectCard
+ project={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
type="overall"
/>
- <Connect(ProjectCard)
- projectKey="bar"
+ <ProjectCard
+ project={
+ Object {
+ "key": "bar",
+ "name": "Bar",
+ }
+ }
type="overall"
/>
</div>
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.tsx
index b8784492319..9f6b3bca427 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.tsx
@@ -18,26 +18,32 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import FilterContainer from './FilterContainer';
+import Filter from './Filter';
import FilterHeader from './FilterHeader';
-import { Facet } from './Filter';
import CoverageRating from '../../../components/ui/CoverageRating';
import { getCoverageRatingLabel, getCoverageRatingAverageValue } from '../../../helpers/ratings';
import { translate } from '../../../helpers/l10n';
+import { Facet } from '../types';
export interface Props {
className?: string;
+ facet?: Facet;
isFavorite?: boolean;
+ maxFacetValue?: number;
organization?: { key: string };
property?: string;
query: { [x: string]: any };
+ value?: any;
}
export default function CoverageFilter(props: Props) {
const { property = 'coverage' } = props;
return (
- <FilterContainer
+ <Filter
+ facet={props.facet}
+ maxFacetValue={props.maxFacetValue}
+ value={props.value}
property={property}
className={props.className}
options={[1, 2, 3, 4, 5, 6]}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.tsx
index 6526aced1b8..f51f1072bc2 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import FilterContainer from './FilterContainer';
+import Filter from './Filter';
import FilterHeader from './FilterHeader';
import DuplicationsRating from '../../../components/ui/DuplicationsRating';
import {
@@ -26,20 +26,26 @@ import {
getDuplicationsRatingAverageValue
} from '../../../helpers/ratings';
import { translate } from '../../../helpers/l10n';
-import { Facet } from './Filter';
+import { Facet } from '../types';
export interface Props {
className?: string;
+ facet?: Facet;
isFavorite?: boolean;
+ maxFacetValue?: number;
organization?: { key: string };
property?: string;
query: { [x: string]: any };
+ value?: any;
}
export default function DuplicationsFilter(props: Props) {
const { property = 'duplications' } = props;
return (
- <FilterContainer
+ <Filter
+ facet={props.facet}
+ maxFacetValue={props.maxFacetValue}
+ value={props.value}
property={property}
className={props.className}
options={[1, 2, 3, 4, 5, 6]}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/Filter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/Filter.tsx
index 04ac288c5a6..2da7a189b2a 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/Filter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/Filter.tsx
@@ -23,9 +23,9 @@ import { Link } from 'react-router';
import { getFilterUrl } from './utils';
import { formatMeasure } from '../../../helpers/measures';
import { translate } from '../../../helpers/l10n';
+import { Facet } from '../types';
export type Option = string | number;
-export type Facet = { [x: string]: number };
interface Props {
property: string;
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.ts b/server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.ts
deleted file mode 100644
index eee1640b8ad..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.ts
+++ /dev/null
@@ -1,33 +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 { connect } from 'react-redux';
-import Filter from './Filter';
-import {
- getProjectsAppFacetByProperty,
- getProjectsAppMaxFacetValue
-} from '../../../store/rootReducer';
-
-const mapStateToProps = (state: any, ownProps: any) => ({
- value: ownProps.query[ownProps.property],
- facet: getProjectsAppFacetByProperty(state, ownProps.property),
- maxFacetValue: getProjectsAppMaxFacetValue(state)
-});
-
-export default connect<any, any, any>(mapStateToProps)(Filter);
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.tsx
index 2a139d6cb00..5e14ac24774 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.tsx
@@ -18,25 +18,31 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import FilterContainer from './FilterContainer';
+import Filter from './Filter';
import FilterHeader from './FilterHeader';
import Rating from '../../../components/ui/Rating';
import { translate } from '../../../helpers/l10n';
-import { Facet } from './Filter';
+import { Facet } from '../types';
interface Props {
className?: string;
+ facet?: Facet;
headerDetail?: React.ReactNode;
isFavorite?: boolean;
+ maxFacetValue?: number;
name: string;
organization?: { key: string };
- property?: string;
+ property: string;
query: { [x: string]: any };
+ value?: any;
}
export default function IssuesFilter(props: Props) {
return (
- <FilterContainer
+ <Filter
+ facet={props.facet}
+ maxFacetValue={props.maxFacetValue}
+ value={props.value}
property={props.property}
className={props.className}
options={[1, 2, 3, 4, 5]}
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 b4c7fb562e8..385425c48f6 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,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 { difference, sortBy } from 'lodash';
-import Filter, { Facet } from './Filter';
+import Filter from './Filter';
import FilterHeader from './FilterHeader';
import SearchableFilterFooter from './SearchableFilterFooter';
import SearchableFilterOption from './SearchableFilterOption';
import { getLanguageByKey } from '../../../store/languages/reducer';
import { translate } from '../../../helpers/l10n';
-
-interface Languages {
- [key: string]: { key: string; name: string };
-}
+import { Facet } from '../types';
interface Props {
facet?: Facet;
isFavorite?: boolean;
- languages: Languages;
maxFacetValue?: number;
organization?: { key: string };
property?: string;
@@ -44,14 +41,18 @@ 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.props.languages);
+ let languageKeys = Object.keys(this.context.languages);
if (this.props.facet) {
languageKeys = difference(languageKeys, Object.keys(this.props.facet));
}
return languageKeys
.slice(0, LIST_SIZE)
- .map(key => ({ label: this.props.languages[key].name, value: key }));
+ .map(key => ({ label: this.context.languages[key].name, value: key }));
};
getSortedOptions = (facet: Facet = {}) =>
@@ -62,7 +63,7 @@ export default class LanguagesFilter extends React.Component<Props> {
renderOption = (option: string) => (
<SearchableFilterOption
optionKey={option}
- option={getLanguageByKey(this.props.languages, option)}
+ option={getLanguageByKey(this.context.languages, option)}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.ts b/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.ts
deleted file mode 100644
index 8b45c11c36a..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.ts
+++ /dev/null
@@ -1,34 +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 { connect } from 'react-redux';
-import LanguagesFilter from './LanguagesFilter';
-import {
- getProjectsAppFacetByProperty,
- getProjectsAppMaxFacetValue,
- getLanguages
-} from '../../../store/rootReducer';
-
-const mapStateToProps = (state: any, ownProps: any) => ({
- languages: getLanguages(state),
- value: ownProps.query['languages'],
- facet: getProjectsAppFacetByProperty(state, 'languages'),
- maxFacetValue: getProjectsAppMaxFacetValue(state)
-});
-export default connect<any, any, any>(mapStateToProps)(LanguagesFilter);
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.tsx
index 424e3f21ce1..d4f71d0f4c6 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.tsx
@@ -19,13 +19,17 @@
*/
import * as React from 'react';
import IssuesFilter from './IssuesFilter';
+import { Facet } from '../types';
interface Props {
className?: string;
+ facet?: Facet;
headerDetail?: React.ReactNode;
isFavorite?: boolean;
+ maxFacetValue?: number;
organization?: { key: string };
query: { [x: string]: any };
+ value?: any;
}
export default function MaintainabilityFilter(props: Props) {
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.tsx
index 29374492c73..74d688c564d 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.tsx
@@ -18,25 +18,31 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import FilterContainer from './FilterContainer';
+import Filter from './Filter';
import FilterHeader from './FilterHeader';
import { translate } from '../../../helpers/l10n';
import { getSizeRatingLabel } from '../../../helpers/ratings';
-import { Facet } from './Filter';
+import { Facet } from '../types';
export interface Props {
className?: string;
+ facet?: Facet;
isFavorite?: boolean;
+ maxFacetValue?: number;
organization?: { key: string };
property?: string;
query: { [x: string]: any };
+ value?: any;
}
export default function NewLinesFilter(props: Props) {
const { property = 'new_lines' } = props;
return (
- <FilterContainer
+ <Filter
+ facet={props.facet}
+ maxFacetValue={props.maxFacetValue}
+ value={props.value}
property={property}
className="leak-facet-box"
options={[1, 2, 3, 4, 5]}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx
index 590645121b2..6e3dd99d2ed 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx
@@ -21,12 +21,16 @@ import * as React from 'react';
import CodeSmellIcon from '../../../components/icons-components/CodeSmellIcon';
import IssuesFilter from './IssuesFilter';
import { translate } from '../../../helpers/l10n';
+import { Facet } from '../types';
interface Props {
className?: string;
+ facet?: Facet;
isFavorite?: boolean;
+ maxFacetValue?: number;
organization?: { key: string };
query: { [x: string]: any };
+ value?: any;
}
export default function NewMaintainabilityFilter(props: Props) {
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx
index c2b6ff0e08d..23eb35a733d 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx
@@ -21,12 +21,16 @@ import * as React from 'react';
import BugIcon from '../../../components/icons-components/BugIcon';
import IssuesFilter from './IssuesFilter';
import { translate } from '../../../helpers/l10n';
+import { Facet } from '../types';
interface Props {
className?: string;
+ facet?: Facet;
isFavorite?: boolean;
+ maxFacetValue?: number;
organization?: { key: string };
query: { [x: string]: any };
+ value?: any;
}
export default function NewReliabilityFilter(props: Props) {
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx
index 66d5bf07c5f..8f09c50130d 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx
@@ -21,12 +21,16 @@ import * as React from 'react';
import VulnerabilityIcon from '../../../components/icons-components/VulnerabilityIcon';
import IssuesFilter from './IssuesFilter';
import { translate } from '../../../helpers/l10n';
+import { Facet } from '../types';
interface Props {
className?: string;
+ facet?: Facet;
isFavorite?: boolean;
+ maxFacetValue?: number;
organization?: { key: string };
query: { [x: string]: any };
+ value?: any;
}
export default function NewSecurityFilter(props: Props) {
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.tsx
index 56ce79e0db6..2daf2eb1755 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.tsx
@@ -18,22 +18,28 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import FilterContainer from './FilterContainer';
+import Filter from './Filter';
import FilterHeader from './FilterHeader';
import Level from '../../../components/ui/Level';
import { translate } from '../../../helpers/l10n';
-import { Facet } from './Filter';
+import { Facet } from '../types';
export interface Props {
className?: string;
+ facet?: Facet;
isFavorite?: boolean;
+ maxFacetValue?: number;
organization?: { key: string };
query: { [x: string]: any };
+ value?: any;
}
export default function QualityGateFilter(props: Props) {
return (
- <FilterContainer
+ <Filter
+ facet={props.facet}
+ maxFacetValue={props.maxFacetValue}
+ value={props.value}
property="gate"
options={['OK', 'WARN', 'ERROR']}
query={props.query}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx
index 99e3ce19bd0..77ab08dc006 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx
@@ -19,13 +19,17 @@
*/
import * as React from 'react';
import IssuesFilter from './IssuesFilter';
+import { Facet } from '../types';
interface Props {
className?: string;
+ facet?: Facet;
headerDetail?: React.ReactNode;
isFavorite?: boolean;
+ maxFacetValue?: number;
organization?: { key: string };
query: { [x: string]: any };
+ value?: any;
}
export default function ReliabilityFilter(props: Props) {
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx
index 27684ca9d93..262573864c5 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx
@@ -19,13 +19,17 @@
*/
import * as React from 'react';
import IssuesFilter from './IssuesFilter';
+import { Facet } from '../types';
interface Props {
className?: string;
+ facet?: Facet;
headerDetail?: React.ReactNode;
isFavorite?: boolean;
+ maxFacetValue?: number;
organization?: { key: string };
query: { [x: string]: any };
+ value?: any;
}
export default function SecurityFilter(props: Props) {
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.tsx
index 36479e486b7..ebf98c8bd87 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.tsx
@@ -18,26 +18,32 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import FilterContainer from './FilterContainer';
+import Filter from './Filter';
import FilterHeader from './FilterHeader';
import SizeRating from '../../../components/ui/SizeRating';
import { translate } from '../../../helpers/l10n';
import { getSizeRatingLabel, getSizeRatingAverageValue } from '../../../helpers/ratings';
-import { Facet } from './Filter';
+import { Facet } from '../types';
export interface Props {
className?: string;
+ facet?: Facet;
isFavorite?: boolean;
+ maxFacetValue?: number;
organization?: { key: string };
property?: string;
query: { [x: string]: any };
+ value?: any;
}
export default function SizeFilter(props: Props) {
const { property = 'size' } = props;
return (
- <FilterContainer
+ <Filter
+ facet={props.facet}
+ maxFacetValue={props.maxFacetValue}
+ value={props.value}
property={property}
className={props.className}
options={[1, 2, 3, 4, 5]}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx
index e163fed3b49..ee49080b8f0 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx
@@ -19,12 +19,13 @@
*/
import * as React from 'react';
import { debounce, difference, sortBy, size } from 'lodash';
-import Filter, { Facet } from './Filter';
+import Filter from './Filter';
import FilterHeader from './FilterHeader';
import SearchableFilterFooter from './SearchableFilterFooter';
import SearchableFilterOption from './SearchableFilterOption';
import { searchProjectTags } from '../../../api/components';
import { translate } from '../../../helpers/l10n';
+import { Facet } from '../types';
interface Props {
facet?: Facet;
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/TagsFilterContainer.ts b/server/sonar-web/src/main/js/apps/projects/filters/TagsFilterContainer.ts
deleted file mode 100644
index 47d25834e5c..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/filters/TagsFilterContainer.ts
+++ /dev/null
@@ -1,32 +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 { connect } from 'react-redux';
-import TagsFilter from './TagsFilter';
-import {
- getProjectsAppFacetByProperty,
- getProjectsAppMaxFacetValue
-} from '../../../store/rootReducer';
-
-const mapStateToProps = (state: any, ownProps: any) => ({
- value: ownProps.query['tags'],
- facet: getProjectsAppFacetByProperty(state, 'tags'),
- maxFacetValue: getProjectsAppMaxFacetValue(state)
-});
-export default connect<any, any, any>(mapStateToProps)(TagsFilter);
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/IssuesFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/IssuesFilter-test.tsx
index 6f839f171dc..a21a219fe68 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/IssuesFilter-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/IssuesFilter-test.tsx
@@ -22,7 +22,7 @@ import { shallow } from 'enzyme';
import IssuesFilter from '../IssuesFilter';
it('renders', () => {
- const wrapper = shallow(<IssuesFilter name="bugs" query={{}} />);
+ const wrapper = shallow(<IssuesFilter name="bugs" property="bugs" query={{}} />);
expect(wrapper).toMatchSnapshot();
const renderOption = wrapper.prop('renderOption');
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 2669fd4f1ae..27ea25d17dd 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,9 +33,9 @@ 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 }} languages={languages} facet={languagesFacet} />
- );
+ const wrapper = shallow(<LanguagesFilter query={{ languages: null }} facet={languagesFacet} />, {
+ context: { languages }
+ });
expect(wrapper).toMatchSnapshot();
});
@@ -44,35 +44,36 @@ it('should render the languages facet with the selected languages', () => {
<LanguagesFilter
query={{ languages: ['java', 'cs'] }}
value={['java', 'cs']}
- languages={languages}
facet={languagesFacet}
isFavorite={true}
- />
+ />,
+ { context: { languages } }
);
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('Filter').shallow()).toMatchSnapshot();
});
it('should render maximum 10 languages in the searchbox results', () => {
+ const manyLanguages = {
+ ...languages,
+ c: { key: 'c', name: 'c' },
+ d: { key: 'd', name: 'd' },
+ e: { key: 'e', name: 'e' },
+ f: { key: 'f', name: 'f' },
+ g: { key: 'g', name: 'g' },
+ h: { key: 'h', name: 'h' },
+ i: { key: 'i', name: 'i' },
+ k: { key: 'k', name: 'k' },
+ l: { key: 'l', name: 'l' }
+ };
const wrapper = shallow(
<LanguagesFilter
query={{ languages: ['java', 'g'] }}
value={['java', 'g']}
- languages={{
- ...languages,
- c: { key: 'c', name: 'c' },
- d: { key: 'd', name: 'd' },
- e: { key: 'e', name: 'e' },
- f: { key: 'f', name: 'f' },
- g: { key: 'g', name: 'g' },
- h: { key: 'h', name: 'h' },
- i: { key: 'i', name: 'i' },
- k: { key: 'k', name: 'k' },
- l: { key: 'l', name: 'l' }
- }}
facet={{ ...languagesFacet, g: 1 }}
isFavorite={true}
- />
+ />,
+ { context: { languages: manyLanguages } }
);
expect(wrapper).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/CoverageFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/CoverageFilter-test.tsx.snap
index d475b329ae7..da8e061961c 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/CoverageFilter-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/CoverageFilter-test.tsx.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders 1`] = `
-<Connect(Filter)
+<Filter
getFacetValueForOption={[Function]}
header={
<FilterHeader
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/DuplicationsFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/DuplicationsFilter-test.tsx.snap
index ff87f83526b..86606f59ac7 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/DuplicationsFilter-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/DuplicationsFilter-test.tsx.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders 1`] = `
-<Connect(Filter)
+<Filter
getFacetValueForOption={[Function]}
header={
<FilterHeader
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/IssuesFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/IssuesFilter-test.tsx.snap
index 2177f4c16e9..3f1103ec20a 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/IssuesFilter-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/IssuesFilter-test.tsx.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders 1`] = `
-<Connect(Filter)
+<Filter
getFacetValueForOption={[Function]}
header={
<FilterHeader
@@ -18,6 +18,7 @@ exports[`renders 1`] = `
5,
]
}
+ property="bugs"
query={Object {}}
renderOption={[Function]}
/>
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewLinesFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewLinesFilter-test.tsx.snap
index 4fde9336313..2da8deedd2a 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewLinesFilter-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewLinesFilter-test.tsx.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders 1`] = `
-<Connect(Filter)
+<Filter
className="leak-facet-box"
getFacetValueForOption={[Function]}
header={
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/QualityGateFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/QualityGateFilter-test.tsx.snap
index c20cd46404a..350e162a603 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/QualityGateFilter-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/QualityGateFilter-test.tsx.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders 1`] = `
-<Connect(Filter)
+<Filter
getFacetValueForOption={[Function]}
header={
<FilterHeader
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SizeFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SizeFilter-test.tsx.snap
index e4ec3a9aa79..057d6b0e42e 100644
--- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SizeFilter-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SizeFilter-test.tsx.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders 1`] = `
-<Connect(Filter)
+<Filter
getFacetValueForOption={[Function]}
header={
<FilterHeader
diff --git a/server/sonar-web/src/main/js/apps/projects/query.ts b/server/sonar-web/src/main/js/apps/projects/query.ts
new file mode 100644
index 00000000000..b64a34fda81
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projects/query.ts
@@ -0,0 +1,256 @@
+/*
+* SonarQube
+* Copyright (C) 2009-2016 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 { VISUALIZATIONS } from './utils';
+import { RawQuery } from '../../helpers/query';
+
+type Level = 'ERROR' | 'WARN' | 'OK';
+
+export interface Query {
+ gate?: Level;
+ reliability?: number;
+ new_reliability?: number;
+ security?: number;
+ new_security?: number;
+ maintainability?: number;
+ new_maintainability?: number;
+ coverage?: number;
+ new_coverage?: number;
+ duplications?: number;
+ new_duplications?: number;
+ size?: number;
+ new_lines?: number;
+ languages?: string[];
+ tags?: string[];
+ search?: string;
+ sort?: string;
+ view?: string;
+ visualization?: string;
+ [x: string]: string | number | string[] | undefined;
+}
+
+export function parseUrlQuery(urlQuery: RawQuery): Query {
+ return {
+ gate: getAsLevel(urlQuery['gate']),
+ reliability: getAsNumericRating(urlQuery['reliability']),
+ new_reliability: getAsNumericRating(urlQuery['new_reliability']),
+ security: getAsNumericRating(urlQuery['security']),
+ new_security: getAsNumericRating(urlQuery['new_security']),
+ maintainability: getAsNumericRating(urlQuery['maintainability']),
+ new_maintainability: getAsNumericRating(urlQuery['new_maintainability']),
+ coverage: getAsNumericRating(urlQuery['coverage']),
+ new_coverage: getAsNumericRating(urlQuery['new_coverage']),
+ duplications: getAsNumericRating(urlQuery['duplications']),
+ new_duplications: getAsNumericRating(urlQuery['new_duplications']),
+ size: getAsNumericRating(urlQuery['size']),
+ new_lines: getAsNumericRating(urlQuery['new_lines']),
+ languages: getAsStringArray(urlQuery['languages']),
+ tags: getAsStringArray(urlQuery['tags']),
+ search: getAsString(urlQuery['search']),
+ sort: getAsString(urlQuery['sort']),
+ view: getView(urlQuery['view']),
+ visualization: getVisualization(urlQuery['visualization'])
+ };
+}
+
+export function convertToFilter(query: Query, isFavorite: boolean): string {
+ const conditions: string[] = [];
+
+ if (isFavorite) {
+ conditions.push('isFavorite');
+ }
+
+ if (query['gate'] != null) {
+ conditions.push(mapPropertyToMetric('gate') + ' = ' + query['gate']);
+ }
+
+ ['coverage', 'new_coverage'].forEach(property =>
+ pushMetricToArray(query, property, conditions, convertCoverage)
+ );
+
+ ['duplications', 'new_duplications'].forEach(property =>
+ pushMetricToArray(query, property, conditions, convertDuplications)
+ );
+
+ ['size', 'new_lines'].forEach(property =>
+ pushMetricToArray(query, property, conditions, convertSize)
+ );
+
+ [
+ 'reliability',
+ 'security',
+ 'maintainability',
+ 'new_reliability',
+ 'new_security',
+ 'new_maintainability'
+ ].forEach(property => pushMetricToArray(query, property, conditions, convertIssuesRating));
+
+ ['languages', 'tags'].forEach(property =>
+ pushMetricToArray(query, property, conditions, convertArrayMetric)
+ );
+
+ if (query['search'] != null) {
+ conditions.push(`${mapPropertyToMetric('search')} = "${query['search']}"`);
+ }
+
+ return conditions.join(' and ');
+}
+
+function getAsNumericRating(value: any): number | undefined {
+ if (value === '' || value == null || isNaN(value)) {
+ return undefined;
+ }
+ const num = Number(value);
+ return num > 0 && num < 7 ? num : undefined;
+}
+
+function getAsLevel(value: any): Level | undefined {
+ if (value === 'ERROR' || value === 'WARN' || value === 'OK') {
+ return value;
+ }
+ return undefined;
+}
+
+function getAsString(value: any): string | undefined {
+ if (typeof value !== 'string' || !value) {
+ return undefined;
+ }
+ return value;
+}
+
+function getAsStringArray(value: any): string[] | undefined {
+ if (typeof value !== 'string' || !value) {
+ return undefined;
+ }
+ return value.split(',');
+}
+
+function getView(value: any): string | undefined {
+ return typeof value !== 'string' || value === 'overall' ? undefined : value;
+}
+
+function getVisualization(value: string): string | undefined {
+ return VISUALIZATIONS.includes(value) ? value : undefined;
+}
+
+function convertIssuesRating(metric: string, rating: number): string {
+ if (rating > 1 && rating < 5) {
+ return `${metric} >= ${rating}`;
+ } else {
+ return `${metric} = ${rating}`;
+ }
+}
+
+function convertCoverage(metric: string, coverage: number): string {
+ switch (coverage) {
+ case 1:
+ return metric + ' >= 80';
+ case 2:
+ return metric + ' < 80';
+ case 3:
+ return metric + ' < 70';
+ case 4:
+ return metric + ' < 50';
+ case 5:
+ return metric + ' < 30';
+ case 6:
+ return metric + '= NO_DATA';
+ default:
+ return '';
+ }
+}
+
+function convertDuplications(metric: string, duplications: number): string {
+ switch (duplications) {
+ case 1:
+ return metric + ' < 3';
+ case 2:
+ return metric + ' >= 3';
+ case 3:
+ return metric + ' >= 5';
+ case 4:
+ return metric + ' >= 10';
+ case 5:
+ return metric + ' >= 20';
+ case 6:
+ return metric + '= NO_DATA';
+ default:
+ return '';
+ }
+}
+
+function convertSize(metric: string, size: number): string {
+ switch (size) {
+ case 1:
+ return metric + ' < 1000';
+ case 2:
+ return metric + ' >= 1000';
+ case 3:
+ return metric + ' >= 10000';
+ case 4:
+ return metric + ' >= 100000';
+ case 5:
+ return metric + ' >= 500000';
+ default:
+ return '';
+ }
+}
+
+function mapPropertyToMetric(property?: string): string | undefined {
+ const map: { [property: string]: string } = {
+ analysis_date: 'analysisDate',
+ reliability: 'reliability_rating',
+ new_reliability: 'new_reliability_rating',
+ security: 'security_rating',
+ new_security: 'new_security_rating',
+ maintainability: 'sqale_rating',
+ new_maintainability: 'new_maintainability_rating',
+ coverage: 'coverage',
+ new_coverage: 'new_coverage',
+ duplications: 'duplicated_lines_density',
+ new_duplications: 'new_duplicated_lines_density',
+ size: 'ncloc',
+ new_lines: 'new_lines',
+ gate: 'alert_status',
+ languages: 'languages',
+ tags: 'tags',
+ search: 'query'
+ };
+ return property && map[property];
+}
+
+function pushMetricToArray(
+ query: Query,
+ property: string,
+ conditionsArray: string[],
+ convertFunction: (metric: string, value: any) => string
+): void {
+ query.foo;
+ const metric = mapPropertyToMetric(property);
+ if (query[property] != null && metric) {
+ conditionsArray.push(convertFunction(metric, query[property]));
+ }
+}
+
+function convertArrayMetric(metric: string, items: string | string[]): string {
+ if (!Array.isArray(items) || items.length < 2) {
+ return metric + ' = ' + items;
+ }
+ return `${metric} IN (${items.join(', ')})`;
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/store/actions.js b/server/sonar-web/src/main/js/apps/projects/store/actions.js
deleted file mode 100644
index 46dc1f670a1..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/store/actions.js
+++ /dev/null
@@ -1,240 +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 { groupBy, uniq } from 'lodash';
-import { searchProjects, setProjectTags as apiSetProjectTags } from '../../../api/components';
-import { addGlobalErrorMessage } from '../../../store/globalMessages/duck';
-import { parseError } from '../../code/utils';
-import { receiveComponents, receiveProjectTags } from '../../../store/components/actions';
-import { receiveProjects, receiveMoreProjects } from './projectsDuck';
-import { updateState } from './stateDuck';
-import { getProjectsAppState, getComponent } from '../../../store/rootReducer';
-import { getMeasuresForProjects } from '../../../api/measures';
-import { receiveComponentsMeasures } from '../../../store/measures/actions';
-import { convertToQueryData } from './utils';
-import { receiveFavorites } from '../../../store/favorites/duck';
-import { getOrganizations } from '../../../api/organizations';
-import { receiveOrganizations } from '../../../store/organizations/duck';
-import { isDiffMetric, getPeriodValue } from '../../../helpers/measures';
-
-const PAGE_SIZE = 50;
-const PAGE_SIZE_VISUALIZATIONS = 99;
-
-const METRICS = [
- 'alert_status',
- 'reliability_rating',
- 'security_rating',
- 'sqale_rating',
- 'duplicated_lines_density',
- 'coverage',
- 'ncloc',
- 'ncloc_language_distribution'
-];
-
-const LEAK_METRICS = [
- 'alert_status',
- 'new_bugs',
- 'new_reliability_rating',
- 'new_vulnerabilities',
- 'new_security_rating',
- 'new_code_smells',
- 'new_maintainability_rating',
- 'new_coverage',
- 'new_duplicated_lines_density',
- 'new_lines'
-];
-
-const METRICS_BY_VISUALIZATION = {
- risk: ['reliability_rating', 'security_rating', 'coverage', 'ncloc', 'sqale_index'],
- // x, y, size, color
- reliability: ['ncloc', 'reliability_remediation_effort', 'bugs', 'reliability_rating'],
- security: ['ncloc', 'security_remediation_effort', 'vulnerabilities', 'security_rating'],
- maintainability: ['ncloc', 'sqale_index', 'code_smells', 'sqale_rating'],
- coverage: ['complexity', 'coverage', 'uncovered_lines'],
- duplications: ['ncloc', 'duplicated_lines', 'duplicated_blocks']
-};
-
-const FACETS = [
- 'reliability_rating',
- 'security_rating',
- 'sqale_rating',
- 'coverage',
- 'duplicated_lines_density',
- 'ncloc',
- 'alert_status',
- 'languages',
- 'tags'
-];
-
-const LEAK_FACETS = [
- 'new_reliability_rating',
- 'new_security_rating',
- 'new_maintainability_rating',
- 'new_coverage',
- 'new_duplicated_lines_density',
- 'new_lines',
- 'alert_status',
- 'languages',
- 'tags'
-];
-
-const onFail = dispatch => error => {
- parseError(error).then(message => dispatch(addGlobalErrorMessage(message)));
- dispatch(updateState({ loading: false }));
-};
-
-const onReceiveMeasures = (dispatch, expectedProjectKeys) => response => {
- const byComponentKey = groupBy(response.measures, 'component');
-
- const toStore = {};
-
- // fill store with empty objects for expected projects
- // this is required to not have "null"s for provisioned projects
- expectedProjectKeys.forEach(projectKey => (toStore[projectKey] = {}));
-
- Object.keys(byComponentKey).forEach(componentKey => {
- const measures = {};
- byComponentKey[componentKey].forEach(measure => {
- measures[measure.metric] = isDiffMetric(measure.metric)
- ? getPeriodValue(measure, 1)
- : measure.value;
- });
- toStore[componentKey] = measures;
- });
-
- dispatch(receiveComponentsMeasures(toStore));
-};
-
-const onReceiveOrganizations = dispatch => response => {
- dispatch(receiveOrganizations(response.organizations));
-};
-
-const defineMetrics = query => {
- switch (query.view) {
- case 'visualizations':
- return METRICS_BY_VISUALIZATION[query.visualization || 'risk'];
- case 'leak':
- return LEAK_METRICS;
- default:
- return METRICS;
- }
-};
-
-const defineFacets = query => {
- if (query.view === 'leak') {
- return LEAK_FACETS;
- }
- return FACETS;
-};
-
-const fetchProjectMeasures = (projects, query) => dispatch => {
- if (!projects.length) {
- return Promise.resolve();
- }
-
- const projectKeys = projects.map(project => project.key);
- const metrics = defineMetrics(query);
- return getMeasuresForProjects(projectKeys, metrics).then(
- onReceiveMeasures(dispatch, projectKeys),
- onFail(dispatch)
- );
-};
-
-const fetchProjectOrganizations = projects => dispatch => {
- if (!projects.length) {
- return Promise.resolve();
- }
-
- const organizationKeys = uniq(projects.map(project => project.organization));
- return getOrganizations(organizationKeys).then(
- onReceiveOrganizations(dispatch),
- onFail(dispatch)
- );
-};
-
-const handleFavorites = (dispatch, projects) => {
- const toAdd = projects.filter(project => project.isFavorite);
- const toRemove = projects.filter(project => project.isFavorite === false);
- if (toAdd.length || toRemove.length) {
- dispatch(receiveFavorites(toAdd, toRemove));
- }
-};
-
-const onReceiveProjects = (dispatch, query) => response => {
- dispatch(receiveComponents(response.components));
- dispatch(receiveProjects(response.components, response.facets));
- handleFavorites(dispatch, response.components);
- Promise.all([
- dispatch(fetchProjectMeasures(response.components, query)),
- dispatch(fetchProjectOrganizations(response.components))
- ]).then(() => {
- dispatch(updateState({ loading: false }));
- });
- dispatch(
- updateState({
- total: response.paging.total,
- pageIndex: response.paging.pageIndex
- })
- );
-};
-
-const onReceiveMoreProjects = (dispatch, query) => response => {
- dispatch(receiveComponents(response.components));
- dispatch(receiveMoreProjects(response.components));
- handleFavorites(dispatch, response.components);
- Promise.all([
- dispatch(fetchProjectMeasures(response.components, query)),
- dispatch(fetchProjectOrganizations(response.components))
- ]).then(() => {
- dispatch(updateState({ loading: false }));
- });
- dispatch(updateState({ pageIndex: response.paging.pageIndex }));
-};
-
-export const fetchProjects = (query, isFavorite, organization) => dispatch => {
- dispatch(updateState({ loading: true }));
- const ps = query.view === 'visualizations' ? PAGE_SIZE_VISUALIZATIONS : PAGE_SIZE;
- const data = convertToQueryData(query, isFavorite, organization, {
- ps,
- facets: defineFacets(query).join(),
- f: 'analysisDate,leakPeriodDate'
- });
- return searchProjects(data).then(onReceiveProjects(dispatch, query), onFail(dispatch));
-};
-
-export const fetchMoreProjects = (query, isFavorite, organization) => (dispatch, getState) => {
- dispatch(updateState({ loading: true }));
- const state = getState();
- const { pageIndex } = getProjectsAppState(state);
- const data = convertToQueryData(query, isFavorite, organization, {
- ps: PAGE_SIZE,
- p: pageIndex + 1,
- f: 'analysisDate,leakPeriodDate'
- });
- return searchProjects(data).then(onReceiveMoreProjects(dispatch, query), onFail(dispatch));
-};
-
-export const setProjectTags = (project, tags) => (dispatch, getState) => {
- const previousTags = getComponent(getState(), project).tags;
- dispatch(receiveProjectTags(project, tags));
- return apiSetProjectTags({ project, tags: tags.join(',') }).then(null, error => {
- dispatch(receiveProjectTags(project, previousTags));
- onFail(dispatch)(error);
- });
-};
diff --git a/server/sonar-web/src/main/js/apps/projects/store/facetsDuck.js b/server/sonar-web/src/main/js/apps/projects/store/facetsDuck.js
deleted file mode 100644
index f47b8d67471..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/store/facetsDuck.js
+++ /dev/null
@@ -1,88 +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 { flatMap, sumBy } from 'lodash';
-import { createMap } from '../../../store/utils/generalReducers';
-import { actions } from './projectsDuck';
-import { mapMetricToProperty } from './utils';
-
-const CUMULATIVE_FACETS = [
- 'reliability',
- 'new_reliability',
- 'security',
- 'new_security',
- 'maintainability',
- 'new_maintainability',
- 'coverage',
- 'new_coverage',
- 'duplications',
- 'new_duplications',
- 'size',
- 'new_lines'
-];
-
-const REVERSED_FACETS = ['coverage', 'new_coverage'];
-
-const mapFacetValues = values => {
- const map = {};
- values.forEach(value => {
- map[value.val] = value.count;
- });
- return map;
-};
-
-const cumulativeMapFacetValues = values => {
- const map = {};
- let sum = sumBy(values, value => value.count);
- values.forEach((value, index) => {
- map[value.val] = index > 0 && index < values.length - 1 ? sum : value.count;
- sum -= value.count;
- });
- return map;
-};
-
-const getFacetsMap = facets => {
- const map = {};
- facets.forEach(facet => {
- const property = mapMetricToProperty(facet.property);
- const { values } = facet;
- if (REVERSED_FACETS.includes(property)) {
- values.reverse();
- }
- map[property] = CUMULATIVE_FACETS.includes(property)
- ? cumulativeMapFacetValues(values)
- : mapFacetValues(values);
- });
- return map;
-};
-
-const reducer = createMap(
- (state, action) => action.type === actions.RECEIVE_PROJECTS,
- () => false,
- (state, action) => getFacetsMap(action.facets)
-);
-
-export default reducer;
-
-export const getFacetByProperty = (state, property) => state[property];
-
-export const getMaxFacetValue = state => {
- const allValues = flatMap(Object.values(state), facet => Object.values(facet));
- return Math.max.apply(null, allValues);
-};
diff --git a/server/sonar-web/src/main/js/apps/projects/store/projectsDuck.js b/server/sonar-web/src/main/js/apps/projects/store/projectsDuck.js
deleted file mode 100644
index fa23ce9ea43..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/store/projectsDuck.js
+++ /dev/null
@@ -1,51 +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.
- */
-export const actions = {
- RECEIVE_PROJECTS: 'projects/RECEIVE_PROJECTS',
- RECEIVE_MORE_PROJECTS: 'projects/RECEIVE_MORE_PROJECTS'
-};
-
-export const receiveProjects = (projects, facets) => ({
- type: actions.RECEIVE_PROJECTS,
- projects,
- facets
-});
-
-export const receiveMoreProjects = projects => ({
- type: actions.RECEIVE_MORE_PROJECTS,
- projects
-});
-
-const reducer = (state = null, action = {}) => {
- if (action.type === actions.RECEIVE_PROJECTS) {
- return action.projects.map(project => project.key);
- }
-
- if (action.type === actions.RECEIVE_MORE_PROJECTS) {
- const keys = action.projects.map(project => project.key);
- return state != null ? [...state, ...keys] : keys;
- }
-
- return state;
-};
-
-export default reducer;
-
-export const getProjects = state => state;
diff --git a/server/sonar-web/src/main/js/apps/projects/store/reducer.js b/server/sonar-web/src/main/js/apps/projects/store/reducer.js
deleted file mode 100644
index f7713f1ffd7..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/store/reducer.js
+++ /dev/null
@@ -1,34 +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 { combineReducers } from 'redux';
-import projects, * as fromProjects from './projectsDuck';
-import state from './stateDuck';
-import facets, * as fromFacets from './facetsDuck';
-
-export default combineReducers({ projects, state, facets });
-
-export const getProjects = state => fromProjects.getProjects(state.projects);
-
-export const getState = state => state.state;
-
-export const getFacetByProperty = (state, property) =>
- fromFacets.getFacetByProperty(state.facets, property);
-
-export const getMaxFacetValue = state => fromFacets.getMaxFacetValue(state.facets);
diff --git a/server/sonar-web/src/main/js/apps/projects/store/utils.js b/server/sonar-web/src/main/js/apps/projects/store/utils.js
deleted file mode 100644
index 190facb928c..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/store/utils.js
+++ /dev/null
@@ -1,271 +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 { VISUALIZATIONS } from '../utils';
-
-const getAsNumericRating = value => {
- if (value === '' || value == null || isNaN(value)) {
- return null;
- }
- const num = Number(value);
- return num > 0 && num < 7 ? num : null;
-};
-
-const getAsLevel = value => {
- if (value === 'ERROR' || value === 'WARN' || value === 'OK') {
- return value;
- }
- return null;
-};
-
-// TODO Maybe use parseAsString form helpers/query
-const getAsString = value => {
- if (!value) {
- return null;
- }
- return value;
-};
-
-// TODO Maybe move it to helpers/query
-const getAsArray = (values, elementGetter) => {
- if (!values) {
- return null;
- }
- return values.split(',').map(elementGetter);
-};
-
-const getView = rawValue => (rawValue === 'overall' ? undefined : rawValue);
-
-const getVisualization = value => {
- return VISUALIZATIONS.includes(value) ? value : null;
-};
-
-export const parseUrlQuery = urlQuery => ({
- gate: getAsLevel(urlQuery['gate']),
- reliability: getAsNumericRating(urlQuery['reliability']),
- new_reliability: getAsNumericRating(urlQuery['new_reliability']),
- security: getAsNumericRating(urlQuery['security']),
- new_security: getAsNumericRating(urlQuery['new_security']),
- maintainability: getAsNumericRating(urlQuery['maintainability']),
- new_maintainability: getAsNumericRating(urlQuery['new_maintainability']),
- coverage: getAsNumericRating(urlQuery['coverage']),
- new_coverage: getAsNumericRating(urlQuery['new_coverage']),
- duplications: getAsNumericRating(urlQuery['duplications']),
- new_duplications: getAsNumericRating(urlQuery['new_duplications']),
- size: getAsNumericRating(urlQuery['size']),
- new_lines: getAsNumericRating(urlQuery['new_lines']),
- languages: getAsArray(urlQuery['languages'], getAsString),
- tags: getAsArray(urlQuery['tags'], getAsString),
- search: getAsString(urlQuery['search']),
- sort: getAsString(urlQuery['sort']),
- view: getView(urlQuery['view']),
- visualization: getVisualization(urlQuery['visualization'])
-});
-
-export const mapMetricToProperty = metricKey => {
- const map = {
- analysisDate: 'analysis_date',
- reliability_rating: 'reliability',
- new_reliability_rating: 'new_reliability',
- security_rating: 'security',
- new_security_rating: 'new_security',
- sqale_rating: 'maintainability',
- new_maintainability_rating: 'new_maintainability',
- coverage: 'coverage',
- new_coverage: 'new_coverage',
- duplicated_lines_density: 'duplications',
- new_duplicated_lines_density: 'new_duplications',
- ncloc: 'size',
- new_lines: 'new_lines',
- alert_status: 'gate',
- languages: 'languages',
- tags: 'tags',
- query: 'search'
- };
- return map[metricKey];
-};
-
-export const mapPropertyToMetric = property => {
- const map = {
- analysis_date: 'analysisDate',
- reliability: 'reliability_rating',
- new_reliability: 'new_reliability_rating',
- security: 'security_rating',
- new_security: 'new_security_rating',
- maintainability: 'sqale_rating',
- new_maintainability: 'new_maintainability_rating',
- coverage: 'coverage',
- new_coverage: 'new_coverage',
- duplications: 'duplicated_lines_density',
- new_duplications: 'new_duplicated_lines_density',
- size: 'ncloc',
- new_lines: 'new_lines',
- gate: 'alert_status',
- languages: 'languages',
- tags: 'tags',
- search: 'query'
- };
- return map[property];
-};
-
-const convertIssuesRating = (metric, rating) => {
- if (rating > 1 && rating < 5) {
- return `${metric} >= ${rating}`;
- } else {
- return `${metric} = ${rating}`;
- }
-};
-
-const convertCoverage = (metric, coverage) => {
- switch (coverage) {
- case 1:
- return metric + ' >= 80';
- case 2:
- return metric + ' < 80';
- case 3:
- return metric + ' < 70';
- case 4:
- return metric + ' < 50';
- case 5:
- return metric + ' < 30';
- case 6:
- return metric + '= NO_DATA';
- default:
- return '';
- }
-};
-
-const convertDuplications = (metric, duplications) => {
- switch (duplications) {
- case 1:
- return metric + ' < 3';
- case 2:
- return metric + ' >= 3';
- case 3:
- return metric + ' >= 5';
- case 4:
- return metric + ' >= 10';
- case 5:
- return metric + ' >= 20';
- case 6:
- return metric + '= NO_DATA';
- default:
- return '';
- }
-};
-
-const convertSize = (metric, size) => {
- switch (size) {
- case 1:
- return metric + ' < 1000';
- case 2:
- return metric + ' >= 1000';
- case 3:
- return metric + ' >= 10000';
- case 4:
- return metric + ' >= 100000';
- case 5:
- return metric + ' >= 500000';
- default:
- return '';
- }
-};
-
-const convertArrayMetric = (metric, items) => {
- if (!Array.isArray(items) || items.length < 2) {
- return metric + ' = ' + items;
- }
- return `${metric} IN (${items.join(', ')})`;
-};
-
-const pushMetricToArray = (query, property, conditionsArray, convertFunction) => {
- if (query[property] != null) {
- conditionsArray.push(convertFunction(mapPropertyToMetric(property), query[property]));
- }
-};
-
-export const convertToFilter = (query, isFavorite) => {
- const conditions = [];
-
- if (isFavorite) {
- conditions.push('isFavorite');
- }
-
- if (query['gate'] != null) {
- conditions.push(mapPropertyToMetric('gate') + ' = ' + query['gate']);
- }
-
- ['coverage', 'new_coverage'].forEach(property =>
- pushMetricToArray(query, property, conditions, convertCoverage)
- );
-
- ['duplications', 'new_duplications'].forEach(property =>
- pushMetricToArray(query, property, conditions, convertDuplications)
- );
-
- ['size', 'new_lines'].forEach(property =>
- pushMetricToArray(query, property, conditions, convertSize)
- );
-
- [
- 'reliability',
- 'security',
- 'maintainability',
- 'new_reliability',
- 'new_security',
- 'new_maintainability'
- ].forEach(property => pushMetricToArray(query, property, conditions, convertIssuesRating));
-
- ['languages', 'tags'].forEach(property =>
- pushMetricToArray(query, property, conditions, convertArrayMetric)
- );
-
- if (query['search'] != null) {
- conditions.push(`${mapPropertyToMetric('search')} = "${query['search']}"`);
- }
-
- return conditions.join(' and ');
-};
-
-export const convertToSorting = ({ sort }) => {
- if (sort && sort[0] === '-') {
- return { s: mapPropertyToMetric(sort.substr(1)), asc: false };
- }
- return { s: mapPropertyToMetric(sort) };
-};
-
-export const convertToQueryData = (query, isFavorite, organization, defaultData = {}) => {
- const data = { ...defaultData };
- const filter = convertToFilter(query, isFavorite);
- const sort = convertToSorting(query);
-
- if (filter) {
- data.filter = filter;
- }
- if (sort.s) {
- data.s = sort.s;
- }
- if (sort.hasOwnProperty('asc')) {
- data.asc = sort.asc;
- }
- if (organization) {
- data.organization = organization.key;
- }
- return data;
-};
diff --git a/server/sonar-web/src/main/js/apps/projects/types.ts b/server/sonar-web/src/main/js/apps/projects/types.ts
index a78e422460f..27855b610e0 100644
--- a/server/sonar-web/src/main/js/apps/projects/types.ts
+++ b/server/sonar-web/src/main/js/apps/projects/types.ts
@@ -18,8 +18,21 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
export interface Project {
+ analysisDate?: string;
+ isFavorite?: boolean;
key: string;
+ leakPeriodDate?: string;
measures: { [key: string]: string };
name: string;
- organization?: { name: string };
+ organization?: { key: string; name: string };
+ tags: string[];
+ visibility: string;
+}
+
+export interface Facet {
+ [value: string]: number;
+}
+
+export interface Facets {
+ [property: string]: Facet;
}
diff --git a/server/sonar-web/src/main/js/apps/projects/utils.ts b/server/sonar-web/src/main/js/apps/projects/utils.ts
index 6a03e7b57cf..8b99d5ee803 100644
--- a/server/sonar-web/src/main/js/apps/projects/utils.ts
+++ b/server/sonar-web/src/main/js/apps/projects/utils.ts
@@ -17,7 +17,14 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { sumBy, uniq } from 'lodash';
import { translate } from '../../helpers/l10n';
+import { RequestData } from '../../helpers/request';
+import { getOrganizations } from '../../api/organizations';
+import { searchProjects, Facet } from '../../api/components';
+import { getMeasuresForProjects } from '../../api/measures';
+import { isDiffMetric, getPeriodValue } from '../../helpers/measures';
+import { Query, convertToFilter } from './query';
interface SortingOption {
class?: string;
@@ -74,6 +81,84 @@ export const VISUALIZATIONS = [
'duplications'
];
+const PAGE_SIZE = 50;
+const PAGE_SIZE_VISUALIZATIONS = 99;
+
+const METRICS = [
+ 'alert_status',
+ 'reliability_rating',
+ 'security_rating',
+ 'sqale_rating',
+ 'duplicated_lines_density',
+ 'coverage',
+ 'ncloc',
+ 'ncloc_language_distribution'
+];
+
+const LEAK_METRICS = [
+ 'alert_status',
+ 'new_bugs',
+ 'new_reliability_rating',
+ 'new_vulnerabilities',
+ 'new_security_rating',
+ 'new_code_smells',
+ 'new_maintainability_rating',
+ 'new_coverage',
+ 'new_duplicated_lines_density',
+ 'new_lines'
+];
+
+const METRICS_BY_VISUALIZATION: { [x: string]: string[] } = {
+ risk: ['reliability_rating', 'security_rating', 'coverage', 'ncloc', 'sqale_index'],
+ // x, y, size, color
+ reliability: ['ncloc', 'reliability_remediation_effort', 'bugs', 'reliability_rating'],
+ security: ['ncloc', 'security_remediation_effort', 'vulnerabilities', 'security_rating'],
+ maintainability: ['ncloc', 'sqale_index', 'code_smells', 'sqale_rating'],
+ coverage: ['complexity', 'coverage', 'uncovered_lines'],
+ duplications: ['ncloc', 'duplicated_lines', 'duplicated_blocks']
+};
+
+const FACETS = [
+ 'reliability_rating',
+ 'security_rating',
+ 'sqale_rating',
+ 'coverage',
+ 'duplicated_lines_density',
+ 'ncloc',
+ 'alert_status',
+ 'languages',
+ 'tags'
+];
+
+const LEAK_FACETS = [
+ 'new_reliability_rating',
+ 'new_security_rating',
+ 'new_maintainability_rating',
+ 'new_coverage',
+ 'new_duplicated_lines_density',
+ 'new_lines',
+ 'alert_status',
+ 'languages',
+ 'tags'
+];
+
+const CUMULATIVE_FACETS = [
+ 'reliability',
+ 'new_reliability',
+ 'security',
+ 'new_security',
+ 'maintainability',
+ 'new_maintainability',
+ 'coverage',
+ 'new_coverage',
+ 'duplications',
+ 'new_duplications',
+ 'size',
+ 'new_lines'
+];
+
+const REVERSED_FACETS = ['coverage', 'new_coverage'];
+
export function localizeSorting(sort?: string): string {
return translate('projects.sort', sort || 'name');
}
@@ -82,3 +167,191 @@ export function parseSorting(sort: string): { sortValue: string; sortDesc: boole
const desc = sort[0] === '-';
return { sortValue: desc ? sort.substr(1) : sort, sortDesc: desc };
}
+
+export function fetchProjects(
+ query: Query,
+ isFavorite: boolean,
+ organization?: string,
+ pageIndex = 1
+) {
+ const ps = query.view === 'visualizations' ? PAGE_SIZE_VISUALIZATIONS : PAGE_SIZE;
+ const data = convertToQueryData(query, isFavorite, organization, {
+ p: pageIndex > 1 ? pageIndex : undefined,
+ ps,
+ facets: defineFacets(query).join(),
+ f: 'analysisDate,leakPeriodDate'
+ });
+ return searchProjects(data).then(({ components, facets, paging }) => {
+ return Promise.all([
+ fetchProjectMeasures(components, query),
+ fetchProjectOrganizations(components)
+ ]).then(([measures, organizations]) => {
+ return {
+ facets: getFacetsMap(facets),
+ projects: components
+ .map(component => {
+ const componentMeasures: { [key: string]: string } = {};
+ measures.filter(measure => measure.component === component.key).forEach(measure => {
+ const value = isDiffMetric(measure.metric)
+ ? getPeriodValue(measure, 1)
+ : measure.value;
+ if (value != undefined) {
+ componentMeasures[measure.metric] = value;
+ }
+ });
+ return { ...component, measures: componentMeasures };
+ })
+ .map(component => {
+ const organization = organizations.find(o => o.key === component.organization);
+ return { ...component, organization };
+ }),
+ total: paging.total
+ };
+ });
+ });
+}
+
+function defineMetrics(query: Query): string[] {
+ switch (query.view) {
+ case 'visualizations':
+ return METRICS_BY_VISUALIZATION[query.visualization || 'risk'];
+ case 'leak':
+ return LEAK_METRICS;
+ default:
+ return METRICS;
+ }
+}
+
+function defineFacets(query: Query): string[] {
+ if (query.view === 'leak') {
+ return LEAK_FACETS;
+ }
+ return FACETS;
+}
+
+function convertToQueryData(
+ query: Query,
+ isFavorite: boolean,
+ organization?: string,
+ defaultData = {}
+) {
+ const data: RequestData = { ...defaultData, organization };
+ const filter = convertToFilter(query, isFavorite);
+ const sort = convertToSorting(query as any);
+
+ if (filter) {
+ data.filter = filter;
+ }
+ if (sort.s) {
+ data.s = sort.s;
+ }
+ if (sort.hasOwnProperty('asc')) {
+ data.asc = sort.asc;
+ }
+ return data;
+}
+
+function fetchProjectMeasures(projects: Array<{ key: string }>, query: Query) {
+ if (!projects.length) {
+ return Promise.resolve([]);
+ }
+
+ const projectKeys = projects.map(project => project.key);
+ const metrics = defineMetrics(query);
+ return getMeasuresForProjects(projectKeys, metrics);
+}
+
+function fetchProjectOrganizations(projects: Array<{ organization: string }>) {
+ if (!projects.length) {
+ return Promise.resolve([]);
+ }
+
+ const organizations = uniq(projects.map(project => project.organization));
+ return getOrganizations(organizations).then(r => r.organizations);
+}
+
+function mapFacetValues(values: Array<{ val: string; count: number }>) {
+ const map: { [value: string]: number } = {};
+ values.forEach(value => {
+ map[value.val] = value.count;
+ });
+ return map;
+}
+
+function cumulativeMapFacetValues(values: Array<{ val: string; count: number }>) {
+ const map: { [value: string]: number } = {};
+ let sum = sumBy(values, value => value.count);
+ values.forEach((value, index) => {
+ map[value.val] = index > 0 && index < values.length - 1 ? sum : value.count;
+ sum -= value.count;
+ });
+ return map;
+}
+
+function getFacetsMap(facets: Facet[]) {
+ const map: { [property: string]: { [value: string]: number } } = {};
+ facets.forEach(facet => {
+ const property = mapMetricToProperty(facet.property);
+ const { values } = facet;
+ if (REVERSED_FACETS.includes(property)) {
+ values.reverse();
+ }
+ map[property] = CUMULATIVE_FACETS.includes(property)
+ ? cumulativeMapFacetValues(values)
+ : mapFacetValues(values);
+ });
+ return map;
+}
+
+function mapPropertyToMetric(property?: string) {
+ const map: { [property: string]: string } = {
+ analysis_date: 'analysisDate',
+ reliability: 'reliability_rating',
+ new_reliability: 'new_reliability_rating',
+ security: 'security_rating',
+ new_security: 'new_security_rating',
+ maintainability: 'sqale_rating',
+ new_maintainability: 'new_maintainability_rating',
+ coverage: 'coverage',
+ new_coverage: 'new_coverage',
+ duplications: 'duplicated_lines_density',
+ new_duplications: 'new_duplicated_lines_density',
+ size: 'ncloc',
+ new_lines: 'new_lines',
+ gate: 'alert_status',
+ languages: 'languages',
+ tags: 'tags',
+ search: 'query'
+ };
+ return property && map[property];
+}
+
+function convertToSorting({ sort }: Query): { s?: string; asc?: boolean } {
+ if (sort && sort[0] === '-') {
+ return { s: mapPropertyToMetric(sort.substr(1)), asc: false };
+ }
+ return { s: mapPropertyToMetric(sort) };
+}
+
+function mapMetricToProperty(metricKey: string) {
+ const map: { [metric: string]: string } = {
+ analysisDate: 'analysis_date',
+ reliability_rating: 'reliability',
+ new_reliability_rating: 'new_reliability',
+ security_rating: 'security',
+ new_security_rating: 'new_security',
+ sqale_rating: 'maintainability',
+ new_maintainability_rating: 'new_maintainability',
+ coverage: 'coverage',
+ new_coverage: 'new_coverage',
+ duplicated_lines_density: 'duplications',
+ new_duplicated_lines_density: 'new_duplications',
+ ncloc: 'size',
+ new_lines: 'new_lines',
+ alert_status: 'gate',
+ languages: 'languages',
+ tags: 'tags',
+ query: 'search'
+ };
+ return map[metricKey];
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/Visualizations.tsx b/server/sonar-web/src/main/js/apps/projects/visualizations/Visualizations.tsx
index 32070c159b5..4443de65c32 100644
--- a/server/sonar-web/src/main/js/apps/projects/visualizations/Visualizations.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/visualizations/Visualizations.tsx
@@ -30,7 +30,7 @@ import { translate, translateWithParameters } from '../../../helpers/l10n';
interface Props {
displayOrganizations: boolean;
- projects?: Project[];
+ projects: Project[];
sort?: string;
total?: number;
visualization: string;
diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/VisualizationsContainer.ts b/server/sonar-web/src/main/js/apps/projects/visualizations/VisualizationsContainer.ts
deleted file mode 100644
index 3b22d353e8e..00000000000
--- a/server/sonar-web/src/main/js/apps/projects/visualizations/VisualizationsContainer.ts
+++ /dev/null
@@ -1,49 +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 { connect } from 'react-redux';
-import Visualizations from './Visualizations';
-import {
- getProjects,
- getComponent,
- getComponentMeasures,
- getOrganizationByKey,
- getProjectsAppState,
- areThereCustomOrganizations
-} from '../../../store/rootReducer';
-
-const mapStateToProps = (state: any) => {
- const projectKeys: string[] = getProjects(state) || [];
- const projects = projectKeys.map(key => {
- const component = getComponent(state, key);
- return {
- ...component,
- measures: getComponentMeasures(state, key) || {},
- organization: getOrganizationByKey(state, component.organization)
- };
- });
- const appState = getProjectsAppState(state);
- return {
- projects,
- total: appState.total,
- displayOrganizations: areThereCustomOrganizations(state)
- };
-};
-
-export default connect<any, any, any>(mapStateToProps)(Visualizations);
diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Risk-test.tsx b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Risk-test.tsx
index dcfbc066e39..1f820a8b484 100644
--- a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Risk-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Risk-test.tsx
@@ -25,7 +25,9 @@ it('renders', () => {
const project1 = {
key: 'foo',
measures: { complexity: '17.2', coverage: '53.5', ncloc: '1734' },
- name: 'Foo'
+ name: 'Foo',
+ tags: [],
+ visibility: 'public'
};
expect(shallow(<Risk displayOrganizations={false} projects={[project1]} />)).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/SimpleBubbleChart-test.tsx b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/SimpleBubbleChart-test.tsx
index 7eda7c9762f..b797a6c7ed5 100644
--- a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/SimpleBubbleChart-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/SimpleBubbleChart-test.tsx
@@ -25,7 +25,9 @@ it('renders', () => {
const project1 = {
key: 'foo',
measures: { complexity: '17.2', coverage: '53.5', ncloc: '1734' },
- name: 'Foo'
+ name: 'Foo',
+ tags: [],
+ visibility: 'public'
};
expect(
shallow(
diff --git a/server/sonar-web/src/main/js/components/controls/Favorite.js b/server/sonar-web/src/main/js/components/controls/Favorite.tsx
index 4faf4e186e6..fb43626c05c 100644
--- a/server/sonar-web/src/main/js/components/controls/Favorite.js
+++ b/server/sonar-web/src/main/js/components/controls/Favorite.tsx
@@ -17,28 +17,23 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import React from 'react';
-import PropTypes from 'prop-types';
+import * as React from 'react';
import FavoriteBase from './FavoriteBase';
import { addFavorite, removeFavorite } from '../../api/favorites';
-export default class Favorite extends React.PureComponent {
- static propTypes = {
- favorite: PropTypes.bool.isRequired,
- component: PropTypes.string.isRequired,
- className: PropTypes.string
- };
-
- render() {
- const { favorite, component, ...other } = this.props;
+interface Props {
+ className?: string;
+ component: string;
+ favorite: boolean;
+}
- return (
- <FavoriteBase
- {...other}
- favorite={favorite}
- addFavorite={() => addFavorite(component)}
- removeFavorite={() => removeFavorite(component)}
- />
- );
- }
+export default function Favorite({ favorite, component, ...other }: Props) {
+ return (
+ <FavoriteBase
+ {...other}
+ favorite={favorite}
+ addFavorite={() => addFavorite(component)}
+ removeFavorite={() => removeFavorite(component)}
+ />
+ );
}
diff --git a/server/sonar-web/src/main/js/components/controls/FavoriteBase.js b/server/sonar-web/src/main/js/components/controls/FavoriteBase.tsx
index d35340dbec5..fc76186117b 100644
--- a/server/sonar-web/src/main/js/components/controls/FavoriteBase.js
+++ b/server/sonar-web/src/main/js/components/controls/FavoriteBase.tsx
@@ -17,30 +17,34 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import React from 'react';
-import PropTypes from 'prop-types';
-import classNames from 'classnames';
+import * as React from 'react';
+import * as classNames from 'classnames';
import FavoriteIcon from '../icons-components/FavoriteIcon';
-export default class FavoriteBase extends React.PureComponent {
- static propTypes = {
- favorite: PropTypes.bool.isRequired,
- addFavorite: PropTypes.func.isRequired,
- removeFavorite: PropTypes.func.isRequired,
- className: PropTypes.string
- };
+interface Props {
+ addFavorite: () => Promise<void>;
+ className?: string;
+ favorite: boolean;
+ removeFavorite: () => Promise<void>;
+}
+
+interface State {
+ favorite: boolean;
+}
- constructor(props) {
+export default class FavoriteBase extends React.PureComponent<Props, State> {
+ mounted: boolean;
+
+ constructor(props: Props) {
super(props);
this.state = { favorite: this.props.favorite };
}
- componentWillMount() {
+ componentDidMount() {
this.mounted = true;
- this.toggleFavorite = this.toggleFavorite.bind(this);
}
- componentWillReceiveProps(nextProps) {
+ componentWillReceiveProps(nextProps: Props) {
if (nextProps.favorite !== this.props.favorite && nextProps.favorite !== this.state.favorite) {
this.setState({ favorite: nextProps.favorite });
}
@@ -50,14 +54,14 @@ export default class FavoriteBase extends React.PureComponent {
this.mounted = false;
}
- toggleFavorite(e) {
- e.preventDefault();
+ toggleFavorite = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+ event.preventDefault();
if (this.state.favorite) {
this.removeFavorite();
} else {
this.addFavorite();
}
- }
+ };
addFavorite() {
this.props.addFavorite().then(() => {
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.ts b/server/sonar-web/src/main/js/components/controls/__tests__/Favorite-test.tsx
index 3d0429e6e3c..0601fc5bd29 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.ts
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/Favorite-test.tsx
@@ -17,12 +17,10 @@
* 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 { getLanguages } from '../../../store/rootReducer';
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import Favorite from '../Favorite';
-const mapStateToProps = (state: any) => ({
- languages: getLanguages(state)
+it('renders', () => {
+ expect(shallow(<Favorite component="foo" favorite={true} />)).toMatchSnapshot();
});
-
-export default connect<any, any, any>(mapStateToProps)(ProjectCardLanguages);
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.js b/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.tsx
index 86ab63dbc6c..a6231ceb7ff 100644
--- a/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.js
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.tsx
@@ -17,17 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import * as React from 'react';
import { shallow } from 'enzyme';
-import React from 'react';
import FavoriteBase from '../FavoriteBase';
import { click } from '../../../helpers/testUtils';
-function renderFavoriteBase(props) {
- return shallow(
- <FavoriteBase favorite={true} addFavorite={jest.fn()} removeFavorite={jest.fn()} {...props} />
- );
-}
-
it('should render favorite', () => {
const favorite = renderFavoriteBase({ favorite: true });
expect(favorite).toMatchSnapshot();
@@ -51,3 +45,9 @@ it('should remove favorite', () => {
click(favorite.find('a'));
expect(removeFavorite).toBeCalled();
});
+
+function renderFavoriteBase(props?: any) {
+ return shallow(
+ <FavoriteBase favorite={true} addFavorite={jest.fn()} removeFavorite={jest.fn()} {...props} />
+ );
+}
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Favorite-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Favorite-test.tsx.snap
new file mode 100644
index 00000000000..dd978f6e724
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Favorite-test.tsx.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<FavoriteBase
+ addFavorite={[Function]}
+ favorite={true}
+ removeFavorite={[Function]}
+/>
+`;
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/FavoriteBase-test.js.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/FavoriteBase-test.tsx.snap
index c7cccad562c..98dc0d71b5b 100644
--- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/FavoriteBase-test.js.snap
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/FavoriteBase-test.tsx.snap
@@ -8,7 +8,6 @@ exports[`should render favorite 1`] = `
>
<FavoriteIcon
favorite={true}
- size={16}
/>
</a>
`;
@@ -21,7 +20,6 @@ exports[`should render not favorite 1`] = `
>
<FavoriteIcon
favorite={false}
- size={16}
/>
</a>
`;
diff --git a/server/sonar-web/src/main/js/helpers/measures.ts b/server/sonar-web/src/main/js/helpers/measures.ts
index 41dec890089..8c200a239ac 100644
--- a/server/sonar-web/src/main/js/helpers/measures.ts
+++ b/server/sonar-web/src/main/js/helpers/measures.ts
@@ -85,7 +85,7 @@ export function enhanceMeasuresWithMetrics(
}
/** Get period value of a measure */
-export function getPeriodValue(measure: Measure, periodIndex: number): string | number | undefined {
+export function getPeriodValue(measure: Measure, periodIndex: number): string | undefined {
const { periods } = measure;
const period = periods && periods.find(period => period.index === periodIndex);
return period ? period.value : undefined;
diff --git a/server/sonar-web/src/main/js/store/components/actions.js b/server/sonar-web/src/main/js/store/components/actions.js
deleted file mode 100644
index 873640f7092..00000000000
--- a/server/sonar-web/src/main/js/store/components/actions.js
+++ /dev/null
@@ -1,32 +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.
- */
-export const RECEIVE_COMPONENTS = 'RECEIVE_COMPONENTS';
-export const RECEIVE_PROJECT_TAGS = 'RECEIVE_PROJECT_TAGS';
-
-export const receiveComponents = components => ({
- type: RECEIVE_COMPONENTS,
- components
-});
-
-export const receiveProjectTags = (project, tags) => ({
- type: RECEIVE_PROJECT_TAGS,
- project,
- tags
-});
diff --git a/server/sonar-web/src/main/js/store/components/reducer.js b/server/sonar-web/src/main/js/store/components/reducer.js
deleted file mode 100644
index e8a96a36685..00000000000
--- a/server/sonar-web/src/main/js/store/components/reducer.js
+++ /dev/null
@@ -1,51 +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 { combineReducers } from 'redux';
-import { keyBy, uniq } from 'lodash';
-import { RECEIVE_COMPONENTS, RECEIVE_PROJECT_TAGS } from './actions';
-
-const byKey = (state = {}, action = {}) => {
- if (action.type === RECEIVE_COMPONENTS) {
- const changes = keyBy(action.components, 'key');
- return { ...state, ...changes };
- }
-
- if (action.type === RECEIVE_PROJECT_TAGS) {
- const project = state[action.project];
- if (project) {
- return { ...state, [action.project]: { ...project, tags: action.tags } };
- }
- }
-
- return state;
-};
-
-const keys = (state = [], action = {}) => {
- if (action.type === RECEIVE_COMPONENTS) {
- const changes = action.components.map(f => f.key);
- return uniq([...state, ...changes]);
- }
-
- return state;
-};
-
-export default combineReducers({ byKey, keys });
-
-export const getComponent = (state, key) => state.byKey[key];
diff --git a/server/sonar-web/src/main/js/store/measures/actions.js b/server/sonar-web/src/main/js/store/measures/actions.js
deleted file mode 100644
index a239d303e2d..00000000000
--- a/server/sonar-web/src/main/js/store/measures/actions.js
+++ /dev/null
@@ -1,42 +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.
- */
-export const RECEIVE_COMPONENT_MEASURE = 'RECEIVE_COMPONENT_MEASURE';
-
-export const receiveComponentMeasure = (componentKey, metricKey, value) => ({
- type: RECEIVE_COMPONENT_MEASURE,
- componentKey,
- metricKey,
- value
-});
-
-export const RECEIVE_COMPONENT_MEASURES = 'RECEIVE_COMPONENT_MEASURES';
-
-export const receiveComponentMeasures = (componentKey, measures) => ({
- type: RECEIVE_COMPONENT_MEASURES,
- componentKey,
- measures
-});
-
-export const RECEIVE_COMPONENTS_MEASURES = 'RECEIVE_COMPONENTS_MEASURES';
-
-export const receiveComponentsMeasures = componentsWithMeasures => ({
- type: RECEIVE_COMPONENTS_MEASURES,
- componentsWithMeasures
-});
diff --git a/server/sonar-web/src/main/js/store/measures/reducer.js b/server/sonar-web/src/main/js/store/measures/reducer.js
deleted file mode 100644
index 4c89ebab8ea..00000000000
--- a/server/sonar-web/src/main/js/store/measures/reducer.js
+++ /dev/null
@@ -1,67 +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 {
- RECEIVE_COMPONENT_MEASURE,
- RECEIVE_COMPONENT_MEASURES,
- RECEIVE_COMPONENTS_MEASURES
-} from './actions';
-
-const byMetricKey = (state = {}, action = {}) => {
- if (action.type === RECEIVE_COMPONENT_MEASURE) {
- return { ...state, [action.metricKey]: action.value };
- }
-
- if (action.type === RECEIVE_COMPONENT_MEASURES) {
- return { ...state, ...action.measures };
- }
-
- return state;
-};
-
-const reducer = (state = {}, action = {}) => {
- if ([RECEIVE_COMPONENT_MEASURE, RECEIVE_COMPONENT_MEASURES].includes(action.type)) {
- const component = state[action.componentKey];
- return { ...state, [action.componentKey]: byMetricKey(component, action) };
- }
-
- if (action.type === RECEIVE_COMPONENTS_MEASURES) {
- const newState = { ...state };
- Object.keys(action.componentsWithMeasures).forEach(componentKey => {
- Object.assign(newState, {
- [componentKey]: byMetricKey(state[componentKey], {
- type: RECEIVE_COMPONENT_MEASURES,
- measures: action.componentsWithMeasures[componentKey]
- })
- });
- });
- return newState;
- }
-
- return state;
-};
-
-export default reducer;
-
-export const getComponentMeasure = (state, componentKey, metricKey) => {
- const component = state[componentKey];
- return component && component[metricKey];
-};
-
-export const getComponentMeasures = (state, componentKey) => state[componentKey];
diff --git a/server/sonar-web/src/main/js/store/rootReducer.js b/server/sonar-web/src/main/js/store/rootReducer.js
index ef3e95d1931..7ab8741e79f 100644
--- a/server/sonar-web/src/main/js/store/rootReducer.js
+++ b/server/sonar-web/src/main/js/store/rootReducer.js
@@ -19,11 +19,9 @@
*/
import { combineReducers } from 'redux';
import appState from './appState/duck';
-import components, * as fromComponents from './components/reducer';
import users, * as fromUsers from './users/reducer';
import favorites, * as fromFavorites from './favorites/duck';
import languages, * as fromLanguages from './languages/reducer';
-import measures, * as fromMeasures from './measures/reducer';
import metrics, * as fromMetrics from './metrics/reducer';
import notifications, * as fromNotifications from './notifications/duck';
import organizations, * as fromOrganizations from './organizations/duck';
@@ -31,17 +29,14 @@ import organizationsMembers, * as fromOrganizationsMembers from './organizations
import globalMessages, * as fromGlobalMessages from './globalMessages/duck';
import permissionsApp, * as fromPermissionsApp from '../apps/permissions/shared/store/rootReducer';
import projectAdminApp, * as fromProjectAdminApp from '../apps/project-admin/store/rootReducer';
-import projectsApp, * as fromProjectsApp from '../apps/projects/store/reducer';
import qualityGatesApp from '../apps/quality-gates/store/rootReducer';
import settingsApp, * as fromSettingsApp from '../apps/settings/store/rootReducer';
export default combineReducers({
appState,
- components,
globalMessages,
favorites,
languages,
- measures,
metrics,
notifications,
organizations,
@@ -51,15 +46,12 @@ export default combineReducers({
// apps
permissionsApp,
projectAdminApp,
- projectsApp,
qualityGatesApp,
settingsApp
});
export const getAppState = state => state.appState;
-export const getComponent = (state, key) => fromComponents.getComponent(state.components, key);
-
export const getGlobalMessages = state =>
fromGlobalMessages.getGlobalMessages(state.globalMessages);
@@ -81,12 +73,6 @@ export const getUsers = state => fromUsers.getUsers(state.users);
export const isFavorite = (state, componentKey) =>
fromFavorites.isFavorite(state.favorites, componentKey);
-export const getComponentMeasure = (state, componentKey, metricKey) =>
- fromMeasures.getComponentMeasure(state.measures, componentKey, metricKey);
-
-export const getComponentMeasures = (state, componentKey) =>
- fromMeasures.getComponentMeasures(state.measures, componentKey);
-
export const getMetrics = state => fromMetrics.getMetrics(state.metrics);
export const getMetricByKey = (state, key) => fromMetrics.getMetricByKey(state.metrics, key);
@@ -126,16 +112,6 @@ export const getOrganizationMembersLogins = (state, organization) =>
export const getOrganizationMembersState = (state, organization) =>
fromOrganizationsMembers.getOrganizationMembersState(state.organizationsMembers, organization);
-export const getProjects = state => fromProjectsApp.getProjects(state.projectsApp);
-
-export const getProjectsAppState = state => fromProjectsApp.getState(state.projectsApp);
-
-export const getProjectsAppFacetByProperty = (state, property) =>
- fromProjectsApp.getFacetByProperty(state.projectsApp, property);
-
-export const getProjectsAppMaxFacetValue = state =>
- fromProjectsApp.getMaxFacetValue(state.projectsApp);
-
export const getQualityGatesAppState = state => state.qualityGatesApp;
export const getPermissionsAppUsers = state => fromPermissionsApp.getUsers(state.permissionsApp);