diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-06-21 15:53:31 +0200 |
---|---|---|
committer | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2017-07-04 14:15:34 +0200 |
commit | 7feb62d1317819a82df8dcbc71969d9c1d51bdc7 (patch) | |
tree | 2a0937f508e28fc242958661a67ef89c03e2b75d /server/sonar-web/src/main/js/apps | |
parent | cc5e586bcc63ddcb678659d44196cbda482141ed (diff) | |
download | sonarqube-7feb62d1317819a82df8dcbc71969d9c1d51bdc7.tar.gz sonarqube-7feb62d1317819a82df8dcbc71969d9c1d51bdc7.zip |
SONAR-9402 Add basic zooming capabilities to the project history graphs
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
4 files changed, 134 insertions, 33 deletions
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js new file mode 100644 index 00000000000..3dea9f1a188 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +// @flow +import React from 'react'; +import { some, throttle } from 'lodash'; +import { AutoSizer } from 'react-virtualized'; +import ZoomTimeLine from '../../../components/charts/ZoomTimeLine'; +import type { RawQuery } from '../../../helpers/query'; +import type { Serie } from '../../../components/charts/AdvancedTimeline'; + +type Props = { + graphEndDate: ?Date, + graphStartDate: ?Date, + leakPeriodDate: Date, + loading: boolean, + metricsType: string, + series: Array<Serie>, + showAreas?: boolean, + updateGraphZoom: (from: ?Date, to: ?Date) => void, + updateQuery: RawQuery => void +}; + +export default class GraphsZoom extends React.PureComponent { + props: Props; + + constructor(props: Props) { + super(props); + this.updateDateRange = throttle(this.updateDateRange, 100); + } + + hasHistoryData = () => some(this.props.series, serie => serie.data && serie.data.length > 2); + + updateDateRange = (from: ?Date, to: ?Date) => this.props.updateQuery({ from, to }); + + render() { + const { loading } = this.props; + if (loading || !this.hasHistoryData()) { + return null; + } + + return ( + <div className="project-activity-graph-zoom"> + <AutoSizer disableHeight={true}> + {({ width }) => ( + <ZoomTimeLine + endDate={this.props.graphEndDate} + height={64} + width={width} + interpolate="linear" + leakPeriodDate={this.props.leakPeriodDate} + metricType={this.props.metricsType} + padding={[0, 10, 18, 60]} + series={this.props.series} + showAreas={this.props.showAreas} + startDate={this.props.graphStartDate} + updateZoom={this.updateDateRange} + updateZoomFast={this.props.updateGraphZoom} + /> + )} + </AutoSizer> + </div> + ); + } +} 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 5680eca4306..db1eb841dac 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 @@ -20,8 +20,14 @@ // @flow import React from 'react'; import ProjectActivityGraphsHeader from './ProjectActivityGraphsHeader'; +import GraphsZoom from './GraphsZoom'; import StaticGraphs from './StaticGraphs'; -import { GRAPHS_METRICS, generateCoveredLinesMetric, historyQueryChanged } from '../utils'; +import { + GRAPHS_METRICS, + datesQueryChanged, + generateCoveredLinesMetric, + historyQueryChanged +} from '../utils'; import { translate } from '../../../helpers/l10n'; import type { RawQuery } from '../../../helpers/query'; import type { Analysis, MeasureHistory, Query } from '../types'; @@ -39,7 +45,8 @@ type Props = { }; type State = { - filteredSeries: Array<Serie>, + graphStartDate: ?Date, + graphEndDate: ?Date, series: Array<Serie> }; @@ -51,7 +58,8 @@ export default class ProjectActivityGraphs extends React.PureComponent { super(props); const series = this.getSeries(props.measuresHistory); this.state = { - filteredSeries: this.filterSeries(series, props.query), + graphStartDate: props.query.from || null, + graphEndDate: props.query.to || null, series }; } @@ -62,10 +70,13 @@ export default class ProjectActivityGraphs extends React.PureComponent { historyQueryChanged(this.props.query, nextProps.query) ) { const series = this.getSeries(nextProps.measuresHistory); - this.setState({ - filteredSeries: this.filterSeries(series, nextProps.query), - series - }); + this.setState({ series }); + } + if ( + nextProps.query !== this.props.query && + datesQueryChanged(this.props.query, nextProps.query) + ) { + this.setState({ graphStartDate: nextProps.query.from, graphEndDate: nextProps.query.to }); } } @@ -89,35 +100,37 @@ export default class ProjectActivityGraphs extends React.PureComponent { }; }); - 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; - }) - })); - }; + updateGraphZoom = (graphStartDate: ?Date, graphEndDate: ?Date) => + this.setState({ graphStartDate, graphEndDate }); render() { - const { graph, category } = this.props.query; + const { leakPeriodDate, loading, metricsType, query } = this.props; + const { series } = this.state; return ( <div className="project-activity-layout-page-main-inner boxed-group boxed-group-inner"> - <ProjectActivityGraphsHeader graph={graph} updateQuery={this.props.updateQuery} /> + <ProjectActivityGraphsHeader graph={query.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} + eventFilter={query.category} + graphEndDate={this.state.graphEndDate} + graphStartDate={this.state.graphStartDate} + leakPeriodDate={leakPeriodDate} + loading={loading} + metricsType={metricsType} project={this.props.project} - series={this.state.series} - showAreas={['coverage', 'duplications'].includes(graph)} + series={series} + showAreas={['coverage', 'duplications'].includes(query.graph)} + /> + <GraphsZoom + graphEndDate={this.state.graphEndDate} + graphStartDate={this.state.graphStartDate} + leakPeriodDate={leakPeriodDate} + loading={loading} + metricsType={metricsType} + series={series} + showAreas={['coverage', 'duplications'].includes(query.graph)} + updateGraphZoom={this.updateGraphZoom} + updateQuery={this.props.updateQuery} /> </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 086c9a6e1d1..e14f5f097fb 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 @@ -32,11 +32,13 @@ import type { Serie } from '../../../components/charts/AdvancedTimeline'; type Props = { analyses: Array<Analysis>, eventFilter: string, - filteredSeries: Array<Serie>, + graphStartDate: ?Date, leakPeriodDate: Date, loading: boolean, metricsType: string, - series: Array<Serie> + series: Array<Serie>, + showAreas?: boolean, + graphEndDate: ?Date }; export default class StaticGraphs extends React.PureComponent { @@ -95,7 +97,7 @@ export default class StaticGraphs extends React.PureComponent { ); } - const { filteredSeries, series } = this.props; + const { series } = this.props; return ( <div className="project-activity-graph-container"> <StaticGraphsLegend series={series} /> @@ -103,6 +105,7 @@ export default class StaticGraphs extends React.PureComponent { <AutoSizer> {({ height, width }) => ( <AdvancedTimeline + endDate={this.props.graphEndDate} events={this.getEvents()} height={height} interpolate="linear" @@ -110,8 +113,9 @@ export default class StaticGraphs extends React.PureComponent { formatYTick={this.formatYTick} leakPeriodDate={this.props.leakPeriodDate} metricType={this.props.metricsType} - series={filteredSeries} + series={series} showAreas={this.props.showAreas} + startDate={this.props.graphStartDate} width={width} /> )} 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 257300e503d..ae651fd891f 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/utils.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/utils.js @@ -77,6 +77,9 @@ export const activityQueryChanged = (prevQuery: Query, nextQuery: Query): boolea export const historyQueryChanged = (prevQuery: Query, nextQuery: Query): boolean => prevQuery.graph !== nextQuery.graph; +export const datesQueryChanged = (prevQuery: Query, nextQuery: Query): boolean => + prevQuery.from !== nextQuery.from || prevQuery.to !== nextQuery.to; + export const generateCoveredLinesMetric = ( uncoveredLines: MeasureHistory, measuresHistory: Array<MeasureHistory>, |