diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-05-22 17:14:37 +0200 |
---|---|---|
committer | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-06-09 08:26:48 +0200 |
commit | 1689d1f1ca71dfac66682fd87b4ce56ad58dfce0 (patch) | |
tree | a0af224d5d10ee2f62b76a190c220ba735667940 /server/sonar-web | |
parent | c26d1c37fdcbc8cbdeb0390648c071297d72d027 (diff) | |
download | sonarqube-1689d1f1ca71dfac66682fd87b4ce56ad58dfce0.tar.gz sonarqube-1689d1f1ca71dfac66682fd87b4ce56ad58dfce0.zip |
SONAR-9245 Create the leak view on projects page
Diffstat (limited to 'server/sonar-web')
14 files changed, 430 insertions, 228 deletions
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 ( - <span> + <span className={className}> {formattedValue != null ? formattedValue : '–'} </span> ); 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 { <div className="layout-page-main"> <div className="layout-page-main-inner"> <PageHeaderContainer onOpenOptionBar={this.openOptionBar} /> - {view === 'overall' && + {view !== 'visualizations' && <ProjectsListContainer isFavorite={this.props.isFavorite} isFiltered={isFiltered} organization={this.props.organization} + cardType={view} />} - {view === 'overall' && + {view !== 'visualizations' && <ProjectsListFooterContainer query={query} isFavorite={this.props.isFavorite} diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js index 40a74de7747..f08708da24d 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js @@ -23,90 +23,94 @@ import classNames from 'classnames'; import moment from 'moment'; import { Link } from 'react-router'; import ProjectCardQualityGate from './ProjectCardQualityGate'; -import ProjectCardMeasures from './ProjectCardMeasures'; +import ProjectCardLeakMeasures from './ProjectCardLeakMeasures'; +import ProjectCardOverallMeasures from './ProjectCardOverallMeasures'; import FavoriteContainer from '../../../components/controls/FavoriteContainer'; import Organization from '../../../components/shared/Organization'; import TagsList from '../../../components/tags/TagsList'; import PrivateBadge from '../../../components/common/PrivateBadge'; import { translate, translateWithParameters } from '../../../helpers/l10n'; -export default class ProjectCard extends React.PureComponent { - props: { - measures: { [string]: string }, - organization?: {}, - project?: { - analysisDate?: string, - key: string, - name: string, - tags: Array<string>, - isFavorite?: boolean, - organization?: string - } - }; +type Props = { + measures: { [string]: string }, + organization?: { key: string }, + project?: { + analysisDate?: string, + key: string, + name: string, + tags: Array<string>, + 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 ( - <div data-key={project.key} className={className}> - {displayQualityGate && - <div className="boxed-group-actions"> - <ProjectCardQualityGate status={this.props.measures['alert_status']} /> - </div>} + return ( + <div data-key={project.key} className={className}> + {displayQualityGate && + <div className="boxed-group-actions"> + <ProjectCardQualityGate status={measures['alert_status']} /> + </div>} - <div className="boxed-group-header"> - {project.isFavorite != null && - <FavoriteContainer className="spacer-right" componentKey={project.key} />} - <h2 className="project-card-name"> - {this.props.organization == null && - project.organization != null && - <span className="text-normal"> - <Organization organizationKey={project.organization} /> - </span>} - <Link to={{ pathname: '/dashboard', query: { id: project.key } }}> - {project.name} - </Link> - </h2> - {project.visibility === 'private' && <PrivateBadge className="spacer-left" />} - {project.tags.length > 0 && <TagsList tags={project.tags} customClass="spacer-left" />} - </div> + <div className="boxed-group-header"> + {project.isFavorite != null && + <FavoriteContainer className="spacer-right" componentKey={project.key} />} + <h2 className="project-card-name"> + {organization == null && + project.organization != null && + <span className="text-normal"> + <Organization organizationKey={project.organization} /> + </span>} + <Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link> + </h2> + {project.visibility === 'private' && <PrivateBadge className="spacer-left" />} + {project.tags.length > 0 && <TagsList tags={project.tags} customClass="spacer-left" />} + </div> - {isProjectAnalyzed - ? <div className="boxed-group-inner"> - {areProjectMeasuresLoaded && <ProjectCardMeasures measures={this.props.measures} />} + {isProjectAnalyzed + ? <div className="boxed-group-inner"> + {areProjectMeasuresLoaded && isLeakView + ? <ProjectCardLeakMeasures measures={measures} /> + : <ProjectCardOverallMeasures measures={measures} />} + </div> + : <div className="boxed-group-inner"> + <div className="note project-card-not-analyzed"> + {translate('projects.not_analyzed')} </div> - : <div className="boxed-group-inner"> - <div className="note project-card-not-analyzed"> - {translate('projects.not_analyzed')} - </div> - </div>} - - {isProjectAnalyzed && - <div className="project-card-analysis-date note"> - {translateWithParameters( - 'overview.last_analysis_on_x', - moment(project.analysisDate).format('LLL') - )} </div>} - </div> - ); - } + + {isProjectAnalyzed && + <div className="project-card-analysis-date note"> + {translateWithParameters( + 'overview.last_analysis_on_x', + moment(project.analysisDate).format('LLL') + )} + </div>} + </div> + ); } 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 ( + <div className="project-card-leak-measures"> + <div className="project-card-measure smaller-card" data-key="new_reliability_rating"> + <div className="project-card-measure-inner"> + <div className="project-card-measure-number"> + <Measure + className="spacer-right" + measure={{ leak: measures['new_bugs'] }} + metric={{ key: 'new_bugs', type: 'SHORT_INT' }} + /> + <Rating value={measures['new_reliability_rating']} /> + </div> + <div className="project-card-measure-label"> + <BugIcon className="little-spacer-right vertical-bottom" /> + {translate('metric.new_bugs.name')} + </div> + </div> + </div> + + <div className="project-card-measure" data-key="new_security_rating"> + <div className="project-card-measure-inner"> + <div className="project-card-measure-number"> + <Measure + className="spacer-right" + measure={{ leak: measures['new_vulnerabilities'] }} + metric={{ key: 'new_vulnerabilities', type: 'SHORT_INT' }} + /> + <Rating value={measures['new_security_rating']} /> + </div> + <div className="project-card-measure-label"> + <VulnerabilityIcon className="little-spacer-right vertical-bottom" /> + {translate('metric.new_vulnerabilities.name')} + </div> + </div> + </div> + + <div className="project-card-measure" data-key="new_maintainability_rating"> + <div className="project-card-measure-inner"> + <div className="project-card-measure-number"> + <Measure + className="spacer-right" + measure={{ leak: measures['new_code_smells'] }} + metric={{ key: 'new_code_smells', type: 'SHORT_INT' }} + /> + <Rating value={measures['new_maintainability_rating']} /> + </div> + <div className="project-card-measure-label"> + <CodeSmellIcon className="little-spacer-right vertical-bottom" /> + {translate('metric.new_code_smells.name')} + </div> + </div> + </div> + + <div className="project-card-measure" data-key="new_coverage"> + <div className="project-card-measure-inner"> + <div className="project-card-measure-number"> + <Measure + measure={{ leak: measures['new_coverage'] }} + metric={{ key: 'new_coverage', type: 'PERCENT' }} + /> + </div> + <div className="project-card-measure-label"> + {translate('metric.new_coverage.name')} + </div> + </div> + </div> + + <div className="project-card-measure" data-key="new_duplicated_lines_density"> + <div className="project-card-measure-inner"> + <div className="project-card-measure-number"> + <Measure + measure={{ leak: measures['new_duplicated_lines_density'] }} + metric={{ key: 'new_duplicated_lines_density', type: 'PERCENT' }} + /> + </div> + <div className="project-card-measure-label"> + {translate('metric.new_duplicated_lines_density.short_name')} + </div> + </div> + </div> + + {measures['new_lines'] != null && + <div className="project-card-measure smaller-card" data-key="new_lines"> + <div className="project-card-measure-inner"> + <div className="project-card-measure-number"> + <Measure + measure={{ leak: measures['new_lines'] }} + metric={{ key: 'new_lines', type: 'SHORT_INT' }} + /> + </div> + <div className="project-card-measure-label"> + {translate('metric.new_lines.short_name')} + </div> + </div> + </div>} + </div> + ); +} 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 ( - <div className="project-card-measures"> - <div className="project-card-measure" data-key="reliability_rating"> - <div className="project-card-measure-inner"> - <div className="project-card-measure-number"> - <Rating value={measures['reliability_rating']} /> - </div> - <div className="project-card-measure-label"> - {translate('metric_domain.Reliability')} - </div> - </div> - </div> - - <div className="project-card-measure" data-key="security_rating"> - <div className="project-card-measure-inner"> - <div className="project-card-measure-number"> - <Rating value={measures['security_rating']} /> - </div> - <div className="project-card-measure-label"> - {translate('metric_domain.Security')} - </div> - </div> - </div> - - <div className="project-card-measure" data-key="sqale_rating"> - <div className="project-card-measure-inner"> - <div className="project-card-measure-number"> - <Rating value={measures['sqale_rating']} /> - </div> - <div className="project-card-measure-label"> - {translate('metric_domain.Maintainability')} - </div> - </div> - </div> - - <div className="project-card-measure" data-key="coverage"> - <div className="project-card-measure-inner"> - <div className="project-card-measure-number"> - {measures['coverage'] != null && - <span className="spacer-right"> - <CoverageRating value={measures['coverage']} /> - </span>} - <Measure - measure={{ value: measures['coverage'] }} - metric={{ key: 'coverage', type: 'PERCENT' }} - /> - </div> - <div className="project-card-measure-label"> - {translate('metric.coverage.name')} - </div> - </div> - </div> - - <div className="project-card-measure" data-key="duplicated_lines_density"> - <div className="project-card-measure-inner"> - <div className="project-card-measure-number"> - {measures['duplicated_lines_density'] != null && - <span className="spacer-right"> - <DuplicationsRating value={measures['duplicated_lines_density']} /> - </span>} - <Measure - measure={{ value: measures['duplicated_lines_density'] }} - metric={{ key: 'duplicated_lines_density', type: 'PERCENT' }} - /> - </div> - <div className="project-card-measure-label"> - {translate('metric.duplicated_lines_density.short_name')} - </div> - </div> - </div> - - {measures['ncloc'] != null && - <div className="project-card-measure" data-key="ncloc"> - <div className="project-card-measure-inner"> - <div className="project-card-measure-number"> - <span className="spacer-right"> - <SizeRating value={measures['ncloc']} /> - </span> - <Measure - measure={{ value: measures['ncloc'] }} - metric={{ key: 'ncloc', type: 'SHORT_INT' }} - /> - </div> - <div className="project-card-measure-label"> - <ProjectCardLanguages distribution={measures['ncloc_language_distribution']} /> - </div> - </div> - </div>} - </div> - ); - } -} 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 ( + <div className="project-card-measures"> + <div className="project-card-measure smaller-card" data-key="reliability_rating"> + <div className="project-card-measure-inner"> + <div className="project-card-measure-number"> + <Rating value={measures['reliability_rating']} /> + </div> + <div className="project-card-measure-label"> + {translate('metric_domain.Reliability')} + </div> + </div> + </div> + + <div className="project-card-measure smaller-card" data-key="security_rating"> + <div className="project-card-measure-inner"> + <div className="project-card-measure-number"> + <Rating value={measures['security_rating']} /> + </div> + <div className="project-card-measure-label"> + {translate('metric_domain.Security')} + </div> + </div> + </div> + + <div className="project-card-measure" data-key="sqale_rating"> + <div className="project-card-measure-inner"> + <div className="project-card-measure-number"> + <Rating value={measures['sqale_rating']} /> + </div> + <div className="project-card-measure-label"> + {translate('metric_domain.Maintainability')} + </div> + </div> + </div> + + <div className="project-card-measure" data-key="coverage"> + <div className="project-card-measure-inner"> + <div className="project-card-measure-number"> + {measures['coverage'] != null && + <span className="spacer-right"> + <CoverageRating value={measures['coverage']} /> + </span>} + <Measure + measure={{ value: measures['coverage'] }} + metric={{ key: 'coverage', type: 'PERCENT' }} + /> + </div> + <div className="project-card-measure-label"> + {translate('metric.coverage.name')} + </div> + </div> + </div> + + <div className="project-card-measure" data-key="duplicated_lines_density"> + <div className="project-card-measure-inner"> + <div className="project-card-measure-number"> + {measures['duplicated_lines_density'] != null && + <span className="spacer-right"> + <DuplicationsRating + value={formatMeasure(measures['duplicated_lines_density'], 'FLOAT')} + /> + </span>} + <Measure + measure={{ value: measures['duplicated_lines_density'] }} + metric={{ key: 'duplicated_lines_density', type: 'PERCENT' }} + /> + </div> + <div className="project-card-measure-label"> + {translate('metric.duplicated_lines_density.short_name')} + </div> + </div> + </div> + + {measures['ncloc'] != null && + <div className="project-card-measure" data-key="ncloc"> + <div className="project-card-measure-inner"> + <div className="project-card-measure-number"> + <span className="spacer-right"> + <SizeRating value={formatMeasure(measures['ncloc'], 'INT')} /> + </span> + <Measure + measure={{ value: measures['ncloc'] }} + metric={{ key: 'ncloc', type: 'SHORT_INT' }} + /> + </div> + <div className="project-card-measure-label"> + <ProjectCardLanguages distribution={measures['ncloc_language_distribution']} /> + </div> + </div> + </div>} + </div> + ); +} 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<string>, + 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 ( - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"> + <svg + className={className} + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 16 16" + width="16" + height="16"> <path style={{ fill: 'currentColor' }} d="M11 9h1.3l.5.8.8-.5-.8-1.3H11v-.3l2-2.3V3h-1v2l-1 1.2V5c-.1-.8-.7-1.5-1.4-1.9L11 1.8l-.7-.7-1.8 1.6-1.8-1.6-.7.7 1.5 1.3C6.7 3.5 6.1 4.2 6 5v1.1L5 5V3H4v2.3l2 2.3V8H4.2l-.7 1.2.8.5.4-.7H6v.3l-2 1.9V14h1v-2.4l1-1C6 12 7.1 13 8.4 13h.8c.7 0 1.4-.3 1.8-.9.3-.4.3-.9.2-1.4l.9.9V14h1v-2.8l-2-1.9V9zm-2 2H8V6h1v5z" diff --git a/server/sonar-web/src/main/js/components/ui/CodeSmellIcon.js b/server/sonar-web/src/main/js/components/ui/CodeSmellIcon.js index 9673f988b24..5a788b1066c 100644 --- a/server/sonar-web/src/main/js/components/ui/CodeSmellIcon.js +++ b/server/sonar-web/src/main/js/components/ui/CodeSmellIcon.js @@ -20,10 +20,15 @@ // @flow import React from 'react'; -export default function CodeSmellIcon() { +export default function CodeSmellIcon({ className }: { className?: string }) { /* eslint-disable max-len */ return ( - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"> + <svg + className={className} + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 16 16" + width="16" + height="16"> <path style={{ fill: 'currentColor' }} d="M8 2C4.7 2 2 4.7 2 8s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6zm-.5 5.5h.9v.9h-.9v-.9zm-3.8.2c-.1 0-.2-.1-.2-.2 0-.4.1-1.2.6-2S5.3 4.2 5.6 4c.2 0 .3 0 .3.1l1.3 2.3c0 .1 0 .2-.1.2-.1.2-.2.3-.3.5-.1.2-.2.4-.2.5 0 .1-.1.2-.2.2l-2.7-.1zM9.9 12c-.3.2-1.1.5-2 .5-.9 0-1.7-.3-2-.5-.1 0-.1-.2-.1-.3l1.3-2.3c0-.1.1-.1.2-.1.2.1.3.1.5.1s.4 0 .5-.1c.1 0 .2 0 .2.1l1.3 2.3c.2.2.2.3.1.3zm2.5-4.1L9.7 8c-.1 0-.2-.1-.2-.2 0-.2-.1-.4-.2-.5 0-.1-.2-.3-.3-.4-.1 0-.1-.1-.1-.2l1.3-2.3c.1-.1.2-.1.3-.1.3.2 1 .7 1.5 1.5s.6 1.6.6 2c0 0-.1.1-.2.1z" diff --git a/server/sonar-web/src/main/js/components/ui/VulnerabilityIcon.js b/server/sonar-web/src/main/js/components/ui/VulnerabilityIcon.js index b3987cb4432..248eae3d844 100644 --- a/server/sonar-web/src/main/js/components/ui/VulnerabilityIcon.js +++ b/server/sonar-web/src/main/js/components/ui/VulnerabilityIcon.js @@ -20,10 +20,15 @@ // @flow import React from 'react'; -export default function VulnerabilityIcon() { +export default function VulnerabilityIcon({ className }: { className?: string }) { /* eslint-disable max-len */ return ( - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"> + <svg + className={className} + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 16 16" + width="16" + height="16"> <path style={{ fill: 'currentColor' }} d="M10.8 5H6V3.9a2.28 2.28 0 0 1 2-2.5 2.22 2.22 0 0 1 1.8 1.2.48.48 0 0 0 .7.2.48.48 0 0 0 .2-.7A3 3 0 0 0 8 .4a3.34 3.34 0 0 0-3 3.5v1.2a2.16 2.16 0 0 0-2 2.1v4.4a2.22 2.22 0 0 0 2.2 2.2h5.6a2.22 2.22 0 0 0 2.2-2.2V7.2A2.22 2.22 0 0 0 10.8 5zm-2.2 5.5v1.2H7.4v-1.2a1.66 1.66 0 0 1-1.1-1.6A1.75 1.75 0 0 1 8 7.2a1.71 1.71 0 0 1 .6 3.3z" |