From: Grégoire Aubert Date: Wed, 21 Jun 2017 07:18:46 +0000 (+0200) Subject: SONAR-9402 Filter project activity graphs based on date range X-Git-Tag: 6.5-M2~45 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=cc5e586bcc63ddcb678659d44196cbda482141ed;p=sonarqube.git SONAR-9402 Filter project activity graphs based on date range --- 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, @@ -36,22 +38,88 @@ type Props = { updateQuery: RawQuery => void }; -export default function ProjectActivityGraphs(props: Props) { - const { graph, category } = props.query; - return ( -
- - -
- ); +type State = { + filteredSeries: Array, + series: Array +}; + +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): Array => + 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, query: Query): Array => { + 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 ( +
+ + +
+ ); + } } 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, eventFilter: string, + filteredSeries: Array, leakPeriodDate: Date, loading: boolean, - measuresHistory: Array, metricsType: string, - seriesOrder: Array + series: Array }; 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 (
@@ -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()).toMatchSnapshot(); }); + +it('should render correctly filter history on dates', () => { + const wrapper = shallow( + + ); + 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() - ).toMatchSnapshot(); + expect(shallow()).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`] = `
+ measuresHistory: Array, + 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, style: string }; - type Event = { className?: string, name: string, date: Date }; - +type Point = { x: Date, y: number | string }; +export type Serie = { name: string, data: Array, style: string }; type Scale = Function; type Props = {