aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-08-04 17:17:21 +0200
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-08-14 11:44:44 +0200
commitbb393dd277ab06c8451513a6339171e800fc9944 (patch)
tree8d452b784eed096a3362fb5730d94a5ddd0b82a1
parent21cd227ec55a54d22edfddbb45ec011359bdba3e (diff)
downloadsonarqube-bb393dd277ab06c8451513a6339171e800fc9944.tar.gz
sonarqube-bb393dd277ab06c8451513a6339171e800fc9944.zip
SONAR-9608 SONAR-9611 Create the project overview bubble chart on the measures page
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.js6
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js20
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/config/bubbles.js29
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChart.js122
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/sidebar/ProjectOverviewFacet.js61
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js8
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.js.snap5
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/style.css12
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/utils.js18
-rw-r--r--server/sonar-web/src/main/js/apps/projects/styles.css25
-rw-r--r--server/sonar-web/src/main/js/apps/projects/visualizations/Risk.js4
-rw-r--r--server/sonar-web/src/main/js/apps/projects/visualizations/SimpleBubbleChart.js4
-rw-r--r--server/sonar-web/src/main/js/components/charts/ColorRatingsLegend.js (renamed from server/sonar-web/src/main/js/apps/projects/visualizations/RatingsLegend.js)13
-rw-r--r--server/sonar-web/src/main/less/components/graphics.less24
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties4
15 files changed, 262 insertions, 93 deletions
diff --git a/server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.js b/server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.js
index 00be69c8786..bd275efccc8 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.js
@@ -92,7 +92,11 @@ describe('groupByDomains', () => {
describe('parseQuery', () => {
it('should correctly parse the url query', () => {
- expect(utils.parseQuery({})).toEqual({ metric: '', selected: '', view: utils.DEFAULT_VIEW });
+ expect(utils.parseQuery({})).toEqual({
+ metric: 'project_overview',
+ selected: '',
+ view: utils.DEFAULT_VIEW
+ });
expect(utils.parseQuery({ metric: 'foo', selected: 'bar', view: 'tree' })).toEqual({
metric: 'foo',
selected: 'bar',
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js
index 4dd6eb92774..6aadddf9fca 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.js
@@ -26,8 +26,7 @@ import MeasureFavoriteContainer from './MeasureFavoriteContainer';
import PageActions from './PageActions';
import SourceViewer from '../../../components/SourceViewer/SourceViewer';
import { getComponentLeaves } from '../../../api/components';
-import { enhanceComponent, isFileType } from '../utils';
-import { bubbles } from '../config/bubbles';
+import { enhanceComponent, getBubbleMetrics, isFileType } from '../utils';
import type { Component, ComponentEnhanced, Paging, Period } from '../types';
import type { Metric } from '../../../store/metrics/actions';
@@ -78,22 +77,19 @@ export default class MeasureOverview extends React.PureComponent {
this.mounted = false;
}
- getBubbleMetrics = ({ domain, metrics }: Props) => {
- const conf = bubbles[domain];
- return {
- xMetric: metrics[conf.x],
- yMetric: metrics[conf.y],
- sizeMetric: metrics[conf.size]
- };
- };
-
fetchComponents = (props: Props) => {
const { component, metrics } = props;
if (isFileType(component)) {
return this.setState({ components: [], paging: null });
}
- const { xMetric, yMetric, sizeMetric } = this.getBubbleMetrics(props);
+ const { xMetric, yMetric, sizeMetric, colorsMetric } = getBubbleMetrics(
+ props.domain,
+ props.metrics
+ );
const metricsKey = [xMetric.key, yMetric.key, sizeMetric.key];
+ if (colorsMetric) {
+ metricsKey.push(colorsMetric.map(metric => metric.key));
+ }
const options = {
s: 'metric',
metricSort: sizeMetric.key,
diff --git a/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.js b/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.js
index 3fd1e2a2055..12aa314d30b 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/config/bubbles.js
@@ -19,9 +19,30 @@
*/
// @flow
export const bubbles = {
- Reliability: { x: 'ncloc', y: 'reliability_remediation_effort', size: 'bugs' },
- Security: { x: 'ncloc', y: 'security_remediation_effort', size: 'vulnerabilities' },
- Maintainability: { x: 'ncloc', y: 'sqale_index', size: 'code_smells' },
+ Reliability: {
+ x: 'ncloc',
+ y: 'reliability_remediation_effort',
+ size: 'bugs',
+ colors: ['reliability_rating']
+ },
+ Security: {
+ x: 'ncloc',
+ y: 'security_remediation_effort',
+ size: 'vulnerabilities',
+ colors: ['security_rating']
+ },
+ Maintainability: {
+ x: 'ncloc',
+ y: 'sqale_index',
+ size: 'code_smells',
+ colors: ['sqale_rating']
+ },
Coverage: { x: 'complexity', y: 'coverage', size: 'uncovered_lines' },
- Duplications: { x: 'ncloc', y: 'duplicated_lines', size: 'duplicated_blocks' }
+ Duplications: { x: 'ncloc', y: 'duplicated_lines', size: 'duplicated_blocks' },
+ project_overview: {
+ x: 'sqale_index',
+ y: 'coverage',
+ size: 'ncloc',
+ colors: ['reliability_rating', 'security_rating']
+ }
};
diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChart.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChart.js
index 495a3fc3158..8ef9df9b759 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChart.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/BubbleChart.js
@@ -21,13 +21,16 @@
import React from 'react';
import EmptyResult from './EmptyResult';
import OriginalBubbleChart from '../../../components/charts/BubbleChart';
+import ColorRatingsLegend from '../../../components/charts/ColorRatingsLegend';
import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
import {
getLocalizedMetricDomain,
getLocalizedMetricName,
+ translate,
translateWithParameters
} from '../../../helpers/l10n';
-import { bubbles } from '../config/bubbles';
+import { getBubbleMetrics, isProjectOverview } from '../utils';
+import { RATING_COLORS } from '../../../helpers/constants';
import type { Component, ComponentEnhanced } from '../types';
import type { Metric } from '../../../store/metrics/actions';
@@ -44,15 +47,6 @@ type Props = {|
export default class BubbleChart extends React.PureComponent {
props: Props;
- getBubbleMetrics = ({ domain, metrics }: Props) => {
- const conf = bubbles[domain];
- return {
- xMetric: metrics[conf.x],
- yMetric: metrics[conf.y],
- sizeMetric: metrics[conf.size]
- };
- };
-
getMeasureVal = (component: ComponentEnhanced, metric: Metric) => {
const measure = component.measures.find(measure => measure.metric.key === metric.key);
if (measure) {
@@ -65,27 +59,45 @@ export default class BubbleChart extends React.PureComponent {
x: number,
y: number,
size: number,
+ colors: ?Array<?number>,
xMetric: Metric,
yMetric: Metric,
- sizeMetric: Metric
+ sizeMetric: Metric,
+ colorsMetric: ?Array<Metric>
) {
const inner = [
componentName,
`${xMetric.name}: ${formatMeasure(x, xMetric.type)}`,
`${yMetric.name}: ${formatMeasure(y, yMetric.type)}`,
`${sizeMetric.name}: ${formatMeasure(size, sizeMetric.type)}`
- ].join('<br>');
- return `<div class="text-left">${inner}</div>`;
+ ];
+ if (colors && colorsMetric) {
+ colorsMetric.forEach((metric, idx) => {
+ // $FlowFixMe colors is always defined at this point
+ const colorValue = colors[idx];
+ if (colorValue || colorValue === 0) {
+ inner.push(`${metric.name}: ${formatMeasure(colorValue, metric.type)}`);
+ }
+ });
+ }
+ return `<div class="text-left">${inner.join('<br/>')}</div>`;
}
handleBubbleClick = (component: ComponentEnhanced) => this.props.updateSelected(component.key);
- renderBubbleChart(xMetric: Metric, yMetric: Metric, sizeMetric: Metric) {
+ renderBubbleChart(
+ xMetric: Metric,
+ yMetric: Metric,
+ sizeMetric: Metric,
+ colorsMetric: ?Array<Metric>
+ ) {
const items = this.props.components
.map(component => {
const x = this.getMeasureVal(component, xMetric);
const y = this.getMeasureVal(component, yMetric);
const size = this.getMeasureVal(component, sizeMetric);
+ const colors =
+ colorsMetric && colorsMetric.map(metric => this.getMeasureVal(component, metric));
if ((!x && x !== 0) || (!y && y !== 0) || (!size && size !== 0)) {
return null;
}
@@ -93,8 +105,20 @@ export default class BubbleChart extends React.PureComponent {
x,
y,
size,
+ color:
+ colors != null ? RATING_COLORS[Math.max(...colors.filter(Boolean)) - 1] : undefined,
link: component,
- tooltip: this.getTooltip(component.name, x, y, size, xMetric, yMetric, sizeMetric)
+ tooltip: this.getTooltip(
+ component.name,
+ x,
+ y,
+ size,
+ colors,
+ xMetric,
+ yMetric,
+ sizeMetric,
+ colorsMetric
+ )
};
})
.filter(Boolean);
@@ -114,35 +138,63 @@ export default class BubbleChart extends React.PureComponent {
);
}
- render() {
- if (this.props.components.length <= 0) {
- return <EmptyResult />;
- }
-
- const { xMetric, yMetric, sizeMetric } = this.getBubbleMetrics(this.props);
+ renderChartHeader(domain: string, sizeMetric: Metric, colorsMetric: ?Array<Metric>) {
+ const title = isProjectOverview(domain)
+ ? translate('component_measures.overview', domain, 'title')
+ : translateWithParameters(
+ 'component_measures.domain_x_overview',
+ getLocalizedMetricDomain(domain)
+ );
return (
- <div className="measure-details-bubble-chart">
- <div className="measure-details-bubble-chart-header">
- <span>
- {translateWithParameters(
- 'component_measures.domain_x_overview',
- getLocalizedMetricDomain(this.props.domain)
- )}
- </span>
- <span className="measure-details-bubble-chart-legend">
+ <div className="measure-overview-bubble-chart-header">
+ <span className="measure-overview-bubble-chart-title">
+ {title}
+ </span>
+ <span className="measure-overview-bubble-chart-legend">
+ <span className="note">
+ {colorsMetric &&
+ <span className="spacer-right">
+ {translateWithParameters(
+ 'component_measures.legend.color_x',
+ colorsMetric.length > 1
+ ? translateWithParameters(
+ 'component_measures.legend.worse_of_x_y',
+ ...colorsMetric.map(metric => getLocalizedMetricName(metric))
+ )
+ : getLocalizedMetricName(colorsMetric[0])
+ )}
+ </span>}
{translateWithParameters(
'component_measures.legend.size_x',
getLocalizedMetricName(sizeMetric)
)}
</span>
+ {colorsMetric && <ColorRatingsLegend className="spacer-top" />}
+ </span>
+ </div>
+ );
+ }
+
+ render() {
+ if (this.props.components.length <= 0) {
+ return <EmptyResult />;
+ }
+ const { domain } = this.props;
+ const { xMetric, yMetric, sizeMetric, colorsMetric } = getBubbleMetrics(
+ domain,
+ this.props.metrics
+ );
+
+ return (
+ <div className="measure-overview-bubble-chart">
+ {this.renderChartHeader(domain, sizeMetric, colorsMetric)}
+ <div className="measure-overview-bubble-chart-content">
+ {this.renderBubbleChart(xMetric, yMetric, sizeMetric, colorsMetric)}
</div>
- <div>
- {this.renderBubbleChart(xMetric, yMetric, sizeMetric)}
- </div>
- <div className="measure-details-bubble-chart-axis x">
+ <div className="measure-overview-bubble-chart-axis x">
{getLocalizedMetricName(xMetric)}
</div>
- <div className="measure-details-bubble-chart-axis y">
+ <div className="measure-overview-bubble-chart-axis y">
{getLocalizedMetricName(yMetric)}
</div>
</div>
diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/ProjectOverviewFacet.js b/server/sonar-web/src/main/js/apps/component-measures/sidebar/ProjectOverviewFacet.js
new file mode 100644
index 00000000000..a3d2a9c5b9a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/ProjectOverviewFacet.js
@@ -0,0 +1,61 @@
+/*
+ * 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 FacetBox from '../../../components/facet/FacetBox';
+import FacetItem from '../../../components/facet/FacetItem';
+import FacetItemsList from '../../../components/facet/FacetItemsList';
+import Tooltip from '../../../components/controls/Tooltip';
+import { translate } from '../../../helpers/l10n';
+
+type Props = {|
+ onChange: (metric: string) => void,
+ selected: string,
+ value: string
+|};
+
+export default class ProjectOverviewFacet extends React.PureComponent {
+ props: Props;
+
+ render() {
+ const { value, selected } = this.props;
+ const facetName = translate('component_measures.overview', value, 'facet');
+ return (
+ <FacetBox>
+ <FacetItemsList>
+ <FacetItem
+ active={value === selected}
+ disabled={false}
+ key={value}
+ name={
+ <Tooltip overlay={facetName} mouseEnterDelay={0.5}>
+ <strong id={`measure-overview-${value}-name`}>
+ {facetName}
+ </strong>
+ </Tooltip>
+ }
+ onClick={this.props.onChange}
+ value={value}
+ />
+ </FacetItemsList>
+ </FacetBox>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js b/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js
index bf126b8a5b4..441491a518f 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js
@@ -19,8 +19,9 @@
*/
// @flow
import React from 'react';
+import ProjectOverviewFacet from './ProjectOverviewFacet';
import DomainFacet from './DomainFacet';
-import { groupByDomains } from '../utils';
+import { groupByDomains, PROJECT_OVERVEW } from '../utils';
import type { MeasureEnhanced } from '../../../components/measure/types';
import type { Query } from '../types';
@@ -56,6 +57,11 @@ export default class Sidebar extends React.PureComponent {
render() {
return (
<div className="search-navigator-facets-list">
+ <ProjectOverviewFacet
+ onChange={this.changeMetric}
+ selected={this.props.selectedMetric}
+ value={PROJECT_OVERVEW}
+ />
{groupByDomains(this.props.measures).map(domain =>
<DomainFacet
key={domain.name}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.js.snap
index ba904466156..5e7477f09e1 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.js.snap
@@ -4,6 +4,11 @@ exports[`should display two facets 1`] = `
<div
className="search-navigator-facets-list"
>
+ <ProjectOverviewFacet
+ onChange={[Function]}
+ selected="foo"
+ value="project_overview"
+ />
<DomainFacet
domain={
Object {
diff --git a/server/sonar-web/src/main/js/apps/component-measures/style.css b/server/sonar-web/src/main/js/apps/component-measures/style.css
index 0f9c831dfb7..5327c58edd2 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/style.css
+++ b/server/sonar-web/src/main/js/apps/component-measures/style.css
@@ -98,16 +98,22 @@
}
.measure-details-bubble-chart-header {
+ display: flex;
+ align-items: center;
padding: 16px;
margin-left: -60px;
border-bottom: 1px solid #e6e6e6;
}
-.measure-details-bubble-chart-legend {
+.measure-details-bubble-chart-title {
position: absolute;
- width: 100%;
- left: 0;
+}
+
+.measure-details-bubble-chart-legend {
+ display: flex;
+ flex-direction: column;
text-align: center;
+ flex-grow: 1;
}
.measure-details-bubble-chart-axis {
diff --git a/server/sonar-web/src/main/js/apps/component-measures/utils.js b/server/sonar-web/src/main/js/apps/component-measures/utils.js
index 8d672b97def..41b4186466d 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/utils.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/utils.js
@@ -29,7 +29,9 @@ import type { RawQuery } from '../../helpers/query';
import type { Metric } from '../../store/metrics/actions';
import type { MeasureEnhanced } from '../../components/measure/types';
+export const PROJECT_OVERVEW = 'project_overview';
export const DEFAULT_VIEW = 'list';
+export const DEFAULT_METRIC = PROJECT_OVERVEW;
const KNOWN_DOMAINS = [
'Releasability',
'Reliability',
@@ -112,15 +114,27 @@ export const hasTreemap = (metricType: string): boolean =>
export const hasBubbleChart = (domainName: string): boolean => bubbles[domainName] != null;
+export const getBubbleMetrics = (domain: string, metrics: { [string]: Metric }) => {
+ const conf = bubbles[domain];
+ return {
+ xMetric: metrics[conf.x],
+ yMetric: metrics[conf.y],
+ sizeMetric: metrics[conf.size],
+ colorsMetric: conf.colors ? conf.colors.map(color => metrics[color]) : null
+ };
+};
+
+export const isProjectOverview = (metric: string) => metric === PROJECT_OVERVEW;
+
export const parseQuery = memoize((urlQuery: RawQuery): Query => ({
- metric: parseAsString(urlQuery['metric']),
+ metric: parseAsString(urlQuery['metric']) || DEFAULT_METRIC,
selected: parseAsString(urlQuery['selected']),
view: parseAsString(urlQuery['view']) || DEFAULT_VIEW
}));
export const serializeQuery = memoize((query: Query): RawQuery => {
return cleanQuery({
- metric: serializeString(query.metric),
+ metric: query.metric === DEFAULT_METRIC ? null : serializeString(query.metric),
selected: serializeString(query.selected),
view: query.view === DEFAULT_VIEW ? null : serializeString(query.view)
});
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 1e16c1a1565..dd0ef5e696d 100644
--- a/server/sonar-web/src/main/js/apps/projects/styles.css
+++ b/server/sonar-web/src/main/js/apps/projects/styles.css
@@ -268,31 +268,6 @@
font-style: italic;
}
-.projects-visualizations-ratings {
- display: flex;
- justify-content: center;
- margin-top: 16px;
-}
-
-.projects-visualizations-ratings > *:not(:first-child) {
- margin-left: 24px;
-}
-
-.projects-visualizations-ratings-rect {
- display: inline-block;
- vertical-align: top;
- margin-top: 1px;
- margin-right: 4px;
- border: 1px solid;
-}
-
-.projects-visualizations-ratings-rect-inner {
- display: block;
- width: 8px;
- height: 8px;
- opacity: 0.2;
-}
-
.measure-details-bubble-chart-axis {
position: absolute;
color: #777;
diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/Risk.js b/server/sonar-web/src/main/js/apps/projects/visualizations/Risk.js
index fc881790b54..d69b6bfce49 100644
--- a/server/sonar-web/src/main/js/apps/projects/visualizations/Risk.js
+++ b/server/sonar-web/src/main/js/apps/projects/visualizations/Risk.js
@@ -19,7 +19,7 @@
*/
// @flow
import React from 'react';
-import RatingsLegend from './RatingsLegend';
+import ColorRatingsLegend from '../../../components/charts/ColorRatingsLegend';
import BubbleChart from '../../../components/charts/BubbleChart';
import { formatMeasure } from '../../../helpers/measures';
import { translate, translateWithParameters } from '../../../helpers/l10n';
@@ -130,7 +130,7 @@ export default class Risk extends React.PureComponent {
'component_measures.legend.size_x',
translate('metric', SIZE_METRIC, 'name')
)}
- <RatingsLegend />
+ <ColorRatingsLegend className="big-spacer-top" />
</div>
</div>
);
diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/SimpleBubbleChart.js b/server/sonar-web/src/main/js/apps/projects/visualizations/SimpleBubbleChart.js
index ede73dbf87b..44ddf736040 100644
--- a/server/sonar-web/src/main/js/apps/projects/visualizations/SimpleBubbleChart.js
+++ b/server/sonar-web/src/main/js/apps/projects/visualizations/SimpleBubbleChart.js
@@ -19,7 +19,7 @@
*/
// @flow
import React from 'react';
-import RatingsLegend from './RatingsLegend';
+import ColorRatingsLegend from '../../../components/charts/ColorRatingsLegend';
import BubbleChart from '../../../components/charts/BubbleChart';
import { formatMeasure } from '../../../helpers/measures';
import { translate, translateWithParameters } from '../../../helpers/l10n';
@@ -130,7 +130,7 @@ export default class SimpleBubbleChart extends React.PureComponent {
'component_measures.legend.size_x',
translate('metric', sizeMetric.key, 'name')
)}
- {colorMetric != null && <RatingsLegend />}
+ {colorMetric != null && <ColorRatingsLegend className="big-spacer-top" />}
</div>
</div>
);
diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/RatingsLegend.js b/server/sonar-web/src/main/js/components/charts/ColorRatingsLegend.js
index b22d9729157..9e17ff01add 100644
--- a/server/sonar-web/src/main/js/apps/projects/visualizations/RatingsLegend.js
+++ b/server/sonar-web/src/main/js/components/charts/ColorRatingsLegend.js
@@ -19,19 +19,20 @@
*/
// @flow
import React from 'react';
-import { formatMeasure } from '../../../helpers/measures';
-import { RATING_COLORS } from '../../../helpers/constants';
+import classNames from 'classnames';
+import { formatMeasure } from '../../helpers/measures';
+import { RATING_COLORS } from '../../helpers/constants';
-export default function RatingsLegend() {
+export default function ColorRatingsLegend({ className }: { className?: string }) {
return (
- <div className="projects-visualizations-ratings">
+ <div className={classNames('color-ratings-legend', className)}>
{[1, 2, 3, 4, 5].map(rating =>
<div key={rating}>
<span
- className="projects-visualizations-ratings-rect"
+ className="color-ratings-legend-rect"
style={{ borderColor: RATING_COLORS[rating - 1] }}>
<span
- className="projects-visualizations-ratings-rect-inner"
+ className="color-ratings-legend-rect-inner"
style={{ backgroundColor: RATING_COLORS[rating - 1] }}
/>
</span>
diff --git a/server/sonar-web/src/main/less/components/graphics.less b/server/sonar-web/src/main/less/components/graphics.less
index 47b0390b2f0..8df1fcb6b70 100644
--- a/server/sonar-web/src/main/less/components/graphics.less
+++ b/server/sonar-web/src/main/less/components/graphics.less
@@ -263,6 +263,30 @@
text-anchor: end;
}
+.color-ratings-legend {
+ display: flex;
+ justify-content: center;
+
+ & > *:not(:first-child) {
+ margin-left: 24px;
+ }
+
+ .color-ratings-legend-rect {
+ display: inline-block;
+ vertical-align: top;
+ margin-top: 1px;
+ margin-right: 4px;
+ border: 1px solid;
+ }
+
+ .color-ratings-legend-rect-inner {
+ display: block;
+ width: 8px;
+ height: 8px;
+ opacity: 0.2;
+ }
+}
+
/*
* Bar Chart
*/
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 621bde0875e..2cb9c8049de 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -2894,6 +2894,7 @@ component_measures.tab.treemap=Treemap
component_measures.tab.history=History
component_measures.legend.color_x=Color: {0}
component_measures.legend.size_x=Size: {0}
+component_measures.legend.worse_of_x_y=Worse of {0} and {1}
component_measures.x_of_y={0} of {1}
component_measures.no_history=There is no historical data.
component_measures.not_found=The requested measure was not found.
@@ -2901,6 +2902,9 @@ component_measures.to_select_files=to select files
component_measures.to_navigate=to navigate
component_measures.to_navigate_back=to navigate back
+component_measures.overview.project_overview.facet=Project Overview
+component_measures.overview.project_overview.title=Risk
+
#------------------------------------------------------------------------------
#
# ABOUT PAGE