aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-07-17 09:21:35 +0200
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-07-19 10:10:10 +0200
commitc79679129c7414e04c7c1d49e9c58ed5390748d7 (patch)
treeba6085aea97f35a1bfb604c9d793e386f0a48b1b /server
parent4fd2ee463589efd5b53537d37cc1f262aaac49c5 (diff)
downloadsonarqube-c79679129c7414e04c7c1d49e9c58ed5390748d7.tar.gz
sonarqube-c79679129c7414e04c7c1d49e9c58ed5390748d7.zip
SONAR-9546 Allow to create two custom graphs on the project activity page
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/PreviewGraph.js42
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltips.js8
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltipsContent.js7
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltips-test.js12
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltipsContent-test.js6
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/PreviewGraphTooltips-test.js.snap51
-rw-r--r--server/sonar-web/src/main/js/apps/overview/types.js2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.js.snap22
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.js2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js9
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js40
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendCustom.js14
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.js7
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.js20
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContent.js13
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentEvents.js25
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentOverview.js13
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js100
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js41
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js118
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.js2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.js20
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendCustom-test.js16
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendStatic-test.js4
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltips-test.js27
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContent-test.js7
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentOverview-test.js8
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.js40
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsHistory-test.js.snap27
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendCustom-test.js.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendStatic-test.js.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltips-test.js.snap133
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.js.snap44
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap156
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.js35
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css14
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/utils.js74
-rw-r--r--server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js52
-rw-r--r--server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js8
40 files changed, 598 insertions, 628 deletions
diff --git a/server/sonar-web/src/main/js/apps/overview/events/PreviewGraph.js b/server/sonar-web/src/main/js/apps/overview/events/PreviewGraph.js
index 63424f5a6d6..aea2204b3c9 100644
--- a/server/sonar-web/src/main/js/apps/overview/events/PreviewGraph.js
+++ b/server/sonar-web/src/main/js/apps/overview/events/PreviewGraph.js
@@ -21,10 +21,14 @@
import React from 'react';
import { minBy } from 'lodash';
import { AutoSizer } from 'react-virtualized';
+import {
+ getDisplayedHistoryMetrics,
+ generateSeries,
+ getSeriesMetricType
+} from '../../projectActivity/utils';
+import { getCustomGraph, getGraph } from '../../../helpers/storage';
import AdvancedTimeline from '../../../components/charts/AdvancedTimeline';
import PreviewGraphTooltips from './PreviewGraphTooltips';
-import { generateSeries, getDisplayedHistoryMetrics } from '../../projectActivity/utils';
-import { getCustomGraph, getGraph } from '../../../helpers/storage';
import { formatMeasure, getShortType } from '../../../helpers/measures';
import type { Serie } from '../../../components/charts/AdvancedTimeline';
import type { History, Metric } from '../types';
@@ -39,7 +43,6 @@ type Props = {
type State = {
customMetrics: Array<string>,
graph: string,
- metricsType: string,
selectedDate: ?Date,
series: Array<Serie>,
tooltipIdx: ?number,
@@ -56,13 +59,11 @@ export default class PreviewGraph extends React.PureComponent {
super(props);
const graph = getGraph();
const customMetrics = getCustomGraph();
- const metricsType = this.getMetricType(props.metrics, graph, customMetrics);
this.state = {
customMetrics,
graph,
- metricsType,
selectedDate: null,
- series: this.getSeries(props.history, graph, customMetrics, metricsType),
+ series: this.getSeries(props.history, graph, customMetrics, props.metrics),
tooltipIdx: null,
tooltipXPos: null
};
@@ -72,18 +73,16 @@ export default class PreviewGraph extends React.PureComponent {
if (nextProps.history !== this.props.history || nextProps.metrics !== this.props.metrics) {
const graph = getGraph();
const customMetrics = getCustomGraph();
- const metricsType = this.getMetricType(nextProps.metrics, graph, customMetrics);
this.setState({
customMetrics,
graph,
- metricsType,
- series: this.getSeries(nextProps.history, graph, customMetrics, metricsType)
+ series: this.getSeries(nextProps.history, graph, customMetrics, nextProps.metrics)
});
}
}
formatValue = (tick: number | string) =>
- formatMeasure(tick, getShortType(this.state.metricsType));
+ formatMeasure(tick, getShortType(this.state.series[0].type));
getDisplayedMetrics = (graph: string, customMetrics: Array<string>): Array<string> => {
const metrics: Array<string> = getDisplayedHistoryMetrics(graph, customMetrics);
@@ -93,34 +92,23 @@ export default class PreviewGraph extends React.PureComponent {
return metrics;
};
- getSeries = (
- history: ?History,
- graph: string,
- customMetrics: Array<string>,
- metricsType: string
- ) => {
+ getSeries = (history: ?History, graph: string, customMetrics: Array<string>, metrics: Array<Metric>) => {
const myHistory = history;
if (!myHistory) {
return [];
}
- const metrics = this.getDisplayedMetrics(graph, customMetrics);
+ const displayedMetrics = this.getDisplayedMetrics(graph, customMetrics);
const firstValid = minBy(
- metrics.map(metric => myHistory[metric].find(p => p.value || p.value === 0)),
+ displayedMetrics.map(metric => myHistory[metric].find(p => p.value || p.value === 0)),
'date'
);
- const measureHistory = metrics.map(metric => ({
+ const measureHistory = displayedMetrics.map(metric => ({
metric,
history: firstValid
? myHistory[metric].filter(p => p.date >= firstValid.date)
: myHistory[metric]
}));
- return generateSeries(measureHistory, graph, metricsType, metrics);
- };
-
- getMetricType = (metrics: Array<Metric>, graph: string, customMetrics: Array<string>) => {
- const metricKey = this.getDisplayedMetrics(graph, customMetrics)[0];
- const metric = metrics.find(metric => metric.key === metricKey);
- return metric ? metric.type : 'INT';
+ return generateSeries(measureHistory, graph, metrics, displayedMetrics);
};
handleClick = () => {
@@ -149,7 +137,7 @@ export default class PreviewGraph extends React.PureComponent {
hideGrid={true}
hideXAxis={true}
interpolate="linear"
- metricType={this.state.metricsType}
+ metricType={getSeriesMetricType(series)}
padding={GRAPH_PADDING}
series={series}
showAreas={['coverage', 'duplications'].includes(graph)}
diff --git a/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltips.js b/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltips.js
index 3ee59922574..baf2738df61 100644
--- a/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltips.js
+++ b/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltips.js
@@ -21,7 +21,6 @@ import React from 'react';
import BubblePopup from '../../../components/common/BubblePopup';
import FormattedDate from '../../../components/ui/FormattedDate';
import PreviewGraphTooltipsContent from './PreviewGraphTooltipsContent';
-import { getLocalizedMetricName } from '../../../helpers/l10n';
import type { Metric } from '../types';
import type { Serie } from '../../../components/charts/AdvancedTimeline';
@@ -59,17 +58,16 @@ export default class PreviewGraphTooltips extends React.PureComponent {
</div>
<table className="width-100">
<tbody>
- {this.props.series.map(serie => {
+ {this.props.series.map((serie, idx) => {
const point = serie.data[tooltipIdx];
if (!point || (!point.y && point.y !== 0)) {
return null;
}
- const metric = this.props.metrics.find(metric => metric.key === serie.name);
return (
<PreviewGraphTooltipsContent
key={serie.name}
- serie={serie}
- translatedName={metric ? getLocalizedMetricName(metric) : serie.translatedName}
+ style={idx.toString()}
+ translatedName={serie.translatedName}
value={this.props.formatValue(point.y)}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltipsContent.js b/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltipsContent.js
index f77511bc923..a68e72cc918 100644
--- a/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltipsContent.js
+++ b/server/sonar-web/src/main/js/apps/overview/events/PreviewGraphTooltipsContent.js
@@ -20,20 +20,19 @@
// @flow
import React from 'react';
import ChartLegendIcon from '../../../components/icons-components/ChartLegendIcon';
-import type { Serie } from '../../../components/charts/AdvancedTimeline';
type Props = {
- serie: Serie,
+ style: string,
translatedName: string,
value: string
};
-export default function PreviewGraphTooltipsContent({ serie, translatedName, value }: Props) {
+export default function PreviewGraphTooltipsContent({ style, translatedName, value }: Props) {
return (
<tr className="overview-analysis-graph-tooltip-line">
<td className="thin">
<ChartLegendIcon
- className={'little-spacer-right line-chart-legend line-chart-legend-' + serie.style}
+ className={'little-spacer-right line-chart-legend line-chart-legend-' + style}
/>
</td>
<td className="overview-analysis-graph-tooltip-value text-right little-spacer-right thin">
diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltips-test.js b/server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltips-test.js
index 1b37aaf3691..6292808b788 100644
--- a/server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltips-test.js
+++ b/server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltips-test.js
@@ -24,7 +24,6 @@ import PreviewGraphTooltips from '../PreviewGraphTooltips';
const SERIES_OVERVIEW = [
{
name: 'code_smells',
- style: 1,
data: [
{
x: '2011-10-01T22:01:00.000Z',
@@ -34,11 +33,11 @@ const SERIES_OVERVIEW = [
x: '2011-10-25T10:27:41.000Z',
y: 15
}
- ]
+ ],
+ translatedName: 'Code Smells'
},
{
name: 'bugs',
- style: 0,
data: [
{
x: '2011-10-01T22:01:00.000Z',
@@ -48,11 +47,11 @@ const SERIES_OVERVIEW = [
x: '2011-10-25T10:27:41.000Z',
y: 0
}
- ]
+ ],
+ translatedName: 'Bugs'
},
{
name: 'vulnerabilities',
- style: 2,
data: [
{
x: '2011-10-01T22:01:00.000Z',
@@ -62,7 +61,8 @@ const SERIES_OVERVIEW = [
x: '2011-10-25T10:27:41.000Z',
y: 1
}
- ]
+ ],
+ translatedName: 'Vulnerabilities'
}
];
diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltipsContent-test.js b/server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltipsContent-test.js
index 4195a9cfd1c..5d01a353afe 100644
--- a/server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltipsContent-test.js
+++ b/server/sonar-web/src/main/js/apps/overview/events/__tests__/PreviewGraphTooltipsContent-test.js
@@ -22,11 +22,7 @@ import { shallow } from 'enzyme';
import PreviewGraphTooltipsContent from '../PreviewGraphTooltipsContent';
const DEFAULT_PROPS = {
- serie: {
- name: 'code_smells',
- translatedName: 'metric.code_smells.name',
- style: 1
- },
+ style: 1,
translatedName: 'Code Smells',
value: '1.2k'
};
diff --git a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/PreviewGraphTooltips-test.js.snap b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/PreviewGraphTooltips-test.js.snap
index 455d71d0124..997962effe7 100644
--- a/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/PreviewGraphTooltips-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/overview/events/__tests__/__snapshots__/PreviewGraphTooltips-test.js.snap
@@ -27,62 +27,17 @@ exports[`should render correctly 1`] = `
>
<tbody>
<PreviewGraphTooltipsContent
- serie={
- Object {
- "data": Array [
- Object {
- "x": "2011-10-01T22:01:00.000Z",
- "y": 18,
- },
- Object {
- "x": "2011-10-25T10:27:41.000Z",
- "y": 15,
- },
- ],
- "name": "code_smells",
- "style": 1,
- }
- }
+ style="0"
translatedName="Code Smells"
value="Formated.15"
/>
<PreviewGraphTooltipsContent
- serie={
- Object {
- "data": Array [
- Object {
- "x": "2011-10-01T22:01:00.000Z",
- "y": 3,
- },
- Object {
- "x": "2011-10-25T10:27:41.000Z",
- "y": 0,
- },
- ],
- "name": "bugs",
- "style": 0,
- }
- }
+ style="1"
translatedName="Bugs"
value="Formated.0"
/>
<PreviewGraphTooltipsContent
- serie={
- Object {
- "data": Array [
- Object {
- "x": "2011-10-01T22:01:00.000Z",
- "y": 0,
- },
- Object {
- "x": "2011-10-25T10:27:41.000Z",
- "y": 1,
- },
- ],
- "name": "vulnerabilities",
- "style": 2,
- }
- }
+ style="2"
translatedName="Vulnerabilities"
value="Formated.1"
/>
diff --git a/server/sonar-web/src/main/js/apps/overview/types.js b/server/sonar-web/src/main/js/apps/overview/types.js
index 9cb99b9780c..cde34fb2095 100644
--- a/server/sonar-web/src/main/js/apps/overview/types.js
+++ b/server/sonar-web/src/main/js/apps/overview/types.js
@@ -28,6 +28,8 @@ export type Component = {
export type History = { [string]: Array<{ date: Date, value: string }> };
export type Metric = {
+ custom?: boolean,
+ hidden?: boolean,
key: string,
name: string,
type: string
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.js.snap
index 14647a21294..356f168475d 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.js.snap
@@ -13,8 +13,8 @@ Object {
},
],
"name": "covered_lines",
- "style": "style",
"translatedName": "project_activity.custom_metric.covered_lines",
+ "type": "INT",
}
`;
@@ -24,31 +24,31 @@ Array [
"data": Array [
Object {
"x": 2017-04-27T06:21:32.000Z,
- "y": 100,
+ "y": 88,
},
Object {
"x": 2017-04-30T21:06:24.000Z,
- "y": 100,
+ "y": 50,
},
],
- "name": "lines_to_cover",
- "style": "0",
- "translatedName": "metric.lines_to_cover.name",
+ "name": "covered_lines",
+ "translatedName": "project_activity.custom_metric.covered_lines",
+ "type": "INT",
},
Object {
"data": Array [
Object {
"x": 2017-04-27T06:21:32.000Z,
- "y": 88,
+ "y": 100,
},
Object {
"x": 2017-04-30T21:06:24.000Z,
- "y": 50,
+ "y": 100,
},
],
- "name": "covered_lines",
- "style": "1",
- "translatedName": "project_activity.custom_metric.covered_lines",
+ "name": "lines_to_cover",
+ "translatedName": "Line to Cover",
+ "type": "PERCENT",
},
]
`;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.js b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.js
index d0c8af6370f..6914d110f1e 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.js
@@ -65,7 +65,7 @@ const emptyState = {
analyses: [],
analysesLoading: false,
graphLoading: false,
- loading: false,
+ initialized: true,
measuresHistory: [],
measures: [],
metrics: [],
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js
index df16d8fe359..371294178f6 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js
@@ -72,6 +72,11 @@ const HISTORY = [
}
];
+const METRICS = [
+ { key: 'uncovered_lines', name: 'Uncovered Lines', type: 'INT' },
+ { key: 'lines_to_cover', name: 'Line to Cover', type: 'PERCENT' }
+];
+
const QUERY = {
category: '',
from: new Date('2017-04-27T08:21:32+0200'),
@@ -94,14 +99,14 @@ jest.mock('moment', () => date => ({
describe('generateCoveredLinesMetric', () => {
it('should correctly generate covered lines metric', () => {
- expect(utils.generateCoveredLinesMetric(HISTORY[1], HISTORY, 'style')).toMatchSnapshot();
+ expect(utils.generateCoveredLinesMetric(HISTORY[1], HISTORY)).toMatchSnapshot();
});
});
describe('generateSeries', () => {
it('should correctly generate the series', () => {
expect(
- utils.generateSeries(HISTORY, 'coverage', 'INT', ['lines_to_cover', 'uncovered_lines'])
+ utils.generateSeries(HISTORY, 'coverage', METRICS, ['uncovered_lines', 'lines_to_cover'])
).toMatchSnapshot();
});
});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js
index 963128938be..bb72897ea26 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js
@@ -26,9 +26,8 @@ import GraphsTooltips from './GraphsTooltips';
import GraphsLegendCustom from './GraphsLegendCustom';
import GraphsLegendStatic from './GraphsLegendStatic';
import { formatMeasure, getShortType } from '../../../helpers/measures';
-import { EVENT_TYPES, hasHistoryData, isCustomGraph } from '../utils';
-import { translate } from '../../../helpers/l10n';
-import type { Analysis, MeasureHistory, Metric } from '../types';
+import { EVENT_TYPES, isCustomGraph } from '../utils';
+import type { Analysis, MeasureHistory } from '../types';
import type { Serie } from '../../../components/charts/AdvancedTimeline';
type Props = {
@@ -38,9 +37,7 @@ type Props = {
graphEndDate: ?Date,
graphStartDate: ?Date,
leakPeriodDate: Date,
- loading: boolean,
measuresHistory: Array<MeasureHistory>,
- metrics: Array<Metric>,
metricsType: string,
removeCustomMetric: (metric: string) => void,
selectedDate: ?Date,
@@ -107,43 +104,13 @@ export default class GraphsHistory extends React.PureComponent {
this.setState({ selectedDate, tooltipXPos, tooltipIdx });
render() {
- const { loading } = this.props;
const { graph, series } = this.props;
const isCustom = isCustomGraph(graph);
-
- if (loading) {
- return (
- <div className="project-activity-graph-container">
- <div className="text-center">
- <i className="spinner" />
- </div>
- </div>
- );
- }
-
- if (!hasHistoryData(series)) {
- return (
- <div className="project-activity-graph-container">
- <div className="note text-center">
- {translate(
- isCustom
- ? 'project_activity.graphs.custom.no_history'
- : 'component_measures.no_history'
- )}
- </div>
- </div>
- );
- }
-
const { selectedDate, tooltipIdx, tooltipXPos } = this.state;
return (
<div className="project-activity-graph-container">
{isCustom
- ? <GraphsLegendCustom
- series={series}
- metrics={this.props.metrics}
- removeMetric={this.props.removeCustomMetric}
- />
+ ? <GraphsLegendCustom series={series} removeMetric={this.props.removeCustomMetric} />
: <GraphsLegendStatic series={series} />}
<div className="project-activity-graph">
<AutoSizer>
@@ -173,7 +140,6 @@ export default class GraphsHistory extends React.PureComponent {
graph={graph}
graphWidth={width}
measuresHistory={this.props.measuresHistory}
- metrics={this.props.metrics}
selectedDate={selectedDate}
series={series}
tooltipIdx={tooltipIdx}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendCustom.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendCustom.js
index 72e4009e401..018565d2e9c 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendCustom.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendCustom.js
@@ -17,32 +17,30 @@
* 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 GraphsLegendItem from './GraphsLegendItem';
import Tooltip from '../../../components/controls/Tooltip';
import { hasDataValues } from '../utils';
-import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
-import type { Metric } from '../types';
+import { translate } from '../../../helpers/l10n';
import type { Serie } from '../../../components/charts/AdvancedTimeline';
type Props = {
- metrics: Array<Metric>,
removeMetric: string => void,
series: Array<Serie & { translatedName: string }>
};
-export default function GraphsLegendCustom({ metrics, removeMetric, series }: Props) {
+export default function GraphsLegendCustom({ removeMetric, series }: Props) {
return (
<div className="project-activity-graph-legends">
- {series.map(serie => {
- const metric = metrics.find(metric => metric.key === serie.name);
+ {series.map((serie, idx) => {
const hasData = hasDataValues(serie);
const legendItem = (
<GraphsLegendItem
metric={serie.name}
- name={metric ? getLocalizedMetricName(metric) : serie.translatedName}
+ name={serie.translatedName}
showWarning={!hasData}
- style={serie.style}
+ style={idx.toString()}
removeMetric={removeMetric}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.js
index 9a8fa09a7da..c48119607cf 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.js
@@ -17,23 +17,24 @@
* 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 GraphsLegendItem from './GraphsLegendItem';
type Props = {
- series: Array<{ name: string, translatedName: string, style: string }>
+ series: Array<{ name: string, translatedName: string }>
};
export default function GraphsLegendStatic({ series }: Props) {
return (
<div className="project-activity-graph-legends">
- {series.map(serie =>
+ {series.map((serie, idx) =>
<GraphsLegendItem
className="big-spacer-left big-spacer-right"
key={serie.name}
metric={serie.name}
name={serie.translatedName}
- style={serie.style}
+ style={idx.toString()}
/>
)}
</div>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.js
index 43dd36a2323..977fd6298fc 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.js
@@ -26,8 +26,7 @@ import GraphsTooltipsContentEvents from './GraphsTooltipsContentEvents';
import GraphsTooltipsContentCoverage from './GraphsTooltipsContentCoverage';
import GraphsTooltipsContentDuplication from './GraphsTooltipsContentDuplication';
import GraphsTooltipsContentOverview from './GraphsTooltipsContentOverview';
-import { getLocalizedMetricName } from '../../../helpers/l10n';
-import type { Event, MeasureHistory, Metric } from '../types';
+import type { Event, MeasureHistory } from '../types';
import type { Serie } from '../../../components/charts/AdvancedTimeline';
type Props = {
@@ -36,7 +35,6 @@ type Props = {
graph: string,
graphWidth: number,
measuresHistory: Array<MeasureHistory>,
- metrics: Array<Metric>,
selectedDate: Date,
series: Array<Serie & { translatedName: string }>,
tooltipIdx: number,
@@ -50,7 +48,7 @@ export default class GraphsTooltips extends React.PureComponent {
render() {
const { events, measuresHistory, tooltipIdx } = this.props;
- const top = 50;
+ const top = 30;
let left = this.props.tooltipPos + 60;
let customClass;
if (left > this.props.graphWidth - TOOLTIP_WIDTH - 50) {
@@ -65,7 +63,7 @@ export default class GraphsTooltips extends React.PureComponent {
</div>
<table className="width-100">
<tbody>
- {this.props.series.map(serie => {
+ {this.props.series.map((serie, idx) => {
const point = serie.data[tooltipIdx];
if (!point || (!point.y && point.y !== 0)) {
return null;
@@ -75,20 +73,20 @@ export default class GraphsTooltips extends React.PureComponent {
<GraphsTooltipsContentOverview
key={serie.name}
measuresHistory={measuresHistory}
- serie={serie}
+ name={serie.name}
+ style={idx.toString()}
tooltipIdx={tooltipIdx}
+ translatedName={serie.translatedName}
value={this.props.formatValue(point.y)}
/>
);
} else {
- const metric = this.props.metrics.find(metric => metric.key === serie.name);
return (
<GraphsTooltipsContent
key={serie.name}
- serie={serie}
- translatedName={
- metric ? getLocalizedMetricName(metric) : serie.translatedName
- }
+ name={serie.name}
+ style={idx.toString()}
+ translatedName={serie.translatedName}
value={this.props.formatValue(point.y)}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContent.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContent.js
index 5d35236a723..288bc539b18 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContent.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContent.js
@@ -21,23 +21,20 @@
import React from 'react';
import classNames from 'classnames';
import ChartLegendIcon from '../../../components/icons-components/ChartLegendIcon';
-import type { Serie } from '../../../components/charts/AdvancedTimeline';
type Props = {
- serie: Serie,
+ name: string,
+ style: string,
translatedName: string,
value: string
};
-export default function GraphsTooltipsContent({ serie, translatedName, value }: Props) {
+export default function GraphsTooltipsContent({ name, style, translatedName, value }: Props) {
return (
- <tr key={serie.name} className="project-activity-graph-tooltip-line">
+ <tr key={name} className="project-activity-graph-tooltip-line">
<td className="thin">
<ChartLegendIcon
- className={classNames(
- 'spacer-right line-chart-legend',
- 'line-chart-legend-' + serie.style
- )}
+ className={classNames('spacer-right line-chart-legend', 'line-chart-legend-' + style)}
/>
</td>
<td className="project-activity-graph-tooltip-value text-right spacer-right thin">
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentEvents.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentEvents.js
index c2759dc7f35..5cfe5bdce9b 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentEvents.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentEvents.js
@@ -35,21 +35,18 @@ export default function GraphsTooltipsContentEvents({ events }: Props) {
<hr />
</td>
</tr>
- {events.map(event =>
- <tr key={event.key} className="project-activity-graph-tooltip-line">
- <td className="text-top spacer-right thin">
- <ProjectEventIcon
- className={'project-activity-event-icon margin-align ' + event.category}
- />
- </td>
- <td colSpan="2">
- <span className="little-spacer-right">
- {translate('event.category', event.category)}:
+ <tr className="project-activity-graph-tooltip-line">
+ <td colSpan="3">
+ <span>
+ {translate('events')}:
+ </span>
+ {events.map(event =>
+ <span key={event.key} className="spacer-left">
+ <ProjectEventIcon className={'project-activity-event-icon ' + event.category} />
</span>
- {event.name}
- </td>
- </tr>
- )}
+ )}
+ </td>
+ </tr>
</tbody>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentOverview.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentOverview.js
index ecd0f5ac126..439e0f8d401 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentOverview.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentOverview.js
@@ -22,13 +22,14 @@ import React from 'react';
import classNames from 'classnames';
import ChartLegendIcon from '../../../components/icons-components/ChartLegendIcon';
import Rating from '../../../components/ui/Rating';
-import type { Serie } from '../../../components/charts/AdvancedTimeline';
import type { MeasureHistory } from '../types';
type Props = {
measuresHistory: Array<MeasureHistory>,
- serie: Serie & { translatedName: string },
+ name: string,
+ style: string,
tooltipIdx: number,
+ translatedName: string,
value: string
};
@@ -40,19 +41,19 @@ const METRIC_RATING = {
export default function GraphsTooltipsContentOverview(props: Props) {
const rating = props.measuresHistory.find(
- measure => measure.metric === METRIC_RATING[props.serie.name]
+ measure => measure.metric === METRIC_RATING[props.name]
);
if (!rating || !rating.history[props.tooltipIdx]) {
return null;
}
const ratingValue = rating.history[props.tooltipIdx].value;
return (
- <tr key={props.serie.name} className="project-activity-graph-tooltip-overview-line">
+ <tr key={props.name} className="project-activity-graph-tooltip-overview-line">
<td className="thin">
<ChartLegendIcon
className={classNames(
'spacer-right line-chart-legend',
- 'line-chart-legend-' + props.serie.style
+ 'line-chart-legend-' + props.style
)}
/>
</td>
@@ -63,7 +64,7 @@ export default function GraphsTooltipsContentOverview(props: Props) {
{ratingValue && <Rating className="spacer-left" small={true} value={ratingValue} />}
</td>
<td>
- {props.serie.translatedName}
+ {props.translatedName}
</td>
</tr>
);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
index e3a3b582796..0af7a5acd85 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
@@ -24,7 +24,6 @@ import moment from 'moment';
import ProjectActivityPageHeader from './ProjectActivityPageHeader';
import ProjectActivityAnalysesList from './ProjectActivityAnalysesList';
import ProjectActivityGraphs from './ProjectActivityGraphs';
-import { getDisplayedHistoryMetrics } from '../utils';
import { translate } from '../../../helpers/l10n';
import './projectActivity.css';
import type { Analysis, MeasureHistory, Metric, Query } from '../types';
@@ -46,65 +45,50 @@ type Props = {
updateQuery: (newQuery: Query) => void
};
-export default class ProjectActivityApp extends React.PureComponent {
- props: Props;
+export default function ProjectActivityApp(props: Props) {
+ const { analyses, measuresHistory, query } = props;
+ const { configuration } = props.project;
+ const canAdmin = configuration ? configuration.showHistory : false;
+ return (
+ <div id="project-activity" className="page page-limited">
+ <Helmet title={translate('project_activity.page')} />
- getMetricType = () => {
- const historyMetrics = getDisplayedHistoryMetrics(
- this.props.query.graph,
- this.props.query.customMetrics
- );
- const metricKey = historyMetrics.length > 0 ? historyMetrics[0] : '';
- const metric = this.props.metrics.find(metric => metric.key === metricKey);
- return metric ? metric.type : 'INT';
- };
+ <ProjectActivityPageHeader
+ category={query.category}
+ from={query.from}
+ to={query.to}
+ updateQuery={props.updateQuery}
+ />
- render() {
- const { analyses, measuresHistory, query } = this.props;
- const { configuration } = this.props.project;
- const canAdmin = configuration ? configuration.showHistory : false;
- return (
- <div id="project-activity" className="page page-limited">
- <Helmet title={translate('project_activity.page')} />
-
- <ProjectActivityPageHeader
- category={query.category}
- from={query.from}
- to={query.to}
- updateQuery={this.props.updateQuery}
- />
-
- <div className="layout-page project-activity-page">
- <div className="layout-page-side-outer project-activity-page-side-outer boxed-group">
- <ProjectActivityAnalysesList
- addCustomEvent={this.props.addCustomEvent}
- addVersion={this.props.addVersion}
- analysesLoading={this.props.analysesLoading}
- analyses={analyses}
- canAdmin={canAdmin}
- className="boxed-group-inner"
- changeEvent={this.props.changeEvent}
- deleteAnalysis={this.props.deleteAnalysis}
- deleteEvent={this.props.deleteEvent}
- loading={this.props.loading}
- query={this.props.query}
- updateQuery={this.props.updateQuery}
- />
- </div>
- <div className="project-activity-layout-page-main">
- <ProjectActivityGraphs
- analyses={analyses}
- leakPeriodDate={moment(this.props.project.leakPeriodDate).toDate()}
- loading={this.props.graphLoading}
- measuresHistory={measuresHistory}
- metrics={this.props.metrics}
- metricsType={this.getMetricType()}
- query={query}
- updateQuery={this.props.updateQuery}
- />
- </div>
+ <div className="layout-page project-activity-page">
+ <div className="layout-page-side-outer project-activity-page-side-outer boxed-group">
+ <ProjectActivityAnalysesList
+ addCustomEvent={props.addCustomEvent}
+ addVersion={props.addVersion}
+ analysesLoading={props.analysesLoading}
+ analyses={analyses}
+ canAdmin={canAdmin}
+ className="boxed-group-inner"
+ changeEvent={props.changeEvent}
+ deleteAnalysis={props.deleteAnalysis}
+ deleteEvent={props.deleteEvent}
+ loading={props.loading}
+ query={props.query}
+ updateQuery={props.updateQuery}
+ />
+ </div>
+ <div className="project-activity-layout-page-main">
+ <ProjectActivityGraphs
+ analyses={analyses}
+ leakPeriodDate={moment(props.project.leakPeriodDate).toDate()}
+ loading={props.graphLoading}
+ measuresHistory={measuresHistory}
+ metrics={props.metrics}
+ query={query}
+ updateQuery={props.updateQuery}
+ />
</div>
</div>
- );
- }
+ </div>
+ );
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
index 035693ad599..f20a8d80cc3 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
@@ -54,7 +54,7 @@ export type State = {
analyses: Array<Analysis>,
analysesLoading: boolean,
graphLoading: boolean,
- loading: boolean,
+ initialized: boolean,
metrics: Array<Metric>,
measuresHistory: Array<MeasureHistory>,
paging?: Paging,
@@ -72,7 +72,7 @@ class ProjectActivityAppContainer extends React.PureComponent {
analyses: [],
analysesLoading: false,
graphLoading: true,
- loading: true,
+ initialized: false,
measuresHistory: [],
metrics: [],
query: parseQuery(props.location.query)
@@ -92,16 +92,22 @@ class ProjectActivityAppContainer extends React.PureComponent {
componentDidMount() {
this.mounted = true;
- this.firstLoadData();
const elem = document.querySelector('html');
elem && elem.classList.add('dashboard-page');
+ if (!this.shouldRedirect()) {
+ this.firstLoadData(this.state.query);
+ }
}
componentWillReceiveProps(nextProps: Props) {
if (nextProps.location.query !== this.props.location.query) {
const query = parseQuery(nextProps.location.query);
if (query.graph !== this.state.query.graph || customMetricsChanged(this.state.query, query)) {
- this.updateGraphData(query.graph, query.customMetrics);
+ if (this.state.initialized) {
+ this.updateGraphData(query.graph, query.customMetrics);
+ } else {
+ this.firstLoadData(query);
+ }
}
this.setState({ query });
}
@@ -203,32 +209,23 @@ class ProjectActivityAppContainer extends React.PureComponent {
});
};
- firstLoadData() {
- const { query } = this.state;
+ firstLoadData(query: Query) {
const graphMetrics = getHistoryMetrics(query.graph, query.customMetrics);
- const ignoreHistory = this.shouldRedirect();
Promise.all([
this.fetchActivity(query.project, 1, 100, serializeQuery(query)),
this.fetchMetrics(),
- ignoreHistory ? Promise.resolve() : this.fetchMeasuresHistory(graphMetrics)
+ this.fetchMeasuresHistory(graphMetrics)
]).then(response => {
if (this.mounted) {
- const newState = {
+ this.setState({
analyses: response[0].analyses,
analysesLoading: true,
- loading: false,
+ graphLoading: false,
+ initialized: true,
+ measuresHistory: response[2],
metrics: response[1],
paging: response[0].paging
- };
- if (ignoreHistory) {
- this.setState(newState);
- } else {
- this.setState({
- ...newState,
- graphLoading: false,
- measuresHistory: response[2]
- });
- }
+ });
this.loadAllActivities(query.project).then(({ analyses, paging }) => {
if (this.mounted) {
@@ -288,8 +285,8 @@ class ProjectActivityAppContainer extends React.PureComponent {
changeEvent={this.changeEvent}
deleteAnalysis={this.deleteAnalysis}
deleteEvent={this.deleteEvent}
- graphLoading={this.state.loading || this.state.graphLoading}
- loading={this.state.loading}
+ graphLoading={!this.state.initialized || this.state.graphLoading}
+ loading={!this.state.initialized}
metrics={this.state.metrics}
measuresHistory={this.state.measuresHistory}
project={this.props.project}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js
index c5b0698f954..80e881c755a 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js
@@ -19,7 +19,7 @@
*/
// @flow
import React from 'react';
-import { debounce, findLast, maxBy, minBy, sortBy } from 'lodash';
+import { debounce, findLast, maxBy, minBy, sortBy, groupBy, flatMap, chunk } from 'lodash';
import ProjectActivityGraphsHeader from './ProjectActivityGraphsHeader';
import GraphsZoom from './GraphsZoom';
import GraphsHistory from './GraphsHistory';
@@ -29,8 +29,11 @@ import {
isCustomGraph,
generateSeries,
getDisplayedHistoryMetrics,
+ getSeriesMetricType,
+ hasHistoryData,
historyQueryChanged
} from '../utils';
+import { translate } from '../../../helpers/l10n';
import type { RawQuery } from '../../../helpers/query';
import type { Analysis, MeasureHistory, Metric, Query } from '../types';
import type { Serie } from '../../../components/charts/AdvancedTimeline';
@@ -41,7 +44,6 @@ type Props = {
loading: boolean,
measuresHistory: Array<MeasureHistory>,
metrics: Array<Metric>,
- metricsType: string,
query: Query,
updateQuery: RawQuery => void
};
@@ -49,9 +51,13 @@ type Props = {
type State = {
graphStartDate: ?Date,
graphEndDate: ?Date,
- series: Array<Serie>
+ series: Array<Serie>,
+ graphs: Array<Array<Serie>>
};
+const MAX_GRAPH_NB = 2;
+const MAX_SERIES_PER_GRAPH = 3;
+
export default class ProjectActivityGraphs extends React.PureComponent {
props: Props;
state: State;
@@ -61,15 +67,20 @@ export default class ProjectActivityGraphs extends React.PureComponent {
const series = generateSeries(
props.measuresHistory,
props.query.graph,
- props.metricsType,
+ props.metrics,
getDisplayedHistoryMetrics(props.query.graph, props.query.customMetrics)
);
- this.state = { series, ...this.getStateZoomDates(null, props, series) };
+ this.state = {
+ series,
+ graphs: this.splitSeriesInGraphs(series),
+ ...this.getStateZoomDates(null, props, series)
+ };
this.updateQueryDateRange = debounce(this.updateQueryDateRange, 500);
}
componentWillReceiveProps(nextProps: Props) {
let newSeries;
+ let newGraphs;
if (
nextProps.measuresHistory !== this.props.measuresHistory ||
historyQueryChanged(this.props.query, nextProps.query)
@@ -77,9 +88,10 @@ export default class ProjectActivityGraphs extends React.PureComponent {
newSeries = generateSeries(
nextProps.measuresHistory,
nextProps.query.graph,
- nextProps.metricsType,
+ nextProps.metrics,
getDisplayedHistoryMetrics(nextProps.query.graph, nextProps.query.customMetrics)
);
+ newGraphs = this.splitSeriesInGraphs(newSeries);
}
const newDates = this.getStateZoomDates(this.props, nextProps, newSeries);
@@ -88,6 +100,7 @@ export default class ProjectActivityGraphs extends React.PureComponent {
let newState = {};
if (newSeries) {
newState.series = newSeries;
+ newState.graphs = newGraphs;
}
if (newDates) {
newState = { ...newState, ...newDates };
@@ -120,6 +133,15 @@ export default class ProjectActivityGraphs extends React.PureComponent {
}
};
+ getMetricsTypeFilter = (): ?Array<string> => {
+ if (this.state.graphs.length < MAX_GRAPH_NB) {
+ return null;
+ }
+ return this.state.graphs
+ .filter(graph => graph.length < MAX_SERIES_PER_GRAPH)
+ .map(graph => graph[0].type);
+ };
+
addCustomMetric = (metric: string) => {
const customMetrics = [...this.props.query.customMetrics, metric];
saveCustomGraph(customMetrics);
@@ -132,6 +154,11 @@ export default class ProjectActivityGraphs extends React.PureComponent {
this.props.updateQuery({ customMetrics });
};
+ splitSeriesInGraphs = (series: Array<Serie>): Array<Array<Serie>> =>
+ flatMap(groupBy(series, serie => serie.type), groupType =>
+ chunk(groupType, MAX_SERIES_PER_GRAPH)
+ ).slice(0, MAX_GRAPH_NB);
+
updateGraph = (graph: string) => {
saveGraph(graph);
if (isCustomGraph(graph) && this.props.query.customMetrics.length <= 0) {
@@ -165,41 +192,76 @@ export default class ProjectActivityGraphs extends React.PureComponent {
}
};
+ renderGraphs() {
+ const { leakPeriodDate, loading, query } = this.props;
+ const { graphEndDate, graphs, graphStartDate, series } = this.state;
+ const isCustom = isCustomGraph(query.graph);
+
+ if (loading) {
+ return (
+ <div className="project-activity-graph-container">
+ <div className="text-center">
+ <i className="spinner" />
+ </div>
+ </div>
+ );
+ }
+
+ if (!hasHistoryData(series)) {
+ return (
+ <div className="project-activity-graph-container">
+ <div className="note text-center">
+ {translate(
+ isCustom
+ ? 'project_activity.graphs.custom.no_history'
+ : 'component_measures.no_history'
+ )}
+ </div>
+ </div>
+ );
+ }
+
+ return graphs.map((series, idx) =>
+ <GraphsHistory
+ key={idx}
+ analyses={this.props.analyses}
+ eventFilter={query.category}
+ graph={query.graph}
+ graphEndDate={graphEndDate}
+ graphStartDate={graphStartDate}
+ leakPeriodDate={leakPeriodDate}
+ measuresHistory={this.props.measuresHistory}
+ metricsType={getSeriesMetricType(series)}
+ removeCustomMetric={this.removeCustomMetric}
+ selectedDate={this.props.query.selectedDate}
+ series={series}
+ updateGraphZoom={this.updateGraphZoom}
+ updateSelectedDate={this.updateSelectedDate}
+ />
+ );
+ }
+
render() {
- const { leakPeriodDate, loading, metrics, metricsType, query } = this.props;
- const { series } = this.state;
+ const { leakPeriodDate, loading, metrics, query } = this.props;
+ const { graphEndDate, graphStartDate, series } = this.state;
+
return (
<div className="project-activity-layout-page-main-inner boxed-group boxed-group-inner">
<ProjectActivityGraphsHeader
addCustomMetric={this.addCustomMetric}
graph={query.graph}
metrics={metrics}
+ metricsTypeFilter={this.getMetricsTypeFilter()}
selectedMetrics={this.props.query.customMetrics}
updateGraph={this.updateGraph}
/>
- <GraphsHistory
- analyses={this.props.analyses}
- eventFilter={query.category}
- graph={query.graph}
- graphEndDate={this.state.graphEndDate}
- graphStartDate={this.state.graphStartDate}
- leakPeriodDate={leakPeriodDate}
- loading={loading}
- measuresHistory={this.props.measuresHistory}
- metrics={metrics}
- metricsType={metricsType}
- removeCustomMetric={this.removeCustomMetric}
- selectedDate={this.props.query.selectedDate}
- series={series}
- updateGraphZoom={this.updateGraphZoom}
- updateSelectedDate={this.updateSelectedDate}
- />
+ {this.renderGraphs()}
<GraphsZoom
- graphEndDate={this.state.graphEndDate}
- graphStartDate={this.state.graphStartDate}
+ graphEndDate={graphEndDate}
+ graphStartDate={graphStartDate}
leakPeriodDate={leakPeriodDate}
loading={loading}
- metricsType={metricsType}
+ metricsType={getSeriesMetricType(series)}
series={series}
showAreas={['coverage', 'duplications'].includes(query.graph)}
updateGraphZoom={this.updateGraphZoom}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.js
index d4e82b0fa62..3b536c25956 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.js
@@ -29,6 +29,7 @@ type Props = {
addCustomMetric: string => void,
graph: string,
metrics: Array<Metric>,
+ metricsTypeFilter: ?Array<string>,
selectedMetrics: Array<string>,
updateGraph: string => void
};
@@ -63,6 +64,7 @@ export default class ProjectActivityGraphsHeader extends React.PureComponent {
addMetric={this.props.addCustomMetric}
className="pull-left spacer-left"
metrics={this.props.metrics}
+ metricsTypeFilter={this.props.metricsTypeFilter}
selectedMetrics={this.props.selectedMetrics}
/>}
</header>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.js b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.js
index f13577ae571..ab4fec25fb0 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.js
@@ -60,7 +60,6 @@ const SERIES = [
{
name: 'bugs',
translatedName: 'metric.bugs.name',
- style: 0,
data: [
{ x: new Date('2016-10-27T16:33:50+0200'), y: 5 },
{ x: new Date('2016-10-27T12:21:15+0200'), y: 16 },
@@ -69,15 +68,6 @@ const SERIES = [
}
];
-const EMPTY_SERIES = [
- {
- name: 'bugs',
- translatedName: 'metric.bugs.name',
- style: 0,
- data: []
- }
-];
-
const DEFAULT_PROPS = {
analyses: ANALYSES,
eventFilter: '',
@@ -85,9 +75,7 @@ const DEFAULT_PROPS = {
graphEndDate: null,
graphStartDate: null,
leakPeriodDate: '2017-05-16T13:50:02+0200',
- loading: false,
measuresHistory: [],
- metrics: [],
metricsType: 'INT',
removeCustomMetric: () => {},
selectedDate: null,
@@ -96,14 +84,6 @@ const DEFAULT_PROPS = {
updateSelectedDate: () => {}
};
-it('should show a loading view', () => {
- expect(shallow(<GraphsHistory {...DEFAULT_PROPS} loading={true} />)).toMatchSnapshot();
-});
-
-it('should show that there is no data', () => {
- expect(shallow(<GraphsHistory {...DEFAULT_PROPS} series={EMPTY_SERIES} />)).toMatchSnapshot();
-});
-
it('should correctly render a graph', () => {
expect(shallow(<GraphsHistory {...DEFAULT_PROPS} />)).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendCustom-test.js b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendCustom-test.js
index c115f0b5f13..7dd1bfdef5a 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendCustom-test.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendCustom-test.js
@@ -22,23 +22,15 @@ import { shallow } from 'enzyme';
import GraphsLegendCustom from '../GraphsLegendCustom';
const SERIES = [
- { name: 'bugs', translatedName: 'Bugs', style: '2', data: [{ x: 1, y: 1 }] },
+ { name: 'bugs', translatedName: 'Bugs', data: [{ x: 1, y: 1 }] },
{
name: 'my_metric',
- translatedName: 'metric.my_metric.name',
- style: '1',
+ translatedName: 'My Metric',
data: [{ x: 1, y: 1 }]
},
- { name: 'foo', translatedName: 'Foo', style: '0', data: [] }
-];
-
-const METRICS = [
- { key: 'bugs', name: 'Bugs' },
- { key: 'my_metric', name: 'My Metric', custom: true }
+ { name: 'foo', translatedName: 'Foo', data: [] }
];
it('should render correctly the list of series', () => {
- expect(
- shallow(<GraphsLegendCustom metrics={METRICS} removeMetric={() => {}} series={SERIES} />)
- ).toMatchSnapshot();
+ expect(shallow(<GraphsLegendCustom removeMetric={() => {}} series={SERIES} />)).toMatchSnapshot();
});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendStatic-test.js b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendStatic-test.js
index 2226ebb4208..40e9c83e3c2 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendStatic-test.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendStatic-test.js
@@ -22,8 +22,8 @@ import { shallow } from 'enzyme';
import GraphsLegendStatic from '../GraphsLegendStatic';
const SERIES = [
- { name: 'bugs', translatedName: 'Bugs', style: '2', data: [] },
- { name: 'code_smells', translatedName: 'Code Smells', style: '1', data: [] }
+ { name: 'bugs', translatedName: 'Bugs', data: [] },
+ { name: 'code_smells', translatedName: 'Code Smells', data: [] }
];
it('should render correctly the list of series', () => {
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltips-test.js b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltips-test.js
index cebdb8265a7..a08472315e2 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltips-test.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltips-test.js
@@ -23,39 +23,36 @@ import GraphsTooltips from '../GraphsTooltips';
const SERIES_OVERVIEW = [
{
- name: 'code_smells',
- translatedName: 'metric.code_smells.name',
- style: 1,
+ name: 'bugs',
+ translatedName: 'Bugs',
data: [
{
x: '2011-10-01T22:01:00.000Z',
- y: 18
+ y: 3
},
{
x: '2011-10-25T10:27:41.000Z',
- y: 15
+ y: 0
}
]
},
{
- name: 'bugs',
- translatedName: 'metric.bugs.name',
- style: 0,
+ name: 'code_smells',
+ translatedName: 'Code Smells',
data: [
{
x: '2011-10-01T22:01:00.000Z',
- y: 3
+ y: 18
},
{
x: '2011-10-25T10:27:41.000Z',
- y: 0
+ y: 15
}
]
},
{
name: 'vulnerabilities',
- translatedName: 'metric.vulnerabilities.name',
- style: 2,
+ translatedName: 'Vulnerabilities',
data: [
{
x: '2011-10-01T22:01:00.000Z',
@@ -69,17 +66,11 @@ const SERIES_OVERVIEW = [
}
];
-const METRICS = [
- { key: 'bugs', name: 'Bugs', type: 'INT' },
- { key: 'vulnerabilities', name: 'Vulnerabilities', type: 'INT', custom: true }
-];
-
const DEFAULT_PROPS = {
formatValue: val => 'Formated.' + val,
graph: 'overview',
graphWidth: 500,
measuresHistory: [],
- metrics: METRICS,
selectedDate: new Date('2011-10-01T22:01:00.000Z'),
series: SERIES_OVERVIEW,
tooltipIdx: 0,
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContent-test.js b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContent-test.js
index ce610f0bdf9..588d543fc5e 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContent-test.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContent-test.js
@@ -22,11 +22,8 @@ import { shallow } from 'enzyme';
import GraphsTooltipsContent from '../GraphsTooltipsContent';
const DEFAULT_PROPS = {
- serie: {
- name: 'code_smells',
- translatedName: 'metric.code_smells.name',
- style: 1
- },
+ name: 'code_smells',
+ style: 1,
translatedName: 'Code Smells',
value: '1.2k'
};
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentOverview-test.js b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentOverview-test.js
index cae7b7bcec4..c0e67204522 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentOverview-test.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentOverview-test.js
@@ -51,12 +51,10 @@ const MEASURES_OVERVIEW = [
const DEFAULT_PROPS = {
measuresHistory: MEASURES_OVERVIEW,
- serie: {
- name: 'bugs',
- translatedName: 'Bugs',
- style: 2
- },
+ name: 'bugs',
+ style: '2',
tooltipIdx: 1,
+ translatedName: 'Bugs',
value: '1.2k'
};
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.js b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.js
index 07930d40463..69327953ec2 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.js
@@ -56,6 +56,8 @@ const ANALYSES = [
}
];
+const METRICS = [{ key: 'code_smells', name: 'Code Smells', type: 'INT' }];
+
const DEFAULT_PROPS = {
analyses: ANALYSES,
leakPeriodDate: '2017-05-16T13:50:02+0200',
@@ -70,7 +72,7 @@ const DEFAULT_PROPS = {
]
}
],
- metricsType: 'INT',
+ metrics: METRICS,
query: { category: '', graph: 'overview', project: 'org.sonarsource.sonarqube:sonarqube' },
updateQuery: () => {}
};
@@ -88,3 +90,39 @@ it('should render correctly with filter history on dates', () => {
);
expect(wrapper.state()).toMatchSnapshot();
});
+
+it('should show a loading view instead of the graph', () => {
+ expect(
+ shallow(<ProjectActivityGraphs {...DEFAULT_PROPS} loading={true} />).find('.spinner')
+ ).toHaveLength(1);
+});
+
+it('should show that there is no history data', () => {
+ expect(
+ shallow(
+ <ProjectActivityGraphs
+ {...DEFAULT_PROPS}
+ measuresHistory={[{ metric: 'code_smells', history: [] }]}
+ />
+ )
+ ).toMatchSnapshot();
+ expect(
+ shallow(
+ <ProjectActivityGraphs
+ {...DEFAULT_PROPS}
+ measuresHistory={[
+ {
+ metric: 'code_smells',
+ history: [{ date: new Date('2016-10-26T12:17:29+0200'), value: undefined }]
+ }
+ ]}
+ query={{
+ category: '',
+ graph: 'custom',
+ project: 'org.sonarsource.sonarqube:sonarqube',
+ customMetrics: ['code_smells']
+ }}
+ />
+ )
+ ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsHistory-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsHistory-test.js.snap
index 06e5621c923..7f82cd14330 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsHistory-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsHistory-test.js.snap
@@ -48,7 +48,6 @@ exports[`should correctly render a graph 1`] = `
},
],
"name": "bugs",
- "style": 0,
"translatedName": "metric.bugs.name",
},
]
@@ -63,29 +62,3 @@ exports[`should correctly render a graph 1`] = `
</div>
</div>
`;
-
-exports[`should show a loading view 1`] = `
-<div
- className="project-activity-graph-container"
->
- <div
- className="text-center"
- >
- <i
- className="spinner"
- />
- </div>
-</div>
-`;
-
-exports[`should show that there is no data 1`] = `
-<div
- className="project-activity-graph-container"
->
- <div
- className="note text-center"
- >
- component_measures.no_history
- </div>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendCustom-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendCustom-test.js.snap
index d90f7a6843d..cd8ec0cc2f4 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendCustom-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendCustom-test.js.snap
@@ -12,7 +12,7 @@ exports[`should render correctly the list of series 1`] = `
name="Bugs"
removeMetric={[Function]}
showWarning={false}
- style="2"
+ style="0"
/>
</span>
<span
@@ -38,7 +38,7 @@ exports[`should render correctly the list of series 1`] = `
name="Foo"
removeMetric={[Function]}
showWarning={true}
- style="0"
+ style="2"
/>
</span>
</Tooltip>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendStatic-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendStatic-test.js.snap
index 97610c6365f..47cc682d03f 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendStatic-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendStatic-test.js.snap
@@ -8,7 +8,7 @@ exports[`should render correctly the list of series 1`] = `
className="big-spacer-left big-spacer-right"
metric="bugs"
name="Bugs"
- style="2"
+ style="0"
/>
<GraphsLegendItem
className="big-spacer-left big-spacer-right"
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltips-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltips-test.js.snap
index ca571b58091..1d344f19d0b 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltips-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltips-test.js.snap
@@ -6,7 +6,7 @@ exports[`should render correctly for overview graphs 1`] = `
position={
Object {
"left": 476,
- "top": 50,
+ "top": 30,
"width": 250,
}
}
@@ -28,68 +28,26 @@ exports[`should render correctly for overview graphs 1`] = `
<tbody>
<GraphsTooltipsContentOverview
measuresHistory={Array []}
- serie={
- Object {
- "data": Array [
- Object {
- "x": "2011-10-01T22:01:00.000Z",
- "y": 18,
- },
- Object {
- "x": "2011-10-25T10:27:41.000Z",
- "y": 15,
- },
- ],
- "name": "code_smells",
- "style": 1,
- "translatedName": "metric.code_smells.name",
- }
- }
+ name="bugs"
+ style="0"
tooltipIdx={0}
- value="Formated.18"
+ translatedName="Bugs"
+ value="Formated.3"
/>
<GraphsTooltipsContentOverview
measuresHistory={Array []}
- serie={
- Object {
- "data": Array [
- Object {
- "x": "2011-10-01T22:01:00.000Z",
- "y": 3,
- },
- Object {
- "x": "2011-10-25T10:27:41.000Z",
- "y": 0,
- },
- ],
- "name": "bugs",
- "style": 0,
- "translatedName": "metric.bugs.name",
- }
- }
+ name="code_smells"
+ style="1"
tooltipIdx={0}
- value="Formated.3"
+ translatedName="Code Smells"
+ value="Formated.18"
/>
<GraphsTooltipsContentOverview
measuresHistory={Array []}
- serie={
- Object {
- "data": Array [
- Object {
- "x": "2011-10-01T22:01:00.000Z",
- "y": 0,
- },
- Object {
- "x": "2011-10-25T10:27:41.000Z",
- "y": 1,
- },
- ],
- "name": "vulnerabilities",
- "style": 2,
- "translatedName": "metric.vulnerabilities.name",
- }
- }
+ name="vulnerabilities"
+ style="2"
tooltipIdx={0}
+ translatedName="Vulnerabilities"
value="Formated.0"
/>
</tbody>
@@ -104,7 +62,7 @@ exports[`should render correctly for random graphs 1`] = `
position={
Object {
"left": 476,
- "top": 50,
+ "top": 30,
"width": 250,
}
}
@@ -125,65 +83,20 @@ exports[`should render correctly for random graphs 1`] = `
>
<tbody>
<GraphsTooltipsContent
- serie={
- Object {
- "data": Array [
- Object {
- "x": "2011-10-01T22:01:00.000Z",
- "y": 18,
- },
- Object {
- "x": "2011-10-25T10:27:41.000Z",
- "y": 15,
- },
- ],
- "name": "code_smells",
- "style": 1,
- "translatedName": "metric.code_smells.name",
- }
- }
- translatedName="metric.code_smells.name"
- value="Formated.15"
- />
- <GraphsTooltipsContent
- serie={
- Object {
- "data": Array [
- Object {
- "x": "2011-10-01T22:01:00.000Z",
- "y": 3,
- },
- Object {
- "x": "2011-10-25T10:27:41.000Z",
- "y": 0,
- },
- ],
- "name": "bugs",
- "style": 0,
- "translatedName": "metric.bugs.name",
- }
- }
+ name="bugs"
+ style="0"
translatedName="Bugs"
value="Formated.0"
/>
<GraphsTooltipsContent
- serie={
- Object {
- "data": Array [
- Object {
- "x": "2011-10-01T22:01:00.000Z",
- "y": 0,
- },
- Object {
- "x": "2011-10-25T10:27:41.000Z",
- "y": 1,
- },
- ],
- "name": "vulnerabilities",
- "style": 2,
- "translatedName": "metric.vulnerabilities.name",
- }
- }
+ name="code_smells"
+ style="1"
+ translatedName="Code Smells"
+ value="Formated.15"
+ />
+ <GraphsTooltipsContent
+ name="vulnerabilities"
+ style="2"
translatedName="Vulnerabilities"
value="Formated.1"
/>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.js.snap
index af1efe60bde..95e4c4b18a1 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.js.snap
@@ -14,44 +14,26 @@ exports[`should render correctly 1`] = `
className="project-activity-graph-tooltip-line"
>
<td
- className="text-top spacer-right thin"
- >
- <ProjectEventIcon
- className="project-activity-event-icon margin-align VERSION"
- />
- </td>
- <td
- colSpan="2"
+ colSpan="3"
>
+ <span>
+ events
+ :
+ </span>
<span
- className="little-spacer-right"
+ className="spacer-left"
>
- event.category.VERSION
- :
+ <ProjectEventIcon
+ className="project-activity-event-icon VERSION"
+ />
</span>
- 6.5
- </td>
- </tr>
- <tr
- className="project-activity-graph-tooltip-line"
- >
- <td
- className="text-top spacer-right thin"
- >
- <ProjectEventIcon
- className="project-activity-event-icon margin-align OTHER"
- />
- </td>
- <td
- colSpan="2"
- >
<span
- className="little-spacer-right"
+ className="spacer-left"
>
- event.category.OTHER
- :
+ <ProjectEventIcon
+ className="project-activity-event-icon OTHER"
+ />
</span>
- Foo
</td>
</tr>
</tbody>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap
index f966955c248..d1d34065dc4 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap
@@ -142,7 +142,6 @@ exports[`should render correctly 1`] = `
},
]
}
- metricsType="INT"
query={
Object {
"category": "",
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap
index 7aaf0a0268d..3d4be8af2fa 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap
@@ -7,6 +7,16 @@ exports[`should render correctly the graph and legends 1`] = `
<ProjectActivityGraphsHeader
addCustomMetric={[Function]}
graph="overview"
+ metrics={
+ Array [
+ Object {
+ "key": "code_smells",
+ "name": "Code Smells",
+ "type": "INT",
+ },
+ ]
+ }
+ metricsTypeFilter={null}
updateGraph={[Function]}
/>
<GraphsHistory
@@ -51,7 +61,6 @@ exports[`should render correctly the graph and legends 1`] = `
graphEndDate={null}
graphStartDate={null}
leakPeriodDate="2017-05-16T13:50:02+0200"
- loading={false}
measuresHistory={
Array [
Object {
@@ -93,8 +102,8 @@ exports[`should render correctly the graph and legends 1`] = `
},
],
"name": "code_smells",
- "style": "1",
- "translatedName": "metric.code_smells.name",
+ "translatedName": "Code Smells",
+ "type": "INT",
},
]
}
@@ -125,8 +134,8 @@ exports[`should render correctly the graph and legends 1`] = `
},
],
"name": "code_smells",
- "style": "1",
- "translatedName": "metric.code_smells.name",
+ "translatedName": "Code Smells",
+ "type": "INT",
},
]
}
@@ -140,6 +149,29 @@ exports[`should render correctly with filter history on dates 1`] = `
Object {
"graphEndDate": null,
"graphStartDate": "2016-10-27T12:21:15+0200",
+ "graphs": Array [
+ Array [
+ Object {
+ "data": Array [
+ Object {
+ "x": 2016-10-26T10:17:29.000Z,
+ "y": 2286,
+ },
+ Object {
+ "x": 2016-10-27T10:21:15.000Z,
+ "y": 1749,
+ },
+ Object {
+ "x": 2016-10-27T14:33:50.000Z,
+ "y": 500,
+ },
+ ],
+ "name": "code_smells",
+ "translatedName": "Code Smells",
+ "type": "INT",
+ },
+ ],
+ ],
"series": Array [
Object {
"data": Array [
@@ -157,9 +189,119 @@ Object {
},
],
"name": "code_smells",
- "style": "1",
- "translatedName": "metric.code_smells.name",
+ "translatedName": "Code Smells",
+ "type": "INT",
},
],
}
`;
+
+exports[`should show that there is no history data 1`] = `
+<div
+ className="project-activity-layout-page-main-inner boxed-group boxed-group-inner"
+>
+ <ProjectActivityGraphsHeader
+ addCustomMetric={[Function]}
+ graph="overview"
+ metrics={
+ Array [
+ Object {
+ "key": "code_smells",
+ "name": "Code Smells",
+ "type": "INT",
+ },
+ ]
+ }
+ metricsTypeFilter={null}
+ updateGraph={[Function]}
+ />
+ <div
+ className="project-activity-graph-container"
+ >
+ <div
+ className="note text-center"
+ >
+ component_measures.no_history
+ </div>
+ </div>
+ <GraphsZoom
+ graphEndDate={null}
+ graphStartDate={null}
+ leakPeriodDate="2017-05-16T13:50:02+0200"
+ loading={false}
+ metricsType="INT"
+ series={
+ Array [
+ Object {
+ "data": Array [],
+ "name": "code_smells",
+ "translatedName": "Code Smells",
+ "type": "INT",
+ },
+ ]
+ }
+ showAreas={false}
+ updateGraphZoom={[Function]}
+ />
+</div>
+`;
+
+exports[`should show that there is no history data 2`] = `
+<div
+ className="project-activity-layout-page-main-inner boxed-group boxed-group-inner"
+>
+ <ProjectActivityGraphsHeader
+ addCustomMetric={[Function]}
+ graph="custom"
+ metrics={
+ Array [
+ Object {
+ "key": "code_smells",
+ "name": "Code Smells",
+ "type": "INT",
+ },
+ ]
+ }
+ metricsTypeFilter={null}
+ selectedMetrics={
+ Array [
+ "code_smells",
+ ]
+ }
+ updateGraph={[Function]}
+ />
+ <div
+ className="project-activity-graph-container"
+ >
+ <div
+ className="note text-center"
+ >
+ project_activity.graphs.custom.no_history
+ </div>
+ </div>
+ <GraphsZoom
+ graphEndDate={null}
+ graphStartDate={null}
+ leakPeriodDate="2017-05-16T13:50:02+0200"
+ loading={false}
+ metricsType="INT"
+ series={
+ Array [
+ Object {
+ "data": Array [
+ Object {
+ "x": 2016-10-26T10:17:29.000Z,
+ "y": NaN,
+ },
+ ],
+ "name": "code_smells",
+ "translatedName": "Code Smells",
+ "type": "INT",
+ },
+ ]
+ }
+ showAreas={false}
+ updateGraphZoom={[Function]}
+ />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.js
index b36f79e9c44..3ab48063f12 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.js
@@ -35,6 +35,7 @@ type Props = {
addMetric: (metric: string) => void,
className?: string,
metrics: Array<Metric>,
+ metricsTypeFilter: ?Array<string>,
selectedMetrics: Array<string>
};
@@ -49,23 +50,18 @@ export default class AddGraphMetric extends React.PureComponent {
open: false
};
- getMetricsType = () => {
- if (this.props.selectedMetrics.length > 0) {
- const metric = this.props.metrics.find(
- metric => metric.key === this.props.selectedMetrics[0]
- );
- return metric && metric.type;
- }
- };
-
- getMetricsOptions = (selectedType: ?string) => {
+ getMetricsOptions = (metricsTypeFilter: ?Array<string>) => {
return this.props.metrics
.filter(metric => {
- if (metric.hidden || isDiffMetric(metric.key)) {
+ if (
+ metric.hidden ||
+ isDiffMetric(metric.key) ||
+ this.props.selectedMetrics.includes(metric.key)
+ ) {
return false;
}
- if (selectedType) {
- return selectedType === metric.type && !this.props.selectedMetrics.includes(metric.key);
+ if (metricsTypeFilter && metricsTypeFilter.length > 0) {
+ return metricsTypeFilter.includes(metric.type);
}
return true;
})
@@ -100,7 +96,7 @@ export default class AddGraphMetric extends React.PureComponent {
};
renderModal() {
- const metricType = this.getMetricsType();
+ const { metricsTypeFilter } = this.props;
return (
<Modal
isOpen={true}
@@ -125,16 +121,19 @@ export default class AddGraphMetric extends React.PureComponent {
clearable={false}
noResultsText={translate('no_results')}
onChange={this.handleChange}
- options={this.getMetricsOptions(metricType)}
+ options={this.getMetricsOptions(metricsTypeFilter)}
placeholder=""
searchable={true}
value={this.state.selectedMetric}
/>
<span className="alert alert-info">
- {metricType != null
+ {metricsTypeFilter != null && metricsTypeFilter.length > 0
? translateWithParameters(
'project_activity.graphs.custom.type_x_message',
- translate('metric.type', metricType)
+ metricsTypeFilter
+ .map(type => translate('metric.type', type))
+ .sort()
+ .join(', ')
)
: translate('project_activity.graphs.custom.add_metric_info')}
</span>
@@ -156,7 +155,7 @@ export default class AddGraphMetric extends React.PureComponent {
}
render() {
- if (this.props.selectedMetrics.length >= 3) {
+ if (this.props.selectedMetrics.length >= 6) {
// Use the class .disabled instead of the property to prevent a bug from
// rc-tooltip : https://github.com/react-component/tooltip/issues/18
return (
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css b/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
index 7e63c546db9..5ef705363f2 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
@@ -76,12 +76,18 @@
.project-activity-graph-tooltip {
padding: 8px;
- pointer-events: none;
}
.project-activity-graph-tooltip-line {
height: 20px;
- padding-bottom: 4px;
+}
+
+.project-activity-graph-tooltip-line + .project-activity-graph-tooltip-line {
+ padding-top: 4px;
+}
+
+.project-activity-graph-tooltip-line .project-activity-event-icon {
+ margin-top: 1px;
}
.project-activity-graph-tooltip-overview-line {
@@ -214,7 +220,7 @@
margin-left: 4px;
}
-.project-activity-event-icon.margin-align {
+.project-activity-event-inner-icon .project-activity-event-icon {
margin-top: 3px;
}
@@ -258,7 +264,7 @@
.project-activity-version-badge .badge {
vertical-align: middle;
padding: 4px 14px 4px 14px;
- border-radius: 2px;
+ border-radius: 0 2px 2px 0;
font-weight: bold;
font-size: 12px;
letter-spacing: 0;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/utils.js b/server/sonar-web/src/main/js/apps/projectActivity/utils.js
index cdf5fa75eb7..83d08e5a171 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/utils.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/utils.js
@@ -19,7 +19,7 @@
*/
// @flow
import moment from 'moment';
-import { isEqual } from 'lodash';
+import { isEqual, sortBy } from 'lodash';
import {
cleanQuery,
parseAsArray,
@@ -29,8 +29,8 @@ import {
serializeDate,
serializeString
} from '../../helpers/query';
-import { translate } from '../../helpers/l10n';
-import type { Analysis, MeasureHistory, Query } from './types';
+import { getLocalizedMetricName, translate } from '../../helpers/l10n';
+import type { Analysis, MeasureHistory, Metric, Query } from './types';
import type { RawQuery } from '../../helpers/query';
import type { Serie } from '../../components/charts/AdvancedTimeline';
@@ -57,13 +57,8 @@ export const activityQueryChanged = (prevQuery: Query, nextQuery: Query): boolea
export const customMetricsChanged = (prevQuery: Query, nextQuery: Query): boolean =>
!isEqual(prevQuery.customMetrics, nextQuery.customMetrics);
-export const datesQueryChanged = (prevQuery: Query, nextQuery: Query): boolean => {
- const nextFrom = nextQuery.from ? nextQuery.from.valueOf() : null;
- const previousFrom = prevQuery.from ? prevQuery.from.valueOf() : null;
- const nextTo = nextQuery.to ? nextQuery.to.valueOf() : null;
- const previousTo = prevQuery.to ? prevQuery.to.valueOf() : null;
- return previousFrom !== nextFrom || previousTo !== nextTo;
-};
+export const datesQueryChanged = (prevQuery: Query, nextQuery: Query): boolean =>
+ !isEqual(prevQuery.from, nextQuery.from) || !isEqual(prevQuery.to, nextQuery.to);
export const hasDataValues = (serie: Serie) => serie.data.some(point => point.y || point.y === 0);
@@ -75,16 +70,12 @@ export const historyQueryChanged = (prevQuery: Query, nextQuery: Query): boolean
export const isCustomGraph = (graph: string) => graph === 'custom';
-export const selectedDateQueryChanged = (prevQuery: Query, nextQuery: Query): boolean => {
- const nextSelectedDate = nextQuery.selectedDate ? nextQuery.selectedDate.valueOf() : null;
- const previousSelectedDate = prevQuery.selectedDate ? prevQuery.selectedDate.valueOf() : null;
- return nextSelectedDate !== previousSelectedDate;
-};
+export const selectedDateQueryChanged = (prevQuery: Query, nextQuery: Query): boolean =>
+ !isEqual(prevQuery.selectedDate, nextQuery.selectedDate);
export const generateCoveredLinesMetric = (
uncoveredLines: MeasureHistory,
- measuresHistory: Array<MeasureHistory>,
- style: string
+ measuresHistory: Array<MeasureHistory>
) => {
const linesToCover = measuresHistory.find(measure => measure.metric === 'lines_to_cover');
return {
@@ -95,42 +86,45 @@ export const generateCoveredLinesMetric = (
}))
: [],
name: 'covered_lines',
- style,
- translatedName: translate('project_activity.custom_metric.covered_lines')
+ translatedName: translate('project_activity.custom_metric.covered_lines'),
+ type: 'INT'
};
};
export const generateSeries = (
measuresHistory: Array<MeasureHistory>,
graph: string,
- dataType: string,
+ metrics: Array<Metric>,
displayedMetrics: Array<string>
): Array<Serie> => {
if (displayedMetrics.length <= 0) {
return [];
}
- return measuresHistory
- .filter(measure => displayedMetrics.indexOf(measure.metric) >= 0)
- .map(measure => {
- if (measure.metric === 'uncovered_lines' && !isCustomGraph(graph)) {
- return generateCoveredLinesMetric(
- measure,
- measuresHistory,
- displayedMetrics.indexOf(measure.metric).toString()
- );
- }
- return {
- name: measure.metric,
- translatedName: translate('metric', measure.metric, 'name'),
- style: displayedMetrics.indexOf(measure.metric).toString(),
- data: measure.history.map(analysis => ({
- x: analysis.date,
- y: dataType === 'LEVEL' ? analysis.value : Number(analysis.value)
- }))
- };
- });
+ return sortBy(
+ measuresHistory
+ .filter(measure => displayedMetrics.indexOf(measure.metric) >= 0)
+ .map(measure => {
+ if (measure.metric === 'uncovered_lines' && !isCustomGraph(graph)) {
+ return generateCoveredLinesMetric(measure, measuresHistory);
+ }
+ const metric = metrics.find(metric => metric.key === measure.metric);
+ return {
+ data: measure.history.map(analysis => ({
+ x: analysis.date,
+ y: metric && metric.type === 'LEVEL' ? analysis.value : Number(analysis.value)
+ })),
+ name: measure.metric,
+ translatedName: metric ? getLocalizedMetricName(metric) : measure.metric,
+ type: metric ? metric.type : 'INT'
+ };
+ }),
+ serie => displayedMetrics.indexOf(serie.name)
+ );
};
+export const getSeriesMetricType = (series: Array<Serie>): string =>
+ series.length > 0 ? series[0].type : 'INT';
+
export const getAnalysesByVersionByDay = (
analyses: Array<Analysis>,
query: Query
diff --git a/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js b/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js
index e5ddd480076..872a1a8bee6 100644
--- a/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js
+++ b/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js
@@ -20,14 +20,14 @@
// @flow
import React from 'react';
import classNames from 'classnames';
-import { throttle, flatten, sortBy } from 'lodash';
+import { flatten, isEqual, sortBy, throttle } from 'lodash';
import { bisector, extent, max } from 'd3-array';
import { scaleLinear, scalePoint, scaleTime } from 'd3-scale';
import { line as d3Line, area, curveBasis } from 'd3-shape';
type Event = { className?: string, name: string, date: Date };
export type Point = { x: Date, y: number | string };
-export type Serie = { name: string, data: Array<Point>, style: string };
+export type Serie = { name: string, data: Array<Point>, type: string };
type Scale = Function;
type Props = {
@@ -82,10 +82,12 @@ export default class AdvancedTimeline extends React.PureComponent {
const selectedDatePos = this.getSelectedDatePos(scales.xScale, props.selectedDate);
this.state = { ...scales, ...selectedDatePos };
this.updateTooltipPos = throttle(this.updateTooltipPos, 40);
+ this.handleZoomUpdate = throttle(this.handleZoomUpdate, 40);
}
componentWillReceiveProps(nextProps: Props) {
let scales;
+ let selectedDatePos;
if (
nextProps.metricType !== this.props.metricType ||
nextProps.startDate !== this.props.startDate ||
@@ -96,13 +98,20 @@ export default class AdvancedTimeline extends React.PureComponent {
nextProps.series !== this.props.series
) {
scales = this.getScales(nextProps);
+ if (this.state.selectedDate != null) {
+ selectedDatePos = this.getSelectedDatePos(scales.xScale, this.state.selectedDate);
+ }
}
- if (scales || nextProps.selectedDate !== this.props.selectedDate) {
+ if (!isEqual(nextProps.selectedDate, this.props.selectedDate)) {
const xScale = scales ? scales.xScale : this.state.xScale;
- const selectedDatePos = this.getSelectedDatePos(xScale, nextProps.selectedDate);
- this.setState({ ...scales, ...selectedDatePos });
- if (nextProps.updateTooltip) {
+ selectedDatePos = this.getSelectedDatePos(xScale, nextProps.selectedDate);
+ }
+
+ if (scales || selectedDatePos) {
+ this.setState({ ...(scales || {}), ...(selectedDatePos || {}) });
+
+ if (selectedDatePos && nextProps.updateTooltip) {
nextProps.updateTooltip(
selectedDatePos.selectedDate,
selectedDatePos.selectedDateXPos,
@@ -159,7 +168,9 @@ export default class AdvancedTimeline extends React.PureComponent {
// $FlowFixMe selectedDate can't be null there
p => p.x.valueOf() === selectedDate.valueOf()
);
- if (idx >= 0) {
+ const xRange = xScale.range();
+ const xPos = xScale(selectedDate);
+ if (idx >= 0 && xPos >= xRange[0] && xPos <= xRange[1]) {
return {
selectedDate,
selectedDateXPos: xScale(selectedDate),
@@ -195,8 +206,13 @@ export default class AdvancedTimeline extends React.PureComponent {
const rightPos = xRange[1] + Math.round(speed * evt.deltaY * (1 - mouseXPos));
const startDate = leftPos > maxXRange[0] ? xScale.invert(leftPos) : null;
const endDate = rightPos < maxXRange[1] ? xScale.invert(rightPos) : null;
- // $FlowFixMe updateZoom can't be undefined at this point
- this.props.updateZoom(startDate, endDate);
+ this.handleZoomUpdate(startDate, endDate);
+ };
+
+ handleZoomUpdate = (startDate: ?Date, endDate: ?Date) => {
+ if (this.props.updateZoom) {
+ this.props.updateZoom(startDate, endDate);
+ }
};
handleMouseMove = (evt: MouseEvent & { target: HTMLElement }) => {
@@ -343,10 +359,10 @@ export default class AdvancedTimeline extends React.PureComponent {
}
return (
<g>
- {this.props.series.map(serie =>
+ {this.props.series.map((serie, idx) =>
<path
key={serie.name}
- className={classNames('line-chart-path', 'line-chart-path-' + serie.style)}
+ className={classNames('line-chart-path', 'line-chart-path-' + idx)}
d={lineGenerator(serie.data)}
/>
)}
@@ -365,10 +381,10 @@ export default class AdvancedTimeline extends React.PureComponent {
}
return (
<g>
- {this.props.series.map(serie =>
+ {this.props.series.map((serie, idx) =>
<path
key={serie.name}
- className={classNames('line-chart-area', 'line-chart-area-' + serie.style)}
+ className={classNames('line-chart-area', 'line-chart-area-' + idx)}
d={areaGenerator(serie.data)}
/>
)}
@@ -416,7 +432,7 @@ export default class AdvancedTimeline extends React.PureComponent {
y1={yScale.range()[0]}
y2={yScale.range()[1]}
/>
- {this.props.series.map(serie => {
+ {this.props.series.map((serie, idx) => {
const point = serie.data[selectedDateIdx];
if (!point || (!point.y && point.y !== 0)) {
return null;
@@ -427,7 +443,7 @@ export default class AdvancedTimeline extends React.PureComponent {
cx={selectedDateXPos}
cy={yScale(point.y)}
r="4"
- className={classNames('line-chart-dot', 'line-chart-dot-' + serie.style)}
+ className={classNames('line-chart-dot', 'line-chart-dot-' + idx)}
/>
);
})}
@@ -439,7 +455,11 @@ export default class AdvancedTimeline extends React.PureComponent {
return (
<defs>
<clipPath id="chart-clip">
- <rect width={this.state.xScale.range()[1]} height={this.state.yScale.range()[0] + 10} />
+ <rect
+ width={this.state.xScale.range()[1]}
+ height={this.state.yScale.range()[0] + 10}
+ transform="translate(0,-5)"
+ />
</clipPath>
</defs>
);
diff --git a/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js b/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js
index f7693b06485..026dc15ac89 100644
--- a/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js
+++ b/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js
@@ -231,8 +231,8 @@ export default class ZoomTimeLine extends React.PureComponent {
<g>
{this.props.series.map((serie, idx) =>
<path
- key={`${idx}-${serie.name}`}
- className={classNames('line-chart-path', 'line-chart-path-' + serie.style)}
+ key={serie.name}
+ className={classNames('line-chart-path', 'line-chart-path-' + idx)}
d={lineGenerator(serie.data)}
/>
)}
@@ -253,8 +253,8 @@ export default class ZoomTimeLine extends React.PureComponent {
<g>
{this.props.series.map((serie, idx) =>
<path
- key={`${idx}-${serie.name}`}
- className={classNames('line-chart-area', 'line-chart-area-' + serie.style)}
+ key={serie.name}
+ className={classNames('line-chart-area', 'line-chart-area-' + idx)}
d={areaGenerator(serie.data)}
/>
)}