aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-05-22 17:14:37 +0200
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-06-09 08:26:48 +0200
commit1689d1f1ca71dfac66682fd87b4ce56ad58dfce0 (patch)
treea0af224d5d10ee2f62b76a190c220ba735667940 /server/sonar-web
parentc26d1c37fdcbc8cbdeb0390648c071297d72d027 (diff)
downloadsonarqube-1689d1f1ca71dfac66682fd87b4ce56ad58dfce0.tar.gz
sonarqube-1689d1f1ca71dfac66682fd87b4ce56ad58dfce0.zip
SONAR-9245 Create the leak view on projects page
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/Measure.js6
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/AllProjects.js5
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js146
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.js135
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardMeasures.js132
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.js132
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js17
-rw-r--r--server/sonar-web/src/main/js/apps/projects/store/actions.js29
-rw-r--r--server/sonar-web/src/main/js/apps/projects/store/utils.js2
-rw-r--r--server/sonar-web/src/main/js/apps/projects/styles.css25
-rw-r--r--server/sonar-web/src/main/js/apps/projects/utils.js2
-rw-r--r--server/sonar-web/src/main/js/components/ui/BugIcon.js9
-rw-r--r--server/sonar-web/src/main/js/components/ui/CodeSmellIcon.js9
-rw-r--r--server/sonar-web/src/main/js/components/ui/VulnerabilityIcon.js9
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"