From 097f6826b2c684e22f4d57bd1d09c3a7029e5b55 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Mon, 17 Jul 2017 17:26:44 +0200 Subject: [PATCH] SONAR-9546 Sync tooltips from the two graphs --- .../components/GraphHistory.js | 116 +++++++++++++++ .../components/GraphsHistory.js | 129 +++++++++-------- .../components/ProjectActivityGraphs.js | 68 ++------- .../components/__tests__/GraphHistory-test.js | 57 ++++++++ .../__tests__/GraphsHistory-test.js | 31 +++- .../__tests__/ProjectActivityGraphs-test.js | 36 ----- .../__snapshots__/GraphHistory-test.js.snap | 39 +++++ .../__snapshots__/GraphsHistory-test.js.snap | 123 +++++++++++++++- .../ProjectActivityGraphs-test.js.snap | 137 ++++-------------- .../components/projectActivity.css | 8 + .../js/components/charts/AdvancedTimeline.js | 2 +- 11 files changed, 478 insertions(+), 268 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/GraphHistory.js create mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphHistory-test.js create mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphHistory-test.js.snap diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphHistory.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphHistory.js new file mode 100644 index 00000000000..64cbfe5d0ab --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphHistory.js @@ -0,0 +1,116 @@ +/* + * 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. + */ +import React from 'react'; +import { AutoSizer } from 'react-virtualized'; +import AdvancedTimeline from '../../../components/charts/AdvancedTimeline'; +import GraphsTooltips from './GraphsTooltips'; +import GraphsLegendCustom from './GraphsLegendCustom'; +import GraphsLegendStatic from './GraphsLegendStatic'; +import { formatMeasure, getShortType } from '../../../helpers/measures'; +import type { Event, MeasureHistory } from '../types'; +import type { Serie } from '../../../components/charts/AdvancedTimeline'; + +type Props = { + events: Array, + graph: string, + graphEndDate: ?Date, + graphStartDate: ?Date, + leakPeriodDate: Date, + isCustom: boolean, + measuresHistory: Array, + metricsType: string, + removeCustomMetric: (metric: string) => void, + showAreas: boolean, + series: Array, + selectedDate?: ?Date, + updateGraphZoom: (from: ?Date, to: ?Date) => void, + updateSelectedDate: (selectedDate: ?Date) => void, + updateTooltip: (selectedDate: ?Date) => void +}; + +type State = { + tooltipIdx: ?number, + tooltipXPos: ?number +}; + +export default class GraphHistory extends React.PureComponent { + props: Props; + state: State = { + tooltipIdx: null, + tooltipXPos: null + }; + + formatValue = (tick: string | number) => + formatMeasure(tick, getShortType(this.props.metricsType)); + + updateTooltip = (selectedDate: ?Date, tooltipXPos: ?number, tooltipIdx: ?number) => { + this.props.updateTooltip(selectedDate); + this.setState({ tooltipXPos, tooltipIdx }); + }; + + render() { + const { graph, selectedDate, series } = this.props; + const { tooltipIdx, tooltipXPos } = this.state; + + return ( +
+ {this.props.isCustom + ? + : } +
+ + {({ height, width }) => +
+ + {selectedDate != null && + tooltipXPos != null && + } +
} +
+
+
+ ); + } +} 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 bb72897ea26..3fae4284ddd 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 @@ -19,14 +19,10 @@ */ import React from 'react'; import moment from 'moment'; -import { sortBy } from 'lodash'; -import { AutoSizer } from 'react-virtualized'; -import AdvancedTimeline from '../../../components/charts/AdvancedTimeline'; -import GraphsTooltips from './GraphsTooltips'; -import GraphsLegendCustom from './GraphsLegendCustom'; -import GraphsLegendStatic from './GraphsLegendStatic'; -import { formatMeasure, getShortType } from '../../../helpers/measures'; -import { EVENT_TYPES, isCustomGraph } from '../utils'; +import { isEqual, sortBy } from 'lodash'; +import GraphHistory from './GraphHistory'; +import { EVENT_TYPES, getSeriesMetricType, hasHistoryData, isCustomGraph } from '../utils'; +import { translate } from '../../../helpers/l10n'; import type { Analysis, MeasureHistory } from '../types'; import type { Serie } from '../../../components/charts/AdvancedTimeline'; @@ -34,11 +30,12 @@ type Props = { analyses: Array, eventFilter: string, graph: string, + graphs: Array>, graphEndDate: ?Date, graphStartDate: ?Date, leakPeriodDate: Date, + loading: boolean, measuresHistory: Array, - metricsType: string, removeCustomMetric: (metric: string) => void, selectedDate: ?Date, series: Array, @@ -47,20 +44,25 @@ type Props = { }; type State = { - selectedDate?: ?Date, - tooltipIdx: ?number, - tooltipXPos: ?number + selectedDate?: ?Date }; export default class GraphsHistory extends React.PureComponent { props: Props; - state: State = { - tooltipIdx: null, - tooltipXPos: null - }; + state: State; - formatValue = (tick: string | number) => - formatMeasure(tick, getShortType(this.props.metricsType)); + constructor(props: Props) { + super(props); + this.state = { + selectedDate: props.selectedDate + }; + } + + componentWillReceiveProps(nextProps: Props) { + if (!isEqual(nextProps.selectedDate, this.props.selectedDate)) { + this.setState({ selectedDate: nextProps.selectedDate }); + } + } getEvents = () => { const { analyses, eventFilter } = this.props; @@ -100,54 +102,59 @@ export default class GraphsHistory extends React.PureComponent { return []; }; - updateTooltip = (selectedDate: ?Date, tooltipXPos: ?number, tooltipIdx: ?number) => - this.setState({ selectedDate, tooltipXPos, tooltipIdx }); + updateTooltip = (selectedDate: ?Date) => this.setState({ selectedDate }); render() { const { graph, series } = this.props; const isCustom = isCustomGraph(graph); - const { selectedDate, tooltipIdx, tooltipXPos } = this.state; - return ( -
- {isCustom - ? - : } -
- - {({ height, width }) => -
- - {selectedDate != null && - tooltipXPos != null && - } -
} -
+ + if (this.props.loading) { + return ( +
+
+ +
+ ); + } + + if (!hasHistoryData(series)) { + return ( +
+
+ {translate( + isCustom + ? 'project_activity.graphs.custom.no_history' + : 'component_measures.no_history' + )} +
+
+ ); + } + const events = this.getSelectedDateEvents(); + const showAreas = ['coverage', 'duplications'].includes(graph); + return ( +
+ {this.props.graphs.map((series, idx) => + + )}
); } 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 80e881c755a..a743645085c 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 @@ -30,10 +30,8 @@ import { 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'; @@ -192,55 +190,6 @@ 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 ( -
-
- -
-
- ); - } - - if (!hasHistoryData(series)) { - return ( -
-
- {translate( - isCustom - ? 'project_activity.graphs.custom.no_history' - : 'component_measures.no_history' - )} -
-
- ); - } - - return graphs.map((series, idx) => - - ); - } - render() { const { leakPeriodDate, loading, metrics, query } = this.props; const { graphEndDate, graphStartDate, series } = this.state; @@ -255,7 +204,22 @@ export default class ProjectActivityGraphs extends React.PureComponent { selectedMetrics={this.props.query.customMetrics} updateGraph={this.updateGraph} /> - {this.renderGraphs()} + {}, + showAreas: true, + selectedDate: null, + series: SERIES, + updateGraphZoom: () => {}, + updateSelectedDate: () => {}, + updateTooltip: () => {} +}; + +it('should correctly render a graph', () => { + expect(shallow()).toMatchSnapshot(); +}); 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 ab4fec25fb0..ea9e7c419b0 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 @@ -72,11 +72,12 @@ const DEFAULT_PROPS = { analyses: ANALYSES, eventFilter: '', graph: 'overview', + graphs: [SERIES], graphEndDate: null, graphStartDate: null, leakPeriodDate: '2017-05-16T13:50:02+0200', + loading: false, measuresHistory: [], - metricsType: 'INT', removeCustomMetric: () => {}, selectedDate: null, series: SERIES, @@ -88,9 +89,37 @@ it('should correctly render a graph', () => { expect(shallow()).toMatchSnapshot(); }); +it('should correctly render multiple graphs', () => { + expect(shallow()).toMatchSnapshot(); +}); + it('should correctly filter events', () => { expect(shallow().instance().getEvents()).toMatchSnapshot(); expect( shallow().instance().getEvents() ).toMatchSnapshot(); }); + +it('should show a loading view instead of the graph', () => { + expect( + shallow().find('.spinner') + ).toHaveLength(1); +}); + +it('should show that there is no history data', () => { + expect(shallow()).toMatchSnapshot(); + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); 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 69327953ec2..4a6f3ba02e2 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 @@ -90,39 +90,3 @@ 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().find('.spinner') - ).toHaveLength(1); -}); - -it('should show that there is no history data', () => { - expect( - shallow( - - ) - ).toMatchSnapshot(); - expect( - shallow( - - ) - ).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphHistory-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphHistory-test.js.snap new file mode 100644 index 00000000000..2b9e964ae1d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphHistory-test.js.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should correctly render a graph 1`] = ` +
+ +
+ +
+
+`; 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 7f82cd14330..0621e71f029 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 @@ -27,9 +27,62 @@ Array [ exports[`should correctly render a graph 1`] = `
+ +
+`; + +exports[`should correctly render multiple graphs 1`] = ` +
- + +
+`; + +exports[`should show that there is no history data 1`] = ` +
+
+ component_measures.no_history +
+
+`; + +exports[`should show that there is no history data 2`] = ` +
- + component_measures.no_history
`; 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 3d4be8af2fa..5fccf4aef7c 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 @@ -60,7 +60,33 @@ exports[`should render correctly the graph and legends 1`] = ` graph="overview" graphEndDate={null} graphStartDate={null} + 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", + }, + ], + ] + } leakPeriodDate="2017-05-16T13:50:02+0200" + loading={false} measuresHistory={ Array [ Object { @@ -82,7 +108,6 @@ exports[`should render correctly the graph and legends 1`] = ` }, ] } - metricsType="INT" removeCustomMetric={[Function]} series={ Array [ @@ -195,113 +220,3 @@ Object { ], } `; - -exports[`should show that there is no history data 1`] = ` -
- -
-
- component_measures.no_history -
-
- -
-`; - -exports[`should show that there is no history data 2`] = ` -
- -
-
- project_activity.graphs.custom.no_history -
-
- -
-`; 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 5ef705363f2..8ef83fa028c 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 @@ -43,6 +43,14 @@ padding-top: 52px; } +.project-activity-graphs { + flex-grow: 1; + display: flex; + flex-direction: column; + align-items: stretch; + justify-content: center; +} + .project-activity-graph-container { padding: 10px 0; flex-grow: 1; 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 872a1a8bee6..9578a6726f6 100644 --- a/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js +++ b/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js @@ -168,7 +168,7 @@ export default class AdvancedTimeline extends React.PureComponent { // $FlowFixMe selectedDate can't be null there p => p.x.valueOf() === selectedDate.valueOf() ); - const xRange = xScale.range(); + const xRange = xScale.range().sort(); const xPos = xScale(selectedDate); if (idx >= 0 && xPos >= xRange[0] && xPos <= xRange[1]) { return { -- 2.39.5