From b52ac0f12f454c4e15acdf71b8578d71332956fb Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Tue, 17 Dec 2019 16:57:15 +0100 Subject: [PATCH] SONAR-12632 Turn activity graph components into globally shared components - Move activity graph components to the shared components folder - Make activity graph local storage logic re-usable - Refactor CSS --- .../sonar-web/__mocks__/react-virtualized.tsx | 39 ++++ .../public/images/activity-chart.svg | 1 + .../src/main/js/app/styles/init/misc.css | 4 + .../__snapshots__/utils-test.ts.snap | 53 ----- .../projectActivity/__tests__/actions-test.ts | 3 +- .../projectActivity/__tests__/utils-test.ts | 165 ++------------ .../components/ProjectActivityApp.tsx | 3 +- .../ProjectActivityAppContainer.tsx | 35 ++- .../components/ProjectActivityGraphs.tsx | 39 ++-- .../GraphsTooltipsContentCoverage-test.tsx | 63 ------ .../GraphsTooltipsContentIssues-test.tsx | 59 ----- .../ProjectActivityAnalysesList-test.tsx | 2 +- .../__tests__/ProjectActivityApp-test.tsx | 2 +- .../__tests__/ProjectActivityGraphs-test.tsx | 2 +- ...raphsTooltipsContentCoverage-test.tsx.snap | 66 ------ ...hsTooltipsContentDuplication-test.tsx.snap | 27 --- .../GraphsTooltipsContentIssues-test.tsx.snap | 62 ------ .../ProjectActivityGraphs-test.tsx.snap | 4 +- .../components/projectActivity.css | 74 +------ .../src/main/js/apps/projectActivity/utils.ts | 172 ++------------- .../components/BranchAnalysisList.tsx | 5 +- .../activity-graph}/AddGraphMetric.tsx | 2 +- .../activity-graph}/AddGraphMetricPopup.tsx | 2 +- .../activity-graph}/GraphHistory.tsx | 46 ++-- .../activity-graph/GraphsHeader.tsx} | 64 ++++-- .../activity-graph}/GraphsHistory.tsx | 60 +++--- .../activity-graph}/GraphsLegendCustom.tsx | 5 +- .../activity-graph}/GraphsLegendItem.tsx | 2 +- .../activity-graph}/GraphsLegendStatic.tsx | 4 +- .../activity-graph}/GraphsTooltips.tsx | 9 +- .../activity-graph}/GraphsTooltipsContent.tsx | 4 +- .../GraphsTooltipsContentCoverage.tsx | 20 +- .../GraphsTooltipsContentDuplication.tsx | 14 +- .../GraphsTooltipsContentEvents.tsx | 9 +- .../GraphsTooltipsContentIssues.tsx | 10 +- .../activity-graph}/GraphsZoom.tsx | 5 +- .../__tests__/AddGraphMetric-test.tsx} | 24 ++- .../__tests__/AddGraphMetricPopup-test.tsx | 2 +- .../__tests__/GraphHistory-test.tsx | 2 +- .../__tests__/GraphsHistory-test.tsx | 4 +- .../__tests__/GraphsLegendCustom-test.tsx | 0 .../__tests__/GraphsLegendItem-test.tsx | 43 ++-- .../__tests__/GraphsLegendStatic-test.tsx | 0 .../__tests__/GraphsTooltips-test.tsx | 3 +- .../__tests__/GraphsTooltipsContent-test.tsx | 0 .../GraphsTooltipsContentCoverage-test.tsx | 65 ++++++ .../GraphsTooltipsContentDuplication-test.tsx | 48 +++-- .../GraphsTooltipsContentEvents-test.tsx | 0 .../GraphsTooltipsContentIssues-test.tsx | 59 +++++ .../AddGraphMetric-test.tsx.snap | 35 +++ .../AddGraphMetricPopup-test.tsx.snap | 0 .../__snapshots__/GraphHistory-test.tsx.snap | 4 +- .../__snapshots__/GraphsHistory-test.tsx.snap | 46 +++- .../GraphsLegendCustom-test.tsx.snap | 2 +- .../GraphsLegendItem-test.tsx.snap | 12 +- .../GraphsLegendStatic-test.tsx.snap | 2 +- .../GraphsTooltips-test.tsx.snap | 12 +- .../GraphsTooltipsContent-test.tsx.snap | 4 +- ...raphsTooltipsContentCoverage-test.tsx.snap | 71 +++++++ ...hsTooltipsContentDuplication-test.tsx.snap | 45 ++++ .../GraphsTooltipsContentEvents-test.tsx.snap | 4 +- .../GraphsTooltipsContentIssues-test.tsx.snap | 34 +++ .../__snapshots__/utils-test.ts.snap | 80 +++++++ .../activity-graph/__tests__/utils-test.ts | 200 +++++++++++++++++ .../js/components/activity-graph/styles.css | 69 ++++++ .../js/components/activity-graph/utils.ts | 166 +++++++++++++++ .../components/preview-graph/PreviewGraph.tsx | 201 ------------------ .../preview-graph/PreviewGraphTooltips.tsx | 80 ------- .../__tests__/PreviewGraphTooltips-test.tsx | 77 ------- .../PreviewGraphTooltips-test.tsx.snap | 52 ----- .../PreviewGraphTooltipsContent-test.tsx.snap | 28 --- .../project-activity.ts} | 44 ++-- .../resources/org/sonar/l10n/core.properties | 4 +- 73 files changed, 1257 insertions(+), 1401 deletions(-) create mode 100644 server/sonar-web/__mocks__/react-virtualized.tsx create mode 100644 server/sonar-web/public/images/activity-chart.svg delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentCoverage-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentIssues-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentCoverage-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentDuplication-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap rename server/sonar-web/src/main/js/{apps/projectActivity/components/forms => components/activity-graph}/AddGraphMetric.tsx (98%) rename server/sonar-web/src/main/js/{apps/projectActivity/components/forms => components/activity-graph}/AddGraphMetricPopup.tsx (97%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/GraphHistory.tsx (76%) rename server/sonar-web/src/main/js/{apps/projectActivity/components/ProjectActivityGraphsHeader.tsx => components/activity-graph/GraphsHeader.tsx} (51%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/GraphsHistory.tsx (64%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/GraphsLegendCustom.tsx (93%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/GraphsLegendItem.tsx (97%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/GraphsLegendStatic.tsx (93%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/GraphsTooltips.tsx (93%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/GraphsTooltipsContent.tsx (88%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/GraphsTooltipsContentCoverage.tsx (79%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/GraphsTooltipsContentDuplication.tsx (82%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/GraphsTooltipsContentEvents.tsx (83%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/GraphsTooltipsContentIssues.tsx (83%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/GraphsZoom.tsx (93%) rename server/sonar-web/src/main/js/components/{preview-graph/__tests__/PreviewGraphTooltipsContent-test.tsx => activity-graph/__tests__/AddGraphMetric-test.tsx} (69%) rename server/sonar-web/src/main/js/{apps/projectActivity/components/forms => components/activity-graph}/__tests__/AddGraphMetricPopup-test.tsx (96%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/GraphHistory-test.tsx (97%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/GraphsHistory-test.tsx (97%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/GraphsLegendCustom-test.tsx (100%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/GraphsLegendItem-test.tsx (54%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/GraphsLegendStatic-test.tsx (100%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/GraphsTooltips-test.tsx (96%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/GraphsTooltipsContent-test.tsx (100%) create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentCoverage-test.tsx rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/GraphsTooltipsContentDuplication-test.tsx (52%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/GraphsTooltipsContentEvents-test.tsx (100%) create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentIssues-test.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/AddGraphMetric-test.tsx.snap rename server/sonar-web/src/main/js/{apps/projectActivity/components/forms => components/activity-graph}/__tests__/__snapshots__/AddGraphMetricPopup-test.tsx.snap (100%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/__snapshots__/GraphHistory-test.tsx.snap (83%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/__snapshots__/GraphsHistory-test.tsx.snap (72%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/__snapshots__/GraphsLegendCustom-test.tsx.snap (95%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/__snapshots__/GraphsLegendItem-test.tsx.snap (71%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/__snapshots__/GraphsLegendStatic-test.tsx.snap (90%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/__snapshots__/GraphsTooltips-test.tsx.snap (88%) rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/__snapshots__/GraphsTooltipsContent-test.tsx.snap (68%) create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentCoverage-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentDuplication-test.tsx.snap rename server/sonar-web/src/main/js/{apps/projectActivity/components => components/activity-graph}/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.tsx.snap (85%) create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/utils-test.ts.snap create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/utils-test.ts create mode 100644 server/sonar-web/src/main/js/components/activity-graph/styles.css create mode 100644 server/sonar-web/src/main/js/components/activity-graph/utils.ts delete mode 100644 server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.tsx delete mode 100644 server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltips.tsx delete mode 100644 server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltips-test.tsx delete mode 100644 server/sonar-web/src/main/js/components/preview-graph/__tests__/__snapshots__/PreviewGraphTooltips-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/components/preview-graph/__tests__/__snapshots__/PreviewGraphTooltipsContent-test.tsx.snap rename server/sonar-web/src/main/js/{components/preview-graph/PreviewGraphTooltipsContent.tsx => types/project-activity.ts} (54%) diff --git a/server/sonar-web/__mocks__/react-virtualized.tsx b/server/sonar-web/__mocks__/react-virtualized.tsx new file mode 100644 index 00000000000..dbcab3e1a61 --- /dev/null +++ b/server/sonar-web/__mocks__/react-virtualized.tsx @@ -0,0 +1,39 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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. + */ +type AutoSizerProps = { + children: (props: AutoSizerChildProps) => React.ReactNode; + disableHeight?: boolean; + disableWidth?: boolean; +}; +type AutoSizerChildProps = { height?: number; width?: number }; + +module.exports = { + ...require.requireActual('react-virtualized'), + AutoSizer: ({ children, disableHeight, disableWidth }: AutoSizerProps) => { + const props: AutoSizerChildProps = {}; + if (!disableHeight) { + props.height = 200; + } + if (!disableWidth) { + props.width = 200; + } + return children(props); + } +}; diff --git a/server/sonar-web/public/images/activity-chart.svg b/server/sonar-web/public/images/activity-chart.svg new file mode 100644 index 00000000000..0f38b445036 --- /dev/null +++ b/server/sonar-web/public/images/activity-chart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/sonar-web/src/main/js/app/styles/init/misc.css b/server/sonar-web/src/main/js/app/styles/init/misc.css index cba59a7718d..2342b4676e3 100644 --- a/server/sonar-web/src/main/js/app/styles/init/misc.css +++ b/server/sonar-web/src/main/js/app/styles/init/misc.css @@ -402,6 +402,10 @@ th.huge-spacer-right { flex: 0 0 auto; } +.flex-grow { + flex-grow: 1; +} + .flex-shrink { flex-shrink: 1; min-width: 0; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.ts.snap b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.ts.snap index 1bc54255ce1..0f23892e414 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.ts.snap +++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.ts.snap @@ -1,58 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`generateCoveredLinesMetric should correctly generate covered lines metric 1`] = ` -Object { - "data": Array [ - Object { - "x": 2017-04-27T08:21:32.000Z, - "y": 88, - }, - Object { - "x": 2017-04-30T23:06:24.000Z, - "y": 50, - }, - ], - "name": "covered_lines", - "translatedName": "project_activity.custom_metric.covered_lines", - "type": "INT", -} -`; - -exports[`generateSeries should correctly generate the series 1`] = ` -Array [ - Object { - "data": Array [ - Object { - "x": 2017-04-27T08:21:32.000Z, - "y": 88, - }, - Object { - "x": 2017-04-30T23:06:24.000Z, - "y": 50, - }, - ], - "name": "covered_lines", - "translatedName": "project_activity.custom_metric.covered_lines", - "type": "INT", - }, - Object { - "data": Array [ - Object { - "x": 2017-04-27T08:21:32.000Z, - "y": 100, - }, - Object { - "x": 2017-04-30T23:06:24.000Z, - "y": 100, - }, - ], - "name": "lines_to_cover", - "translatedName": "Line to Cover", - "type": "PERCENT", - }, -] -`; - exports[`getAnalysesByVersionByDay should also filter analysis based on the query 1`] = ` Array [ Object { diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.ts b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.ts index 140520be2fa..f0c1feefab6 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.ts +++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.ts @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { parseDate } from 'sonar-ui-common/helpers/dates'; +import { DEFAULT_GRAPH } from '../../../components/activity-graph/utils'; import * as actions from '../actions'; const ANALYSES = [ @@ -69,7 +70,7 @@ const emptyState = { measuresHistory: [], measures: [], metrics: [], - query: { category: '', graph: '', project: '', customMetrics: [] } + query: { category: '', graph: DEFAULT_GRAPH, project: '', customMetrics: [] } }; const state = { ...emptyState, analyses: ANALYSES }; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.ts index f690a16b717..0928b0b9334 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.ts @@ -18,6 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as dates from 'sonar-ui-common/helpers/dates'; +import { DEFAULT_GRAPH } from '../../../components/activity-graph/utils'; +import { GraphType } from '../../../types/project-activity'; import * as utils from '../utils'; jest.mock('date-fns/start_of_day', () => @@ -67,78 +69,34 @@ const ANALYSES = [ { key: 'AVvtGF3IY6vCuQNDdwxI', date: dates.parseDate('2017-05-09T12:03:59.000Z'), events: [] } ]; -const HISTORY = [ - { - metric: 'lines_to_cover', - history: [ - { date: dates.parseDate('2017-04-27T08:21:32.000Z'), value: '100' }, - { date: dates.parseDate('2017-04-30T23:06:24.000Z'), value: '100' } - ] - }, - { - metric: 'uncovered_lines', - history: [ - { date: dates.parseDate('2017-04-27T08:21:32.000Z'), value: '12' }, - { date: dates.parseDate('2017-04-30T23:06:24.000Z'), value: '50' } - ] - } -]; - -const METRICS = [ - { id: '1', key: 'uncovered_lines', name: 'Uncovered Lines', type: 'INT' }, - { id: '2', key: 'lines_to_cover', name: 'Line to Cover', type: 'PERCENT' } -]; - const QUERY = { category: '', from: dates.parseDate('2017-04-27T08:21:32.000Z'), - graph: utils.DEFAULT_GRAPH, + graph: DEFAULT_GRAPH, project: 'foo', to: undefined, selectedDate: undefined, customMetrics: ['foo', 'bar', 'baz'] }; -describe('generateCoveredLinesMetric', () => { - it('should correctly generate covered lines metric', () => { - expect(utils.generateCoveredLinesMetric(HISTORY[1], HISTORY)).toMatchSnapshot(); - }); -}); - -describe('generateSeries', () => { - it('should correctly generate the series', () => { - expect( - utils.generateSeries(HISTORY, 'coverage', METRICS, ['uncovered_lines', 'lines_to_cover']) - ).toMatchSnapshot(); - }); -}); - describe('getAnalysesByVersionByDay', () => { it('should correctly map analysis by versions and by days', () => { expect( utils.getAnalysesByVersionByDay(ANALYSES, { - category: '', - customMetrics: [], - graph: utils.DEFAULT_GRAPH, - project: 'foo' + category: '' }) ).toMatchSnapshot(); }); it('should also filter analysis based on the query', () => { expect( utils.getAnalysesByVersionByDay(ANALYSES, { - category: 'QUALITY_PROFILE', - customMetrics: [], - graph: utils.DEFAULT_GRAPH, - project: 'foo' + category: 'QUALITY_PROFILE' }) ).toMatchSnapshot(); expect( utils.getAnalysesByVersionByDay(ANALYSES, { category: '', - customMetrics: [], - graph: utils.DEFAULT_GRAPH, - project: 'foo', + to: dates.parseDate('2017-06-09T11:12:27.000Z'), from: dates.parseDate('2017-05-18T14:13:07.000Z') }) @@ -170,54 +128,13 @@ describe('getAnalysesByVersionByDay', () => { } ], { - category: '', - customMetrics: [], - graph: utils.DEFAULT_GRAPH, - project: 'foo' + category: '' } ) ).toMatchSnapshot(); }); }); -describe('getDisplayedHistoryMetrics', () => { - const customMetrics = ['foo', 'bar']; - it('should return only displayed metrics on the graph', () => { - expect(utils.getDisplayedHistoryMetrics(utils.DEFAULT_GRAPH, [])).toEqual([ - 'bugs', - 'code_smells', - 'vulnerabilities' - ]); - expect(utils.getDisplayedHistoryMetrics('coverage', customMetrics)).toEqual([ - 'lines_to_cover', - 'uncovered_lines' - ]); - }); - it('should return all custom metrics for the custom graph', () => { - expect(utils.getDisplayedHistoryMetrics('custom', customMetrics)).toEqual(customMetrics); - }); -}); - -describe('getHistoryMetrics', () => { - const customMetrics = ['foo', 'bar']; - it('should return all metrics', () => { - expect(utils.getHistoryMetrics(utils.DEFAULT_GRAPH, [])).toEqual([ - 'bugs', - 'code_smells', - 'vulnerabilities', - 'reliability_rating', - 'security_rating', - 'sqale_rating' - ]); - expect(utils.getHistoryMetrics('coverage', customMetrics)).toEqual([ - 'lines_to_cover', - 'uncovered_lines', - 'coverage' - ]); - expect(utils.getHistoryMetrics('custom', customMetrics)).toEqual(customMetrics); - }); -}); - describe('parseQuery', () => { it('should parse query with default values', () => { expect( @@ -236,11 +153,13 @@ describe('serializeQuery', () => { from: '2017-04-27T08:21:32+0000', project: 'foo' }); - expect(utils.serializeQuery({ ...QUERY, graph: 'coverage', category: 'test' })).toEqual({ - from: '2017-04-27T08:21:32+0000', - project: 'foo', - category: 'test' - }); + expect(utils.serializeQuery({ ...QUERY, graph: GraphType.coverage, category: 'test' })).toEqual( + { + from: '2017-04-27T08:21:32+0000', + project: 'foo', + category: 'test' + } + ); }); }); @@ -252,59 +171,17 @@ describe('serializeUrlQuery', () => { custom_metrics: 'foo,bar,baz' }); expect( - utils.serializeUrlQuery({ ...QUERY, graph: 'coverage', category: 'test', customMetrics: [] }) + utils.serializeUrlQuery({ + ...QUERY, + graph: GraphType.coverage, + category: 'test', + customMetrics: [] + }) ).toEqual({ from: '2017-04-27T08:21:32+0000', id: 'foo', - graph: 'coverage', + graph: GraphType.coverage, category: 'test' }); }); }); - -describe('hasHistoryData', () => { - it('should correctly detect if there is history data', () => { - expect( - utils.hasHistoryData([ - { - name: 'foo', - translatedName: 'foo', - type: 'INT', - data: [ - { x: dates.parseDate('2017-04-27T08:21:32.000Z'), y: 2 }, - { x: dates.parseDate('2017-04-30T23:06:24.000Z'), y: 2 } - ] - } - ]) - ).toBeTruthy(); - expect( - utils.hasHistoryData([ - { - name: 'foo', - translatedName: 'foo', - type: 'INT', - data: [] - }, - { - name: 'bar', - translatedName: 'bar', - type: 'INT', - data: [ - { x: dates.parseDate('2017-04-27T08:21:32.000Z'), y: 2 }, - { x: dates.parseDate('2017-04-30T23:06:24.000Z'), y: 2 } - ] - } - ]) - ).toBeTruthy(); - expect( - utils.hasHistoryData([ - { - name: 'bar', - translatedName: 'bar', - type: 'INT', - data: [{ x: dates.parseDate('2017-04-27T08:21:32.000Z'), y: 2 }] - } - ]) - ).toBeFalsy(); - }); -}); diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx index d586c683458..7f9c038babe 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx @@ -23,7 +23,8 @@ import { parseDate } from 'sonar-ui-common/helpers/dates'; import { translate } from 'sonar-ui-common/helpers/l10n'; import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; -import { MeasureHistory, Query } from '../utils'; +import { MeasureHistory } from '../../../types/project-activity'; +import { Query } from '../utils'; import './projectActivity.css'; import ProjectActivityAnalysesList from './ProjectActivityAnalysesList'; import ProjectActivityGraphs from './ProjectActivityGraphs'; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.tsx index 3e68ce9187f..c4960998b5a 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.tsx @@ -24,16 +24,18 @@ import { parseDate } from 'sonar-ui-common/helpers/dates'; import { getAllMetrics } from '../../../api/metrics'; import * as api from '../../../api/projectActivity'; import { getAllTimeMachineData } from '../../../api/time-machine'; +import { + DEFAULT_GRAPH, + getActivityGraph, + getHistoryMetrics, + isCustomGraph +} from '../../../components/activity-graph/utils'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { BranchLike } from '../../../types/branch-like'; +import { GraphType, MeasureHistory } from '../../../types/project-activity'; import * as actions from '../actions'; import { customMetricsChanged, - DEFAULT_GRAPH, - getHistoryMetrics, - getProjectActivityGraph, - isCustomGraph, - MeasureHistory, parseQuery, Query, serializeQuery, @@ -59,6 +61,8 @@ export interface State { query: Query; } +export const PROJECT_ACTIVITY_GRAPH = 'sonar_project_activity.graph'; + export default class ProjectActivityAppContainer extends React.PureComponent { mounted = false; @@ -78,7 +82,10 @@ export default class ProjectActivityAppContainer extends React.PureComponent { return api.deleteAnalysis(analysis).then(() => { if (this.mounted) { - this.updateGraphData(this.state.query.graph, this.state.query.customMetrics); + this.updateGraphData( + this.state.query.graph || DEFAULT_GRAPH, + this.state.query.customMetrics + ); this.setState(actions.deleteAnalysis(analysis)); } }); @@ -242,7 +252,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent { + updateGraphData = (graph: GraphType, customMetrics: string[]) => { const graphMetrics = getHistoryMetrics(graph, customMetrics); this.setState({ graphLoading: true }); this.fetchMeasuresHistory(graphMetrics).then( @@ -312,7 +322,10 @@ export default class ProjectActivityAppContainer extends React.PureComponent key !== 'id' && locationQuery[key] !== '' ); - const { graph, customGraphs } = getProjectActivityGraph(this.props.component.key); + const { graph, customGraphs } = getActivityGraph( + PROJECT_ACTIVITY_GRAPH, + this.props.component.key + ); const emptyCustomGraph = isCustomGraph(graph) && customGraphs.length <= 0; // if there is no filter, but there are saved preferences in the localStorage diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.tsx index 43c56e6ca5b..d501ede75f1 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.tsx @@ -19,26 +19,21 @@ */ import { debounce, findLast, maxBy, minBy, sortBy } from 'lodash'; import * as React from 'react'; -import { save } from 'sonar-ui-common/helpers/storage'; +import GraphsHeader from '../../../components/activity-graph/GraphsHeader'; +import GraphsHistory from '../../../components/activity-graph/GraphsHistory'; +import GraphsZoom from '../../../components/activity-graph/GraphsZoom'; import { - datesQueryChanged, generateSeries, + getActivityGraph, getDisplayedHistoryMetrics, - getProjectActivityGraph, getSeriesMetricType, - historyQueryChanged, isCustomGraph, - MeasureHistory, - Point, - PROJECT_ACTIVITY_GRAPH, - PROJECT_ACTIVITY_GRAPH_CUSTOM, - Query, - Serie, + saveActivityGraph, splitSeriesInGraphs -} from '../utils'; -import GraphsHistory from './GraphsHistory'; -import GraphsZoom from './GraphsZoom'; -import ProjectActivityGraphsHeader from './ProjectActivityGraphsHeader'; +} from '../../../components/activity-graph/utils'; +import { GraphType, MeasureHistory, Point, Serie } from '../../../types/project-activity'; +import { datesQueryChanged, historyQueryChanged, Query } from '../utils'; +import { PROJECT_ACTIVITY_GRAPH } from './ProjectActivityAppContainer'; interface Props { analyses: T.ParsedAnalysis[]; @@ -148,20 +143,20 @@ export default class ProjectActivityGraphs extends React.PureComponent { const customMetrics = [...this.props.query.customMetrics, metric]; - save(PROJECT_ACTIVITY_GRAPH_CUSTOM, customMetrics.join(','), this.props.project); + saveActivityGraph(PROJECT_ACTIVITY_GRAPH, this.props.project, GraphType.custom, customMetrics); this.props.updateQuery({ customMetrics }); }; removeCustomMetric = (removedMetric: string) => { const customMetrics = this.props.query.customMetrics.filter(metric => metric !== removedMetric); - save(PROJECT_ACTIVITY_GRAPH_CUSTOM, customMetrics.join(','), this.props.project); + saveActivityGraph(PROJECT_ACTIVITY_GRAPH, this.props.project, GraphType.custom, customMetrics); this.props.updateQuery({ customMetrics }); }; - updateGraph = (graph: string) => { - save(PROJECT_ACTIVITY_GRAPH, graph, this.props.project); + updateGraph = (graph: GraphType) => { + saveActivityGraph(PROJECT_ACTIVITY_GRAPH, this.props.project, graph); if (isCustomGraph(graph) && this.props.query.customMetrics.length <= 0) { - const { customGraphs } = getProjectActivityGraph(this.props.project); + const { customGraphs } = getActivityGraph(PROJECT_ACTIVITY_GRAPH, this.props.project); this.props.updateQuery({ graph, customMetrics: customGraphs }); } else { this.props.updateQuery({ graph, customMetrics: [] }); @@ -198,8 +193,9 @@ export default class ProjectActivityGraphs extends React.PureComponent - diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentCoverage-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentCoverage-test.tsx deleted file mode 100644 index 4b9ba0a7e95..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentCoverage-test.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { parseDate } from 'sonar-ui-common/helpers/dates'; -import GraphsTooltipsContentCoverage from '../GraphsTooltipsContentCoverage'; - -const MEASURES_COVERAGE = [ - { - metric: 'coverage', - history: [ - { date: parseDate('2011-10-01T22:01:00.000Z') }, - { date: parseDate('2011-10-25T10:27:41.000Z'), value: '80.3' } - ] - }, - { - metric: 'lines_to_cover', - history: [ - { date: parseDate('2011-10-01T22:01:00.000Z'), value: '60545' }, - { date: parseDate('2011-10-25T10:27:41.000Z'), value: '65215' } - ] - }, - { - metric: 'uncovered_lines', - history: [ - { date: parseDate('2011-10-01T22:01:00.000Z'), value: '40564' }, - { date: parseDate('2011-10-25T10:27:41.000Z'), value: '10245' } - ] - } -]; - -const DEFAULT_PROPS = { - addSeparator: true, - measuresHistory: MEASURES_COVERAGE, - tooltipIdx: 1 -}; - -it('should render correctly', () => { - expect(shallow()).toMatchSnapshot(); -}); - -it('should render correctly when data is missing', () => { - expect( - shallow() - ).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentIssues-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentIssues-test.tsx deleted file mode 100644 index babe1217bae..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentIssues-test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { parseDate } from 'sonar-ui-common/helpers/dates'; -import GraphsTooltipsContentIssues from '../GraphsTooltipsContentIssues'; - -const MEASURES_ISSUES = [ - { - metric: 'bugs', - history: [ - { date: parseDate('2011-10-01T22:01:00.000Z'), value: '500' }, - { date: parseDate('2011-10-25T10:27:41.000Z'), value: '1.2k' } - ] - }, - { - metric: 'reliability_rating', - history: [ - { date: parseDate('2011-10-01T22:01:00.000Z') }, - { date: parseDate('2011-10-25T10:27:41.000Z'), value: '5.0' } - ] - } -]; - -const DEFAULT_PROPS = { - index: 2, - measuresHistory: MEASURES_ISSUES, - name: 'bugs', - tooltipIdx: 1, - translatedName: 'Bugs', - value: '1.2k' -}; - -it('should render correctly', () => { - expect(shallow()).toMatchSnapshot(); -}); - -it('should render correctly when rating data is missing', () => { - expect( - shallow() - ).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAnalysesList-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAnalysesList-test.tsx index 27f010105ab..d4f5e2ad6e4 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAnalysesList-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAnalysesList-test.tsx @@ -20,9 +20,9 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { parseDate } from 'sonar-ui-common/helpers/dates'; +import { DEFAULT_GRAPH } from '../../../../components/activity-graph/utils'; import { mockParsedAnalysis } from '../../../../helpers/testMocks'; import { ComponentQualifier } from '../../../../types/component'; -import { DEFAULT_GRAPH } from '../../utils'; import ProjectActivityAnalysesList from '../ProjectActivityAnalysesList'; jest.mock('date-fns/start_of_day', () => (date: Date) => { diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-test.tsx index d1678a19e02..24c3b387b81 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-test.tsx @@ -20,7 +20,7 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { parseDate } from 'sonar-ui-common/helpers/dates'; -import { DEFAULT_GRAPH } from '../../utils'; +import { DEFAULT_GRAPH } from '../../../../components/activity-graph/utils'; import ProjectActivityApp from '../ProjectActivityApp'; const ANALYSES = [ diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.tsx index 3b36620845f..613cbd2959d 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.tsx @@ -20,7 +20,7 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { parseDate } from 'sonar-ui-common/helpers/dates'; -import { DEFAULT_GRAPH } from '../../utils'; +import { DEFAULT_GRAPH } from '../../../../components/activity-graph/utils'; import ProjectActivityGraphs from '../ProjectActivityGraphs'; const ANALYSES = [ diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentCoverage-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentCoverage-test.tsx.snap deleted file mode 100644 index e68fa2aacd4..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentCoverage-test.tsx.snap +++ /dev/null @@ -1,66 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` - - - -
- - - - - 10short_number_suffix.k - - - metric.uncovered_lines.name - - - - - 80.3% - - - metric.coverage.name - - - -`; - -exports[`should render correctly when data is missing 1`] = ` - - - -
- - - - - 41short_number_suffix.k - - - metric.uncovered_lines.name - - - -`; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentDuplication-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentDuplication-test.tsx.snap deleted file mode 100644 index a8ba47bbcc8..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentDuplication-test.tsx.snap +++ /dev/null @@ -1,27 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` - - - -
- - - - - 10,245.0% - - - metric.duplicated_lines_density.name - - - -`; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap deleted file mode 100644 index 2cb9fd48c09..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap +++ /dev/null @@ -1,62 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` - - - - - - - 1.2k - - - - - Bugs - - -`; - -exports[`should render correctly when rating data is missing 1`] = ` - - - - - - - 500 - - - - Bugs - - -`; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.tsx.snap index 21d5f179b4b..b817fd80c5e 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.tsx.snap @@ -4,8 +4,9 @@ exports[`should render correctly the graph and legends 1`] = `
- = { - issues: ['bugs', 'code_smells', 'vulnerabilities'], - coverage: ['lines_to_cover', 'uncovered_lines'], - duplications: ['ncloc', 'duplicated_lines'] -}; -export const GRAPHS_METRICS: T.Dict = { - issues: GRAPHS_METRICS_DISPLAYED['issues'].concat([ - 'reliability_rating', - 'security_rating', - 'sqale_rating' - ]), - coverage: GRAPHS_METRICS_DISPLAYED['coverage'].concat(['coverage']), - duplications: GRAPHS_METRICS_DISPLAYED['duplications'].concat(['duplicated_lines_density']) -}; - -export const PROJECT_ACTIVITY_GRAPH = 'sonar_project_activity.graph'; -export const PROJECT_ACTIVITY_GRAPH_CUSTOM = 'sonar_project_activity.graph.custom'; export function activityQueryChanged(prevQuery: Query, nextQuery: Query) { return prevQuery.category !== nextQuery.category || datesQueryChanged(prevQuery, nextQuery); @@ -98,105 +57,24 @@ export function datesQueryChanged(prevQuery: Query, nextQuery: Query) { return !isEqual(prevQuery.from, nextQuery.from) || !isEqual(prevQuery.to, nextQuery.to); } -export function hasDataValues(serie: Serie) { - return serie.data.some(point => Boolean(point.y || point.y === 0)); -} - -export function hasHistoryData(series: Serie[]) { - return series.some(serie => serie.data && serie.data.length > 1); -} - -export function hasHistoryDataValue(series: Serie[]) { - return series.some(serie => serie.data && serie.data.length > 1 && hasDataValues(serie)); -} - export function historyQueryChanged(prevQuery: Query, nextQuery: Query) { return prevQuery.graph !== nextQuery.graph; } -export function isCustomGraph(graph: string) { - return graph === 'custom'; -} - export function selectedDateQueryChanged(prevQuery: Query, nextQuery: Query) { return !isEqual(prevQuery.selectedDate, nextQuery.selectedDate); } -export function generateCoveredLinesMetric( - uncoveredLines: MeasureHistory, - measuresHistory: MeasureHistory[] -) { - const linesToCover = measuresHistory.find(measure => measure.metric === 'lines_to_cover'); - return { - data: linesToCover - ? uncoveredLines.history.map((analysis, idx) => ({ - x: analysis.date, - y: Number(linesToCover.history[idx].value) - Number(analysis.value) - })) - : [], - name: 'covered_lines', - translatedName: translate('project_activity.custom_metric.covered_lines'), - type: 'INT' - }; -} - -function findMetric(key: string, metrics: T.Metric[] | T.Dict) { - if (Array.isArray(metrics)) { - return metrics.find(metric => metric.key === key); - } - return metrics[key]; -} - -export function generateSeries( - measuresHistory: MeasureHistory[], - graph: string, - metrics: T.Metric[] | T.Dict, - displayedMetrics: string[] -): Serie[] { - if (displayedMetrics.length <= 0 || typeof measuresHistory === 'undefined') { - return []; - } - 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 = findMetric(measure.metric, metrics); - 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 === 'covered_lines' ? 'uncovered_lines' : serie.name) - ); -} - -export function splitSeriesInGraphs(series: Serie[], maxGraph: number, maxSeries: number) { - return flatMap( - groupBy(series, serie => serie.type), - type => chunk(type, maxSeries) - ).slice(0, maxGraph); -} - -export function getSeriesMetricType(series: Serie[]) { - return series.length > 0 ? series[0].type : 'INT'; -} - interface AnalysesByDay { byDay: T.Dict; version: string | null; key: string | null; } -export function getAnalysesByVersionByDay(analyses: T.ParsedAnalysis[], query: Query) { +export function getAnalysesByVersionByDay( + analyses: T.ParsedAnalysis[], + query: Pick +) { return analyses.reduce((acc, analysis) => { let currentVersion = acc[acc.length - 1]; const versionEvent = analysis.events.find(event => event.category === 'VERSION'); @@ -237,31 +115,6 @@ export function getAnalysesByVersionByDay(analyses: T.ParsedAnalysis[], query: Q }, []); } -export function getDisplayedHistoryMetrics(graph: string, customMetrics: string[]) { - return isCustomGraph(graph) ? customMetrics : GRAPHS_METRICS_DISPLAYED[graph]; -} - -export function getHistoryMetrics(graph: string, customMetrics: string[]) { - return isCustomGraph(graph) ? customMetrics : GRAPHS_METRICS[graph]; -} - -export function getProjectActivityGraph(project: string) { - const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM, project); - return { - graph: get(PROJECT_ACTIVITY_GRAPH, project) || 'issues', - customGraphs: customGraphs ? customGraphs.split(',') : [] - }; -} - -function parseGraph(value?: string) { - const graph = parseAsString(value); - return GRAPH_TYPES.includes(graph) ? graph : DEFAULT_GRAPH; -} - -function serializeGraph(value: string) { - return value === DEFAULT_GRAPH ? undefined : value; -} - export function parseQuery(urlQuery: T.RawQuery): Query { return { category: parseAsString(urlQuery['category']), @@ -294,3 +147,12 @@ export function serializeUrlQuery(query: Query): T.RawQuery { selected_date: serializeDate(query.selectedDate) }); } + +function parseGraph(value?: string) { + const graph = parseAsString(value); + return Object.keys(GraphType).includes(graph) ? (graph as GraphType) : DEFAULT_GRAPH; +} + +function serializeGraph(value?: GraphType) { + return value === DEFAULT_GRAPH ? undefined : value; +} diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx index 327ce48bfed..f6118da854a 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx @@ -156,10 +156,7 @@ export default class BranchAnalysisList extends React.PureComponent void; + removeCustomMetric?: (metric: string) => void; showAreas: boolean; series: Serie[]; selectedDate?: Date; - updateGraphZoom: (from?: Date, to?: Date) => void; - updateSelectedDate: (selectedDate?: Date) => void; + updateGraphZoom?: (from?: Date, to?: Date) => void; + updateSelectedDate?: (selectedDate?: Date) => void; updateTooltip: (selectedDate?: Date) => void; } @@ -67,30 +67,42 @@ export default class GraphHistory extends React.PureComponent { }; render() { - const { graph, selectedDate, series } = this.props; + const { + events, + graph, + graphEndDate, + graphStartDate, + isCustom, + leakPeriodDate, + measuresHistory, + metricsType, + selectedDate, + series, + showAreas + } = this.props; const { tooltipIdx, tooltipXPos } = this.state; return ( -
- {this.props.isCustom ? ( +
+ {isCustom && this.props.removeCustomMetric ? ( ) : ( )} -
+
{({ height, width }) => (
{ tooltipIdx !== undefined && tooltipXPos !== undefined && ( void; - removeCustomMetric: (metric: string) => void; - graph: string; + addCustomMetric?: (metric: string) => void; + className?: string; + removeCustomMetric?: (metric: string) => void; + graph: GraphType; metrics: T.Metric[]; metricsTypeFilter?: string[]; - selectedMetrics: string[]; + selectedMetrics?: string[]; updateGraph: (graphType: string) => void; } -export default class ProjectActivityGraphsHeader extends React.PureComponent { +export default class GraphsHeader extends React.PureComponent { handleGraphChange = (option: { value: string }) => { if (option.value !== this.props.graph) { this.props.updateGraph(option.value); @@ -41,32 +45,46 @@ export default class ProjectActivityGraphsHeader extends React.PureComponent ({ - label: translate('project_activity.graphs', graph), - value: graph + const { + addCustomMetric, + className, + graph, + metrics, + metricsTypeFilter, + removeCustomMetric, + selectedMetrics = [] + } = this.props; + + const types = getGraphTypes(addCustomMetric === undefined || removeCustomMetric === undefined); + + const selectOptions = types.map(type => ({ + label: translate('project_activity.graphs', type), + value: type })); return ( -
+