aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-06-21 09:18:46 +0200
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-07-04 14:15:34 +0200
commitcc5e586bcc63ddcb678659d44196cbda482141ed (patch)
treee20c81362d2c0d22be8b131e918d0993dc2a63b8 /server
parentad31ff822027745cf785f857613b24b775d0ba99 (diff)
downloadsonarqube-cc5e586bcc63ddcb678659d44196cbda482141ed.tar.gz
sonarqube-cc5e586bcc63ddcb678659d44196cbda482141ed.zip
SONAR-9402 Filter project activity graphs based on date range
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js106
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphs.js35
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.js10
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/StaticGraphs-test.js39
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap82
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/utils.js10
-rw-r--r--server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js7
7 files changed, 205 insertions, 84 deletions
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 4e79f6b962c..5680eca4306 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
@@ -21,9 +21,11 @@
import React from 'react';
import ProjectActivityGraphsHeader from './ProjectActivityGraphsHeader';
import StaticGraphs from './StaticGraphs';
-import { GRAPHS_METRICS } from '../utils';
+import { GRAPHS_METRICS, generateCoveredLinesMetric, historyQueryChanged } from '../utils';
+import { translate } from '../../../helpers/l10n';
import type { RawQuery } from '../../../helpers/query';
import type { Analysis, MeasureHistory, Query } from '../types';
+import type { Serie } from '../../../components/charts/AdvancedTimeline';
type Props = {
analyses: Array<Analysis>,
@@ -36,22 +38,88 @@ type Props = {
updateQuery: RawQuery => void
};
-export default function ProjectActivityGraphs(props: Props) {
- const { graph, category } = props.query;
- return (
- <div className="project-activity-layout-page-main-inner boxed-group boxed-group-inner">
- <ProjectActivityGraphsHeader graph={graph} updateQuery={props.updateQuery} />
- <StaticGraphs
- analyses={props.analyses}
- eventFilter={category}
- leakPeriodDate={props.leakPeriodDate}
- loading={props.loading}
- measuresHistory={props.measuresHistory}
- metricsType={props.metricsType}
- project={props.project}
- seriesOrder={GRAPHS_METRICS[graph]}
- showAreas={['coverage', 'duplications'].includes(graph)}
- />
- </div>
- );
+type State = {
+ filteredSeries: Array<Serie>,
+ series: Array<Serie>
+};
+
+export default class ProjectActivityGraphs extends React.PureComponent {
+ props: Props;
+ state: State;
+
+ constructor(props: Props) {
+ super(props);
+ const series = this.getSeries(props.measuresHistory);
+ this.state = {
+ filteredSeries: this.filterSeries(series, props.query),
+ series
+ };
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ if (
+ nextProps.measuresHistory !== this.props.measuresHistory ||
+ historyQueryChanged(this.props.query, nextProps.query)
+ ) {
+ const series = this.getSeries(nextProps.measuresHistory);
+ this.setState({
+ filteredSeries: this.filterSeries(series, nextProps.query),
+ series
+ });
+ }
+ }
+
+ getSeries = (measuresHistory: Array<MeasureHistory>): Array<Serie> =>
+ measuresHistory.map(measure => {
+ if (measure.metric === 'uncovered_lines') {
+ return generateCoveredLinesMetric(
+ measure,
+ measuresHistory,
+ GRAPHS_METRICS[this.props.query.graph].indexOf(measure.metric)
+ );
+ }
+ return {
+ name: measure.metric,
+ translatedName: translate('metric', measure.metric, 'name'),
+ style: GRAPHS_METRICS[this.props.query.graph].indexOf(measure.metric),
+ data: measure.history.map(analysis => ({
+ x: analysis.date,
+ y: this.props.metricsType === 'LEVEL' ? analysis.value : Number(analysis.value)
+ }))
+ };
+ });
+
+ filterSeries = (series: Array<Serie>, query: Query): Array<Serie> => {
+ if (!query.from && !query.to) {
+ return series;
+ }
+ return series.map(serie => ({
+ ...serie,
+ data: serie.data.filter(p => {
+ const isAfterFrom = !query.from || p.x >= query.from;
+ const isBeforeTo = !query.to || p.x <= query.to;
+ return isAfterFrom && isBeforeTo;
+ })
+ }));
+ };
+
+ render() {
+ const { graph, category } = this.props.query;
+ return (
+ <div className="project-activity-layout-page-main-inner boxed-group boxed-group-inner">
+ <ProjectActivityGraphsHeader graph={graph} updateQuery={this.props.updateQuery} />
+ <StaticGraphs
+ analyses={this.props.analyses}
+ eventFilter={category}
+ filteredSeries={this.state.filteredSeries}
+ leakPeriodDate={this.props.leakPeriodDate}
+ loading={this.props.loading}
+ metricsType={this.props.metricsType}
+ project={this.props.project}
+ series={this.state.series}
+ showAreas={['coverage', 'duplications'].includes(graph)}
+ />
+ </div>
+ );
+ }
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphs.js b/server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphs.js
index 4d93e77504e..086c9a6e1d1 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphs.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphs.js
@@ -24,18 +24,19 @@ import { AutoSizer } from 'react-virtualized';
import AdvancedTimeline from '../../../components/charts/AdvancedTimeline';
import StaticGraphsLegend from './StaticGraphsLegend';
import { formatMeasure, getShortType } from '../../../helpers/measures';
-import { EVENT_TYPES, generateCoveredLinesMetric } from '../utils';
+import { EVENT_TYPES } from '../utils';
import { translate } from '../../../helpers/l10n';
-import type { Analysis, MeasureHistory } from '../types';
+import type { Analysis } from '../types';
+import type { Serie } from '../../../components/charts/AdvancedTimeline';
type Props = {
analyses: Array<Analysis>,
eventFilter: string,
+ filteredSeries: Array<Serie>,
leakPeriodDate: Date,
loading: boolean,
- measuresHistory: Array<MeasureHistory>,
metricsType: string,
- seriesOrder: Array<string>
+ series: Array<Serie>
};
export default class StaticGraphs extends React.PureComponent {
@@ -69,27 +70,7 @@ export default class StaticGraphs extends React.PureComponent {
return sortBy(filteredEvents, 'date');
};
- getSeries = () =>
- sortBy(
- this.props.measuresHistory.map(measure => {
- if (measure.metric === 'uncovered_lines') {
- return generateCoveredLinesMetric(measure, this.props.measuresHistory);
- }
- return {
- name: measure.metric,
- translatedName: translate('metric', measure.metric, 'name'),
- style: this.props.seriesOrder.indexOf(measure.metric),
- data: measure.history.map(analysis => ({
- x: analysis.date,
- y: this.props.metricsType === 'LEVEL' ? analysis.value : Number(analysis.value)
- }))
- };
- }),
- 'name'
- );
-
- hasHistoryData = () =>
- some(this.props.measuresHistory, measure => measure.history && measure.history.length > 2);
+ hasHistoryData = () => some(this.props.series, serie => serie.data && serie.data.length > 2);
render() {
const { loading } = this.props;
@@ -114,7 +95,7 @@ export default class StaticGraphs extends React.PureComponent {
);
}
- const series = this.getSeries();
+ const { filteredSeries, series } = this.props;
return (
<div className="project-activity-graph-container">
<StaticGraphsLegend series={series} />
@@ -129,7 +110,7 @@ export default class StaticGraphs extends React.PureComponent {
formatYTick={this.formatYTick}
leakPeriodDate={this.props.leakPeriodDate}
metricType={this.props.metricsType}
- series={series}
+ series={filteredSeries}
showAreas={this.props.showAreas}
width={width}
/>
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 57f093bd56d..3434f552fae 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
@@ -79,3 +79,13 @@ const DEFAULT_PROPS = {
it('should render correctly the graph and legends', () => {
expect(shallow(<ProjectActivityGraphs {...DEFAULT_PROPS} />)).toMatchSnapshot();
});
+
+it('should render correctly filter history on dates', () => {
+ const wrapper = shallow(
+ <ProjectActivityGraphs
+ {...DEFAULT_PROPS}
+ query={{ ...DEFAULT_PROPS.query, from: '2016-10-27T12:21:15+0200' }}
+ />
+ );
+ expect(wrapper.state()).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/StaticGraphs-test.js b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/StaticGraphs-test.js
index 47984030882..3b8a4526cf4 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/StaticGraphs-test.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/StaticGraphs-test.js
@@ -56,22 +56,35 @@ const ANALYSES = [
}
];
+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 },
+ { x: new Date('2016-10-26T12:17:29+0200'), y: 12 }
+ ]
+ }
+];
+
+const EMPTY_SERIES = [
+ {
+ name: 'bugs',
+ translatedName: 'metric.bugs.name',
+ style: 0,
+ data: []
+ }
+];
+
const DEFAULT_PROPS = {
analyses: ANALYSES,
eventFilter: '',
+ filteredSeries: SERIES,
leakPeriodDate: '2017-05-16T13:50:02+0200',
loading: false,
- measuresHistory: [
- {
- metric: 'bugs',
- history: [
- { date: new Date('2016-10-27T16:33:50+0200'), value: '5' },
- { date: new Date('2016-10-27T12:21:15+0200'), value: '16' },
- { date: new Date('2016-10-26T12:17:29+0200'), value: '12' }
- ]
- }
- ],
- seriesOrder: ['bugs'],
+ series: SERIES,
metricsType: 'INT'
};
@@ -80,9 +93,7 @@ it('should show a loading view', () => {
});
it('should show that there is no data', () => {
- expect(
- shallow(<StaticGraphs {...DEFAULT_PROPS} measuresHistory={[{ metric: 'bugs', history: [] }]} />)
- ).toMatchSnapshot();
+ expect(shallow(<StaticGraphs {...DEFAULT_PROPS} series={EMPTY_SERIES} />)).toMatchSnapshot();
});
it('should correctly render a graph', () => {
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 27471bc95a8..0fa696ab435 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
@@ -1,5 +1,39 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`should render correctly filter history on dates 1`] = `
+Object {
+ "filteredSeries": Array [
+ Object {
+ "data": Array [],
+ "name": "code_smells",
+ "style": 1,
+ "translatedName": "metric.code_smells.name",
+ },
+ ],
+ "series": 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",
+ "style": 1,
+ "translatedName": "metric.code_smells.name",
+ },
+ ],
+}
+`;
+
exports[`should render correctly the graph and legends 1`] = `
<div
className="project-activity-layout-page-main-inner boxed-group boxed-group-inner"
@@ -46,36 +80,54 @@ exports[`should render correctly the graph and legends 1`] = `
]
}
eventFilter=""
- leakPeriodDate="2017-05-16T13:50:02+0200"
- loading={false}
- measuresHistory={
+ filteredSeries={
Array [
Object {
- "history": Array [
+ "data": Array [
Object {
- "date": 2016-10-26T10:17:29.000Z,
- "value": "2286",
+ "x": 2016-10-26T10:17:29.000Z,
+ "y": 2286,
},
Object {
- "date": 2016-10-27T10:21:15.000Z,
- "value": "1749",
+ "x": 2016-10-27T10:21:15.000Z,
+ "y": 1749,
},
Object {
- "date": 2016-10-27T14:33:50.000Z,
- "value": "500",
+ "x": 2016-10-27T14:33:50.000Z,
+ "y": 500,
},
],
- "metric": "code_smells",
+ "name": "code_smells",
+ "style": 1,
+ "translatedName": "metric.code_smells.name",
},
]
}
+ leakPeriodDate="2017-05-16T13:50:02+0200"
+ loading={false}
metricsType="INT"
project="org.sonarsource.sonarqube:sonarqube"
- seriesOrder={
+ series={
Array [
- "bugs",
- "code_smells",
- "vulnerabilities",
+ 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",
+ "style": 1,
+ "translatedName": "metric.code_smells.name",
+ },
]
}
showAreas={false}
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 c4d1b9239fa..257300e503d 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/utils.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/utils.js
@@ -79,17 +79,19 @@ export const historyQueryChanged = (prevQuery: Query, nextQuery: Query): boolean
export const generateCoveredLinesMetric = (
uncoveredLines: MeasureHistory,
- measuresHistory: Array<MeasureHistory>
+ measuresHistory: Array<MeasureHistory>,
+ style: string
) => {
const linesToCover = measuresHistory.find(measure => measure.metric === 'lines_to_cover');
return {
- name: 'covered_lines',
- translatedName: translate('project_activity.custom_metric.covered_lines'),
data: linesToCover
? uncoveredLines.history.map((analysis, idx) => ({
x: analysis.date,
y: Number(linesToCover.history[idx].value) - Number(analysis.value)
}))
- : []
+ : [],
+ name: 'covered_lines',
+ style,
+ translatedName: translate('project_activity.custom_metric.covered_lines')
};
};
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 bae8d1ec00d..bc71efecf69 100644
--- a/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js
+++ b/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js
@@ -25,12 +25,9 @@ import { extent, max } from 'd3-array';
import { scaleLinear, scalePoint, scaleTime } from 'd3-scale';
import { line as d3Line, area, curveBasis } from 'd3-shape';
-type Point = { x: Date, y: number | string };
-
-type Serie = { name: string, data: Array<Point>, style: string };
-
type Event = { className?: string, name: string, date: Date };
-
+type Point = { x: Date, y: number | string };
+export type Serie = { name: string, data: Array<Point>, style: string };
type Scale = Function;
type Props = {