From 1689d1f1ca71dfac66682fd87b4ce56ad58dfce0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Mon, 22 May 2017 17:14:37 +0200 Subject: [PATCH] SONAR-9245 Create the leak view on projects page --- .../component-measures/components/Measure.js | 6 +- .../apps/projects/components/AllProjects.js | 5 +- .../apps/projects/components/ProjectCard.js | 146 +++++++++--------- .../components/ProjectCardLeakMeasures.js | 135 ++++++++++++++++ .../components/ProjectCardMeasures.js | 132 ---------------- .../components/ProjectCardOverallMeasures.js | 132 ++++++++++++++++ .../apps/projects/components/ProjectsList.js | 17 +- .../main/js/apps/projects/store/actions.js | 29 +++- .../src/main/js/apps/projects/store/utils.js | 2 +- .../src/main/js/apps/projects/styles.css | 25 ++- .../src/main/js/apps/projects/utils.js | 2 +- .../src/main/js/components/ui/BugIcon.js | 9 +- .../main/js/components/ui/CodeSmellIcon.js | 9 +- .../js/components/ui/VulnerabilityIcon.js | 9 +- .../resources/org/sonar/l10n/core.properties | 3 + 15 files changed, 433 insertions(+), 228 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.js delete mode 100644 server/sonar-web/src/main/js/apps/projects/components/ProjectCardMeasures.js create mode 100644 server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.js diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/Measure.js b/server/sonar-web/src/main/js/apps/component-measures/components/Measure.js index 520852b4367..3b3672ed4f1 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/Measure.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/Measure.js @@ -26,6 +26,7 @@ import { formatLeak, isDiffMetric, getRatingTooltip } from '../utils'; export default class Measure extends React.PureComponent { static propTypes = { + className: React.PropTypes.string, measure: React.PropTypes.object, metric: React.PropTypes.object, decimals: React.PropTypes.number @@ -52,7 +53,7 @@ export default class Measure extends React.PureComponent { } render() { - const { measure, metric, decimals } = this.props; + const { measure, metric, decimals, className } = this.props; const finalMetric = metric || measure.metric; if (finalMetric.type === 'RATING') { @@ -66,9 +67,8 @@ export default class Measure extends React.PureComponent { const formattedValue = isDiffMetric(finalMetric) ? formatLeak(measure.leak, finalMetric, { decimals }) : formatMeasure(measure.value, finalMetric.type, { decimals }); - return ( - + {formattedValue != null ? formattedValue : '–'} ); diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js index 55e16b34019..2dcc5720562 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js @@ -131,13 +131,14 @@ export default class AllProjects extends React.PureComponent {
- {view === 'overall' && + {view !== 'visualizations' && } - {view === 'overall' && + {view !== 'visualizations' && , - isFavorite?: boolean, - organization?: string - } - }; +type Props = { + measures: { [string]: string }, + organization?: { key: string }, + project?: { + analysisDate?: string, + key: string, + name: string, + tags: Array, + isFavorite?: boolean, + organization?: string + }, + type?: string +}; - render() { - const { project } = this.props; +export default function ProjectCard({ measures, organization, project, type }: Props) { + if (project == null) { + return null; + } - if (project == null) { - return null; - } + const isProjectAnalyzed = project.analysisDate != null; + const isLeakView = type === 'leak'; - const isProjectAnalyzed = project.analysisDate != null; - // check reliability_rating because only some measures can be loaded - // if coming from visualizations tab - const areProjectMeasuresLoaded = - !isProjectAnalyzed || - (this.props.measures != null && - this.props.measures['reliability_rating'] != null && - this.props.measures['sqale_rating'] != null); - const displayQualityGate = areProjectMeasuresLoaded && isProjectAnalyzed; + let areProjectMeasuresLoaded; + // check for particular measures because only some measures can be loaded + // if coming from visualizations tab + if (isLeakView) { + areProjectMeasuresLoaded = measures != null && measures['new_bugs']; + } else { + areProjectMeasuresLoaded = + measures != null && + measures['reliability_rating'] != null && + measures['sqale_rating'] != null; + } - const className = classNames('boxed-group', 'project-card', { - 'boxed-group-loading': !areProjectMeasuresLoaded - }); + const displayQualityGate = areProjectMeasuresLoaded && isProjectAnalyzed; + const className = classNames('boxed-group', 'project-card', { + 'boxed-group-loading': isProjectAnalyzed && !areProjectMeasuresLoaded + }); - return ( -
- {displayQualityGate && -
- -
} + return ( +
+ {displayQualityGate && +
+ +
} -
- {project.isFavorite != null && - } -

- {this.props.organization == null && - project.organization != null && - - - } - - {project.name} - -

- {project.visibility === 'private' && } - {project.tags.length > 0 && } -
+
+ {project.isFavorite != null && + } +

+ {organization == null && + project.organization != null && + + + } + {project.name} +

+ {project.visibility === 'private' && } + {project.tags.length > 0 && } +
- {isProjectAnalyzed - ?
- {areProjectMeasuresLoaded && } + {isProjectAnalyzed + ?
+ {areProjectMeasuresLoaded && isLeakView + ? + : } +
+ :
+
+ {translate('projects.not_analyzed')}
- :
-
- {translate('projects.not_analyzed')} -
-
} - - {isProjectAnalyzed && -
- {translateWithParameters( - 'overview.last_analysis_on_x', - moment(project.analysisDate).format('LLL') - )}
} -
- ); - } + + {isProjectAnalyzed && +
+ {translateWithParameters( + 'overview.last_analysis_on_x', + moment(project.analysisDate).format('LLL') + )} +
} +
+ ); } diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.js new file mode 100644 index 00000000000..b1490444391 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.js @@ -0,0 +1,135 @@ +/* + * 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 Measure from '../../component-measures/components/Measure'; +import BugIcon from '../../../components/ui/BugIcon'; +import CodeSmellIcon from '../../../components/ui/CodeSmellIcon'; +import Rating from '../../../components/ui/Rating'; +import VulnerabilityIcon from '../../../components/ui/VulnerabilityIcon'; +import { translate } from '../../../helpers/l10n'; + +type Props = { + measures?: { [string]: string } +}; + +export default function ProjectCardLeakMeasures({ measures }: Props) { + if (measures == null) { + return null; + } + + return ( +
+
+
+
+ + +
+
+ + {translate('metric.new_bugs.name')} +
+
+
+ +
+
+
+ + +
+
+ + {translate('metric.new_vulnerabilities.name')} +
+
+
+ +
+
+
+ + +
+
+ + {translate('metric.new_code_smells.name')} +
+
+
+ +
+
+
+ +
+
+ {translate('metric.new_coverage.name')} +
+
+
+ +
+
+
+ +
+
+ {translate('metric.new_duplicated_lines_density.short_name')} +
+
+
+ + {measures['new_lines'] != null && +
+
+
+ +
+
+ {translate('metric.new_lines.short_name')} +
+
+
} +
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardMeasures.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardMeasures.js deleted file mode 100644 index 6d08f16980e..00000000000 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardMeasures.js +++ /dev/null @@ -1,132 +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 React from 'react'; -import ProjectCardLanguages from './ProjectCardLanguages'; -import Measure from '../../component-measures/components/Measure'; -import Rating from '../../../components/ui/Rating'; -import CoverageRating from '../../../components/ui/CoverageRating'; -import DuplicationsRating from '../../../components/ui/DuplicationsRating'; -import SizeRating from '../../../components/ui/SizeRating'; -import { translate } from '../../../helpers/l10n'; - -export default class ProjectCardMeasures extends React.PureComponent { - static propTypes = { - measures: React.PropTypes.object - }; - - render() { - const { measures } = this.props; - - if (measures == null) { - return null; - } - - return ( -
-
-
-
- -
-
- {translate('metric_domain.Reliability')} -
-
-
- -
-
-
- -
-
- {translate('metric_domain.Security')} -
-
-
- -
-
-
- -
-
- {translate('metric_domain.Maintainability')} -
-
-
- -
-
-
- {measures['coverage'] != null && - - - } - -
-
- {translate('metric.coverage.name')} -
-
-
- -
-
-
- {measures['duplicated_lines_density'] != null && - - - } - -
-
- {translate('metric.duplicated_lines_density.short_name')} -
-
-
- - {measures['ncloc'] != null && -
-
-
- - - - -
-
- -
-
-
} -
- ); - } -} diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.js new file mode 100644 index 00000000000..883b8039953 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.js @@ -0,0 +1,132 @@ +/* + * 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 ProjectCardLanguages from './ProjectCardLanguages'; +import Measure from '../../component-measures/components/Measure'; +import Rating from '../../../components/ui/Rating'; +import CoverageRating from '../../../components/ui/CoverageRating'; +import DuplicationsRating from '../../../components/ui/DuplicationsRating'; +import SizeRating from '../../../components/ui/SizeRating'; +import { translate } from '../../../helpers/l10n'; +import { formatMeasure } from '../../../helpers/measures'; + +type Props = { + measures?: { [string]: string } +}; + +export default function ProjectCardMeasures({ measures }: Props) { + if (measures == null) { + return null; + } + + return ( +
+
+
+
+ +
+
+ {translate('metric_domain.Reliability')} +
+
+
+ +
+
+
+ +
+
+ {translate('metric_domain.Security')} +
+
+
+ +
+
+
+ +
+
+ {translate('metric_domain.Maintainability')} +
+
+
+ +
+
+
+ {measures['coverage'] != null && + + + } + +
+
+ {translate('metric.coverage.name')} +
+
+
+ +
+
+
+ {measures['duplicated_lines_density'] != null && + + + } + +
+
+ {translate('metric.duplicated_lines_density.short_name')} +
+
+
+ + {measures['ncloc'] != null && +
+
+
+ + + + +
+
+ +
+
+
} +
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js index d692e2ca9ce..e9eea0c0a88 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js @@ -17,19 +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. */ +//@flow import React from 'react'; import ProjectCardContainer from './ProjectCardContainer'; import NoFavoriteProjects from './NoFavoriteProjects'; import EmptyInstance from './EmptyInstance'; import EmptySearch from '../../../components/common/EmptySearch'; +type Props = { + projects?: Array, + isFavorite: boolean, + isFiltered: boolean, + organization?: { key: string }, + cardType?: string +}; + export default class ProjectsList extends React.PureComponent { - static propTypes = { - projects: React.PropTypes.arrayOf(React.PropTypes.string), - isFavorite: React.PropTypes.bool.isRequired, - isFiltered: React.PropTypes.bool.isRequired, - organization: React.PropTypes.object - }; + props: Props; renderNoProjects() { if (this.props.isFavorite && !this.props.isFiltered) { @@ -56,6 +60,7 @@ export default class ProjectsList extends React.PureComponent { key={projectKey} projectKey={projectKey} organization={this.props.organization} + type={this.props.cardType} /> )) : this.renderNoProjects()} 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 index 1bd9eacd087..7b5d11e2e47 100644 --- a/server/sonar-web/src/main/js/apps/projects/store/actions.js +++ b/server/sonar-web/src/main/js/apps/projects/store/actions.js @@ -31,6 +31,7 @@ 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; @@ -46,6 +47,19 @@ const METRICS = [ '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 @@ -85,7 +99,9 @@ const onReceiveMeasures = (dispatch, expectedProjectKeys) => response => { Object.keys(byComponentKey).forEach(componentKey => { const measures = {}; byComponentKey[componentKey].forEach(measure => { - measures[measure.metric] = measure.value; + measures[measure.metric] = isDiffMetric(measure.metric) + ? getPeriodValue(measure, 1) + : measure.value; }); toStore[componentKey] = measures; }); @@ -98,10 +114,13 @@ const onReceiveOrganizations = dispatch => response => { }; const defineMetrics = query => { - if (query.view === 'visualizations') { - return METRICS_BY_VISUALIZATION[query.visualization || 'risk']; - } else { - return METRICS; + switch (query.view) { + case 'visualizations': + return METRICS_BY_VISUALIZATION[query.visualization || 'risk']; + case 'leak': + return LEAK_METRICS; + default: + return METRICS; } }; 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 index 8cba5d71531..0316f71ab7f 100644 --- a/server/sonar-web/src/main/js/apps/projects/store/utils.js +++ b/server/sonar-web/src/main/js/apps/projects/store/utils.js @@ -48,7 +48,7 @@ const getAsArray = (values, elementGetter) => { return values.split(',').map(elementGetter); }; -const getView = rawValue => (rawValue === 'visualizations' ? rawValue : undefined); +const getView = rawValue => (rawValue === 'overall' ? undefined : rawValue); const getVisualization = value => { return VISUALIZATIONS.includes(value) ? value : null; diff --git a/server/sonar-web/src/main/js/apps/projects/styles.css b/server/sonar-web/src/main/js/apps/projects/styles.css index d413f3d0cb6..5a94f6006a7 100644 --- a/server/sonar-web/src/main/js/apps/projects/styles.css +++ b/server/sonar-web/src/main/js/apps/projects/styles.css @@ -85,16 +85,39 @@ margin: 0 -15px; } +.project-card-leak-measures { + padding: 4px 0; + margin: 0 -4px 4px; + background-color: #fbf3d5; + border: 1px solid #eae3c7; +} + .project-card-measures .project-card-measure { width: 120px; box-sizing: border-box; padding: 0 15px; } -.project-card-measures .project-card-measure:nth-child(-n+2) { +.project-card-leak-measures .project-card-measure { + width: 140px; + box-sizing: border-box; + padding: 0 5px; +} + +.project-card-measure.smaller-card { width: 90px; } +@media (max-width: 1130px) { + .project-card-leak-measures .project-card-measure { + width: 134px; + padding: 0 2px; + } + .project-card-measure.smaller-card { + width: 81px; + } +} + .project-card-measure { position: relative; display: inline-block; diff --git a/server/sonar-web/src/main/js/apps/projects/utils.js b/server/sonar-web/src/main/js/apps/projects/utils.js index 9fe7815798c..a81d6258c14 100644 --- a/server/sonar-web/src/main/js/apps/projects/utils.js +++ b/server/sonar-web/src/main/js/apps/projects/utils.js @@ -47,7 +47,7 @@ export const saveAll = () => save(LOCALSTORAGE_ALL); export const saveFavorite = () => save(LOCALSTORAGE_FAVORITE); -export const VIEWS = ['overall']; +export const VIEWS = ['overall', 'leak']; export const VISUALIZATIONS = [ 'risk', diff --git a/server/sonar-web/src/main/js/components/ui/BugIcon.js b/server/sonar-web/src/main/js/components/ui/BugIcon.js index ada2170bc5b..5c794e2e080 100644 --- a/server/sonar-web/src/main/js/components/ui/BugIcon.js +++ b/server/sonar-web/src/main/js/components/ui/BugIcon.js @@ -20,10 +20,15 @@ // @flow import React from 'react'; -export default function BugIcon() { +export default function BugIcon({ className }: { className?: string }) { /* eslint-disable max-len */ return ( - + + +