From b52ac0f12f454c4e15acdf71b8578d71332956fb Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Tue, 17 Dec 2019 16:57:15 +0100 Subject: 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 --- server/sonar-web/__mocks__/react-virtualized.tsx | 39 ++++ server/sonar-web/public/images/activity-chart.svg | 1 + .../sonar-web/src/main/js/app/styles/init/misc.css | 4 + .../__tests__/__snapshots__/utils-test.ts.snap | 53 ------ .../apps/projectActivity/__tests__/actions-test.ts | 3 +- .../apps/projectActivity/__tests__/utils-test.ts | 165 +++-------------- .../projectActivity/components/GraphHistory.tsx | 121 ------------- .../projectActivity/components/GraphsHistory.tsx | 140 -------------- .../components/GraphsLegendCustom.tsx | 62 ------- .../components/GraphsLegendItem.tsx | 66 ------- .../components/GraphsLegendStatic.tsx | 42 ----- .../projectActivity/components/GraphsTooltips.tsx | 124 ------------- .../components/GraphsTooltipsContent.tsx | 40 ---- .../components/GraphsTooltipsContentCoverage.tsx | 74 -------- .../GraphsTooltipsContentDuplication.tsx | 65 ------- .../components/GraphsTooltipsContentEvents.tsx | 51 ------ .../components/GraphsTooltipsContentIssues.tsx | 60 ------ .../apps/projectActivity/components/GraphsZoom.tsx | 61 ------- .../components/ProjectActivityApp.tsx | 3 +- .../components/ProjectActivityAppContainer.tsx | 35 ++-- .../components/ProjectActivityGraphs.tsx | 39 ++-- .../components/ProjectActivityGraphsHeader.tsx | 72 -------- .../components/__tests__/GraphHistory-test.tsx | 56 ------ .../components/__tests__/GraphsHistory-test.tsx | 119 ------------ .../__tests__/GraphsLegendCustom-test.tsx | 48 ----- .../components/__tests__/GraphsLegendItem-test.tsx | 48 ----- .../__tests__/GraphsLegendStatic-test.tsx | 31 ---- .../components/__tests__/GraphsTooltips-test.tsx | 107 ----------- .../__tests__/GraphsTooltipsContent-test.tsx | 33 ---- .../GraphsTooltipsContentCoverage-test.tsx | 63 ------- .../GraphsTooltipsContentDuplication-test.tsx | 49 ----- .../__tests__/GraphsTooltipsContentEvents-test.tsx | 33 ---- .../__tests__/GraphsTooltipsContentIssues-test.tsx | 59 ------ .../__tests__/ProjectActivityAnalysesList-test.tsx | 2 +- .../__tests__/ProjectActivityApp-test.tsx | 2 +- .../__tests__/ProjectActivityGraphs-test.tsx | 2 +- .../__snapshots__/GraphHistory-test.tsx.snap | 40 ---- .../__snapshots__/GraphsHistory-test.tsx.snap | 150 --------------- .../__snapshots__/GraphsLegendCustom-test.tsx.snap | 48 ----- .../__snapshots__/GraphsLegendItem-test.tsx.snap | 57 ------ .../__snapshots__/GraphsLegendStatic-test.tsx.snap | 22 --- .../__snapshots__/GraphsTooltips-test.tsx.snap | 149 --------------- .../GraphsTooltipsContent-test.tsx.snap | 25 --- .../GraphsTooltipsContentCoverage-test.tsx.snap | 66 ------- .../GraphsTooltipsContentDuplication-test.tsx.snap | 27 --- .../GraphsTooltipsContentEvents-test.tsx.snap | 42 ----- .../GraphsTooltipsContentIssues-test.tsx.snap | 62 ------- .../ProjectActivityGraphs-test.tsx.snap | 4 +- .../components/forms/AddGraphMetric.tsx | 150 --------------- .../components/forms/AddGraphMetricPopup.tsx | 81 --------- .../forms/__tests__/AddGraphMetricPopup-test.tsx | 64 ------- .../AddGraphMetricPopup-test.tsx.snap | 90 --------- .../projectActivity/components/projectActivity.css | 74 +------- .../src/main/js/apps/projectActivity/utils.ts | 172 ++---------------- .../components/BranchAnalysisList.tsx | 5 +- .../components/activity-graph/AddGraphMetric.tsx | 150 +++++++++++++++ .../activity-graph/AddGraphMetricPopup.tsx | 81 +++++++++ .../js/components/activity-graph/GraphHistory.tsx | 133 ++++++++++++++ .../js/components/activity-graph/GraphsHeader.tsx | 90 +++++++++ .../js/components/activity-graph/GraphsHistory.tsx | 142 +++++++++++++++ .../activity-graph/GraphsLegendCustom.tsx | 63 +++++++ .../components/activity-graph/GraphsLegendItem.tsx | 66 +++++++ .../activity-graph/GraphsLegendStatic.tsx | 42 +++++ .../components/activity-graph/GraphsTooltips.tsx | 125 +++++++++++++ .../activity-graph/GraphsTooltipsContent.tsx | 40 ++++ .../GraphsTooltipsContentCoverage.tsx | 70 +++++++ .../GraphsTooltipsContentDuplication.tsx | 63 +++++++ .../activity-graph/GraphsTooltipsContentEvents.tsx | 54 ++++++ .../activity-graph/GraphsTooltipsContentIssues.tsx | 60 ++++++ .../js/components/activity-graph/GraphsZoom.tsx | 62 +++++++ .../__tests__/AddGraphMetric-test.tsx | 40 ++++ .../__tests__/AddGraphMetricPopup-test.tsx | 64 +++++++ .../activity-graph/__tests__/GraphHistory-test.tsx | 56 ++++++ .../__tests__/GraphsHistory-test.tsx | 119 ++++++++++++ .../__tests__/GraphsLegendCustom-test.tsx | 48 +++++ .../__tests__/GraphsLegendItem-test.tsx | 51 ++++++ .../__tests__/GraphsLegendStatic-test.tsx | 31 ++++ .../__tests__/GraphsTooltips-test.tsx | 108 +++++++++++ .../__tests__/GraphsTooltipsContent-test.tsx | 33 ++++ .../GraphsTooltipsContentCoverage-test.tsx | 65 +++++++ .../GraphsTooltipsContentDuplication-test.tsx | 51 ++++++ .../__tests__/GraphsTooltipsContentEvents-test.tsx | 33 ++++ .../__tests__/GraphsTooltipsContentIssues-test.tsx | 59 ++++++ .../__snapshots__/AddGraphMetric-test.tsx.snap | 35 ++++ .../AddGraphMetricPopup-test.tsx.snap | 90 +++++++++ .../__snapshots__/GraphHistory-test.tsx.snap | 40 ++++ .../__snapshots__/GraphsHistory-test.tsx.snap | 180 ++++++++++++++++++ .../__snapshots__/GraphsLegendCustom-test.tsx.snap | 48 +++++ .../__snapshots__/GraphsLegendItem-test.tsx.snap | 57 ++++++ .../__snapshots__/GraphsLegendStatic-test.tsx.snap | 22 +++ .../__snapshots__/GraphsTooltips-test.tsx.snap | 149 +++++++++++++++ .../GraphsTooltipsContent-test.tsx.snap | 25 +++ .../GraphsTooltipsContentCoverage-test.tsx.snap | 71 ++++++++ .../GraphsTooltipsContentDuplication-test.tsx.snap | 45 +++++ .../GraphsTooltipsContentEvents-test.tsx.snap | 42 +++++ .../GraphsTooltipsContentIssues-test.tsx.snap | 34 ++++ .../__tests__/__snapshots__/utils-test.ts.snap | 80 ++++++++ .../activity-graph/__tests__/utils-test.ts | 200 ++++++++++++++++++++ .../main/js/components/activity-graph/styles.css | 69 +++++++ .../src/main/js/components/activity-graph/utils.ts | 166 +++++++++++++++++ .../js/components/preview-graph/PreviewGraph.tsx | 201 --------------------- .../preview-graph/PreviewGraphTooltips.tsx | 80 -------- .../preview-graph/PreviewGraphTooltipsContent.tsx | 45 ----- .../__tests__/PreviewGraphTooltips-test.tsx | 77 -------- .../__tests__/PreviewGraphTooltipsContent-test.tsx | 32 ---- .../PreviewGraphTooltips-test.tsx.snap | 52 ------ .../PreviewGraphTooltipsContent-test.tsx.snap | 28 --- .../src/main/js/types/project-activity.ts | 47 +++++ 108 files changed, 3535 insertions(+), 3679 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/GraphHistory.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendCustom.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendItem.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContent.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentCoverage.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentDuplication.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentEvents.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentIssues.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphHistory-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendCustom-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendItem-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendStatic-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltips-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContent-test.tsx 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__/GraphsTooltipsContentDuplication-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentEvents-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__/GraphHistory-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsHistory-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendCustom-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendItem-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendStatic-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltips-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContent-test.tsx.snap 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__/GraphsTooltipsContentEvents-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetricPopup.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/AddGraphMetricPopup-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/AddGraphMetricPopup-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/AddGraphMetricPopup.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/GraphHistory.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/GraphsLegendCustom.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/GraphsLegendItem.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/GraphsLegendStatic.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/GraphsTooltips.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContent.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentCoverage.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentDuplication.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentEvents.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentIssues.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/GraphsZoom.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetric-test.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetricPopup-test.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphHistory-test.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsHistory-test.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendCustom-test.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendItem-test.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendStatic-test.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltips-test.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContent-test.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentCoverage-test.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentDuplication-test.tsx create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentEvents-test.tsx 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 create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/AddGraphMetricPopup-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphHistory-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsHistory-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendCustom-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendItem-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendStatic-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltips-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContent-test.tsx.snap 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 create mode 100644 server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.tsx.snap 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/PreviewGraphTooltipsContent.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__/PreviewGraphTooltipsContent-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 create mode 100644 server/sonar-web/src/main/js/types/project-activity.ts (limited to 'server') 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/GraphHistory.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphHistory.tsx deleted file mode 100644 index e6658b63bcb..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphHistory.tsx +++ /dev/null @@ -1,121 +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 * as React from 'react'; -import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'; -import AdvancedTimeline from 'sonar-ui-common/components/charts/AdvancedTimeline'; -import { formatMeasure } from 'sonar-ui-common/helpers/measures'; -import { getShortType } from '../../../helpers/measures'; -import { MeasureHistory, Serie } from '../utils'; -import GraphsLegendCustom from './GraphsLegendCustom'; -import GraphsLegendStatic from './GraphsLegendStatic'; -import GraphsTooltips from './GraphsTooltips'; - -interface Props { - events: T.AnalysisEvent[]; - graph: string; - graphEndDate?: Date; - graphStartDate?: Date; - leakPeriodDate?: Date; - isCustom: boolean; - measuresHistory: MeasureHistory[]; - metricsType: string; - removeCustomMetric: (metric: string) => void; - showAreas: boolean; - series: Serie[]; - selectedDate?: Date; - updateGraphZoom: (from?: Date, to?: Date) => void; - updateSelectedDate: (selectedDate?: Date) => void; - updateTooltip: (selectedDate?: Date) => void; -} - -interface State { - tooltipIdx?: number; - tooltipXPos?: number; -} - -export default class GraphHistory extends React.PureComponent { - state: State = {}; - - formatValue = (tick: string | number) => { - return formatMeasure(tick, getShortType(this.props.metricsType)); - }; - - formatTooltipValue = (tick: string | number) => { - return formatMeasure(tick, 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 !== undefined && - tooltipIdx !== undefined && - tooltipXPos !== undefined && ( - - )} -
- )} -
-
-
- ); - } -} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.tsx deleted file mode 100644 index 8ac065c55c5..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.tsx +++ /dev/null @@ -1,140 +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 { isEqual } from 'lodash'; -import * as React from 'react'; -import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { - getSeriesMetricType, - hasHistoryData, - isCustomGraph, - MeasureHistory, - Serie -} from '../utils'; -import GraphHistory from './GraphHistory'; - -interface Props { - analyses: T.ParsedAnalysis[]; - eventFilter: string; - graph: string; - graphs: Serie[][]; - graphEndDate?: Date; - graphStartDate?: Date; - leakPeriodDate?: Date; - loading: boolean; - measuresHistory: MeasureHistory[]; - removeCustomMetric: (metric: string) => void; - selectedDate?: Date; - series: Serie[]; - updateGraphZoom: (from?: Date, to?: Date) => void; - updateSelectedDate: (selectedDate?: Date) => void; -} - -interface State { - selectedDate?: Date; -} - -export default class GraphsHistory extends React.PureComponent { - 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 }); - } - } - - getSelectedDateEvents = () => { - const { selectedDate } = this.state; - const { analyses } = this.props; - if (analyses && selectedDate) { - const analysis = analyses.find( - analysis => analysis.date.valueOf() === selectedDate.valueOf() - ); - if (analysis) { - return analysis.events; - } - } - return []; - }; - - updateTooltip = (selectedDate?: Date) => { - this.setState({ selectedDate }); - }; - - render() { - const { graph, loading, series } = this.props; - const isCustom = isCustomGraph(graph); - - if (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/GraphsLegendCustom.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendCustom.tsx deleted file mode 100644 index 67b552883c6..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendCustom.tsx +++ /dev/null @@ -1,62 +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 * as React from 'react'; -import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { hasDataValues, Serie } from '../utils'; -import GraphsLegendItem from './GraphsLegendItem'; - -interface Props { - removeMetric: (metric: string) => void; - series: Serie[]; -} - -export default function GraphsLegendCustom({ removeMetric, series }: Props) { - return ( -
- {series.map((serie, idx) => { - const hasData = hasDataValues(serie); - const legendItem = ( - - ); - if (!hasData) { - return ( - - {legendItem} - - ); - } - return ( - - {legendItem} - - ); - })} -
- ); -} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendItem.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendItem.tsx deleted file mode 100644 index 49052648fca..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendItem.tsx +++ /dev/null @@ -1,66 +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 * as classNames from 'classnames'; -import * as React from 'react'; -import { ClearButton } from 'sonar-ui-common/components/controls/buttons'; -import AlertWarnIcon from 'sonar-ui-common/components/icons/AlertWarnIcon'; -import ChartLegendIcon from 'sonar-ui-common/components/icons/ChartLegendIcon'; - -interface Props { - className?: string; - index: number; - metric: string; - name: string; - showWarning?: boolean; - removeMetric?: (metric: string) => void; -} - -export default class GraphsLegendItem extends React.PureComponent { - handleClick = () => { - if (this.props.removeMetric) { - this.props.removeMetric(this.props.metric); - } - }; - - render() { - const isActionable = this.props.removeMetric != null; - const legendClass = classNames( - { 'project-activity-graph-legend-actionable': isActionable }, - this.props.className - ); - return ( - - {this.props.showWarning ? ( - - ) : ( - - )} - {this.props.name} - {isActionable && ( - - )} - - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.tsx deleted file mode 100644 index b3973e58d5e..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.tsx +++ /dev/null @@ -1,42 +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 * as React from 'react'; -import { Serie } from '../utils'; -import GraphsLegendItem from './GraphsLegendItem'; - -interface Props { - series: Array>; -} - -export default function GraphsLegendStatic({ series }: Props) { - return ( -
- {series.map((serie, idx) => ( - - ))} -
- ); -} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.tsx deleted file mode 100644 index 60d0ee93743..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.tsx +++ /dev/null @@ -1,124 +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 * as React from 'react'; -import { Popup, PopupPlacement } from 'sonar-ui-common/components/ui/popups'; -import { isDefined } from 'sonar-ui-common/helpers/types'; -import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; -import { DEFAULT_GRAPH, MeasureHistory, Serie } from '../utils'; -import GraphsTooltipsContent from './GraphsTooltipsContent'; -import GraphsTooltipsContentCoverage from './GraphsTooltipsContentCoverage'; -import GraphsTooltipsContentDuplication from './GraphsTooltipsContentDuplication'; -import GraphsTooltipsContentEvents from './GraphsTooltipsContentEvents'; -import GraphsTooltipsContentIssues from './GraphsTooltipsContentIssues'; - -interface Props { - events: T.AnalysisEvent[]; - formatValue: (tick: number | string) => string; - graph: string; - graphWidth: number; - measuresHistory: MeasureHistory[]; - selectedDate: Date; - series: Serie[]; - tooltipIdx: number; - tooltipPos: number; -} - -const TOOLTIP_WIDTH = 250; - -export default class GraphsTooltips extends React.PureComponent { - renderContent() { - const { tooltipIdx } = this.props; - - return this.props.series.map((serie, idx) => { - const point = serie.data[tooltipIdx]; - if (!point || (!point.y && point.y !== 0)) { - return null; - } - if (this.props.graph === DEFAULT_GRAPH) { - return ( - - ); - } else { - return ( - - ); - } - }); - } - - render() { - const { events, measuresHistory, tooltipIdx } = this.props; - const top = 30; - let left = this.props.tooltipPos + 60; - let placement = PopupPlacement.RightTop; - if (left > this.props.graphWidth - TOOLTIP_WIDTH - 50) { - left -= TOOLTIP_WIDTH; - placement = PopupPlacement.LeftTop; - } - const tooltipContent = this.renderContent().filter(isDefined); - const addSeparator = tooltipContent.length > 0; - return ( - -
-
- -
- - {tooltipContent} - {this.props.graph === 'coverage' && ( - - )} - {this.props.graph === 'duplications' && ( - - )} - {events && events.length > 0 && ( - - )} -
-
-
- ); - } -} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContent.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContent.tsx deleted file mode 100644 index 32450e15e69..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContent.tsx +++ /dev/null @@ -1,40 +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 * as React from 'react'; -import ChartLegendIcon from 'sonar-ui-common/components/icons/ChartLegendIcon'; - -interface Props { - name: string; - index: number; - translatedName: string; - value: string; -} - -export default function GraphsTooltipsContent({ name, index, translatedName, value }: Props) { - return ( - - - - - {value} - {translatedName} - - ); -} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentCoverage.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentCoverage.tsx deleted file mode 100644 index 8a82fd94581..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentCoverage.tsx +++ /dev/null @@ -1,74 +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 * as React from 'react'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { formatMeasure } from 'sonar-ui-common/helpers/measures'; -import { MeasureHistory } from '../utils'; - -interface Props { - addSeparator: boolean; - measuresHistory: MeasureHistory[]; - tooltipIdx: number; -} - -export default function GraphsTooltipsContentCoverage({ - addSeparator, - measuresHistory, - tooltipIdx -}: Props) { - const uncovered = measuresHistory.find(measure => measure.metric === 'uncovered_lines'); - const coverage = measuresHistory.find(measure => measure.metric === 'coverage'); - if (!uncovered || !uncovered.history[tooltipIdx] || !coverage || !coverage.history[tooltipIdx]) { - return null; - } - const uncoveredValue = uncovered.history[tooltipIdx].value; - const coverageValue = coverage.history[tooltipIdx].value; - return ( - - {addSeparator && ( - - -
- - - )} - {uncoveredValue && ( - - - {formatMeasure(uncoveredValue, 'SHORT_INT')} - - {translate('metric.uncovered_lines.name')} - - )} - {coverageValue && ( - - - {formatMeasure(coverageValue, 'PERCENT')} - - {translate('metric.coverage.name')} - - )} - - ); -} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentDuplication.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentDuplication.tsx deleted file mode 100644 index a82966d3ee0..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentDuplication.tsx +++ /dev/null @@ -1,65 +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 * as React from 'react'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { formatMeasure } from 'sonar-ui-common/helpers/measures'; -import { MeasureHistory } from '../utils'; - -interface Props { - addSeparator: boolean; - measuresHistory: MeasureHistory[]; - tooltipIdx: number; -} - -export default function GraphsTooltipsContentDuplication({ - addSeparator, - measuresHistory, - tooltipIdx -}: Props) { - const duplicationDensity = measuresHistory.find( - measure => measure.metric === 'duplicated_lines_density' - ); - if (!duplicationDensity || !duplicationDensity.history[tooltipIdx]) { - return null; - } - const duplicationDensityValue = duplicationDensity.history[tooltipIdx].value; - if (!duplicationDensityValue) { - return null; - } - return ( - - {addSeparator && ( - - -
- - - )} - - - {formatMeasure(duplicationDensityValue, 'PERCENT')} - - {translate('metric.duplicated_lines_density.name')} - - - ); -} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentEvents.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentEvents.tsx deleted file mode 100644 index b81effd4697..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentEvents.tsx +++ /dev/null @@ -1,51 +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 * as React from 'react'; -import ProjectEventIcon from 'sonar-ui-common/components/icons/ProjectEventIcon'; -import { translate } from 'sonar-ui-common/helpers/l10n'; - -interface Props { - addSeparator: boolean; - events: T.AnalysisEvent[]; -} - -export default function GraphsTooltipsContentEvents({ addSeparator, events }: Props) { - return ( - - {addSeparator && ( - - -
- - - )} - - - {translate('events')}: - {events.map(event => ( - - - - ))} - - - - ); -} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentIssues.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentIssues.tsx deleted file mode 100644 index dc2a3c784ad..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentIssues.tsx +++ /dev/null @@ -1,60 +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 * as React from 'react'; -import ChartLegendIcon from 'sonar-ui-common/components/icons/ChartLegendIcon'; -import Rating from 'sonar-ui-common/components/ui/Rating'; -import { MeasureHistory } from '../utils'; - -interface Props { - index: number; - measuresHistory: MeasureHistory[]; - name: string; - tooltipIdx: number; - translatedName: string; - value: string; -} - -const METRIC_RATING: T.Dict = { - bugs: 'reliability_rating', - vulnerabilities: 'security_rating', - code_smells: 'sqale_rating' -}; - -export default function GraphsTooltipsContentIssues(props: Props) { - const rating = props.measuresHistory.find( - measure => measure.metric === METRIC_RATING[props.name] - ); - if (!rating || !rating.history[props.tooltipIdx]) { - return null; - } - const ratingValue = rating.history[props.tooltipIdx].value; - return ( - - - - - - {props.value} - {ratingValue && } - - {props.translatedName} - - ); -} diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.tsx deleted file mode 100644 index fc15730d9c3..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.tsx +++ /dev/null @@ -1,61 +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 * as React from 'react'; -import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'; -import ZoomTimeLine from 'sonar-ui-common/components/charts/ZoomTimeLine'; -import { hasHistoryData, Serie } from '../utils'; - -interface Props { - graphEndDate?: Date; - graphStartDate?: Date; - leakPeriodDate?: Date; - loading: boolean; - metricsType: string; - series: Serie[]; - showAreas?: boolean; - updateGraphZoom: (from?: Date, to?: Date) => void; -} - -export default function GraphsZoom(props: Props) { - if (props.loading || !hasHistoryData(props.series)) { - return null; - } - - return ( -
- - {({ width }) => ( - - )} - -
- ); -} 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/ProjectActivityGraphsHeader.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.tsx deleted file mode 100644 index 7c4fdea6230..00000000000 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.tsx +++ /dev/null @@ -1,72 +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 * as React from 'react'; -import Select from 'sonar-ui-common/components/controls/Select'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { GRAPH_TYPES, isCustomGraph } from '../utils'; -import AddGraphMetric from './forms/AddGraphMetric'; - -interface Props { - addCustomMetric: (metric: string) => void; - removeCustomMetric: (metric: string) => void; - graph: string; - metrics: T.Metric[]; - metricsTypeFilter?: string[]; - selectedMetrics: string[]; - updateGraph: (graphType: string) => void; -} - -export default class ProjectActivityGraphsHeader extends React.PureComponent { - handleGraphChange = (option: { value: string }) => { - if (option.value !== this.props.graph) { - this.props.updateGraph(option.value); - } - }; - - render() { - const selectOptions = GRAPH_TYPES.map(graph => ({ - label: translate('project_activity.graphs', graph), - value: graph - })); - - return ( -
- + {isCustomGraph(graph) && + addCustomMetric !== undefined && + removeCustomMetric !== undefined && ( + + )} + + ); + } +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx new file mode 100644 index 00000000000..d19242f24a7 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx @@ -0,0 +1,142 @@ +/* + * 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 { isEqual } from 'lodash'; +import * as React from 'react'; +import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; +import { GraphType, MeasureHistory, Serie } from '../../types/project-activity'; +import GraphHistory from './GraphHistory'; +import './styles.css'; +import { getSeriesMetricType, hasHistoryData, isCustomGraph } from './utils'; + +interface Props { + analyses: T.ParsedAnalysis[]; + graph: GraphType; + graphs: Serie[][]; + graphEndDate?: Date; + graphStartDate?: Date; + leakPeriodDate?: Date; + loading: boolean; + measuresHistory: MeasureHistory[]; + removeCustomMetric?: (metric: string) => void; + selectedDate?: Date; + series: Serie[]; + updateGraphZoom?: (from?: Date, to?: Date) => void; + updateSelectedDate?: (selectedDate?: Date) => void; +} + +interface State { + selectedDate?: Date; +} + +export default class GraphsHistory extends React.PureComponent { + 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 }); + } + } + + getSelectedDateEvents = () => { + const { selectedDate } = this.state; + const { analyses } = this.props; + if (analyses && selectedDate) { + const analysis = analyses.find(a => a.date.valueOf() === selectedDate.valueOf()); + if (analysis) { + return analysis.events; + } + } + return []; + }; + + updateTooltip = (selectedDate?: Date) => { + this.setState({ selectedDate }); + }; + + render() { + const { graph, loading, series } = this.props; + const isCustom = isCustomGraph(graph); + + if (loading) { + return ( +
+
+ +
+
+ ); + } + + if (!hasHistoryData(series)) { + return ( +
+
+ +
+ {translate( + isCustom + ? 'project_activity.graphs.custom.no_history' + : 'component_measures.no_history' + )} +
+
+
+ ); + } + const events = this.getSelectedDateEvents(); + const showAreas = [GraphType.coverage, GraphType.duplications].includes(graph); + return ( +
+ {this.props.graphs.map((graphSeries, idx) => ( + + ))} +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendCustom.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendCustom.tsx new file mode 100644 index 00000000000..aaf71573351 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendCustom.tsx @@ -0,0 +1,63 @@ +/* + * 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 * as React from 'react'; +import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { Serie } from '../../types/project-activity'; +import GraphsLegendItem from './GraphsLegendItem'; +import { hasDataValues } from './utils'; + +interface Props { + removeMetric: (metric: string) => void; + series: Serie[]; +} + +export default function GraphsLegendCustom({ removeMetric, series }: Props) { + return ( +
+ {series.map((serie, idx) => { + const hasData = hasDataValues(serie); + const legendItem = ( + + ); + if (!hasData) { + return ( + + {legendItem} + + ); + } + return ( + + {legendItem} + + ); + })} +
+ ); +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendItem.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendItem.tsx new file mode 100644 index 00000000000..cd0bcb9bc7a --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendItem.tsx @@ -0,0 +1,66 @@ +/* + * 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 * as classNames from 'classnames'; +import * as React from 'react'; +import { ClearButton } from 'sonar-ui-common/components/controls/buttons'; +import AlertWarnIcon from 'sonar-ui-common/components/icons/AlertWarnIcon'; +import ChartLegendIcon from 'sonar-ui-common/components/icons/ChartLegendIcon'; + +interface Props { + className?: string; + index: number; + metric: string; + name: string; + showWarning?: boolean; + removeMetric?: (metric: string) => void; +} + +export default class GraphsLegendItem extends React.PureComponent { + handleClick = () => { + if (this.props.removeMetric) { + this.props.removeMetric(this.props.metric); + } + }; + + render() { + const isActionable = this.props.removeMetric != null; + const legendClass = classNames( + { 'activity-graph-legend-actionable': isActionable }, + this.props.className + ); + return ( + + {this.props.showWarning ? ( + + ) : ( + + )} + {this.props.name} + {isActionable && ( + + )} + + ); + } +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendStatic.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendStatic.tsx new file mode 100644 index 00000000000..9d723ad7071 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendStatic.tsx @@ -0,0 +1,42 @@ +/* + * 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 * as React from 'react'; +import { Serie } from '../../types/project-activity'; +import GraphsLegendItem from './GraphsLegendItem'; + +interface Props { + series: Array>; +} + +export default function GraphsLegendStatic({ series }: Props) { + return ( +
+ {series.map((serie, idx) => ( + + ))} +
+ ); +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltips.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltips.tsx new file mode 100644 index 00000000000..5cf39c6296e --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltips.tsx @@ -0,0 +1,125 @@ +/* + * 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 * as React from 'react'; +import { Popup, PopupPlacement } from 'sonar-ui-common/components/ui/popups'; +import { isDefined } from 'sonar-ui-common/helpers/types'; +import { MeasureHistory, Serie } from '../../types/project-activity'; +import DateTimeFormatter from '../intl/DateTimeFormatter'; +import GraphsTooltipsContent from './GraphsTooltipsContent'; +import GraphsTooltipsContentCoverage from './GraphsTooltipsContentCoverage'; +import GraphsTooltipsContentDuplication from './GraphsTooltipsContentDuplication'; +import GraphsTooltipsContentEvents from './GraphsTooltipsContentEvents'; +import GraphsTooltipsContentIssues from './GraphsTooltipsContentIssues'; +import { DEFAULT_GRAPH } from './utils'; + +interface Props { + events: T.AnalysisEvent[]; + formatValue: (tick: number | string) => string; + graph: string; + graphWidth: number; + measuresHistory: MeasureHistory[]; + selectedDate: Date; + series: Serie[]; + tooltipIdx: number; + tooltipPos: number; +} + +const TOOLTIP_WIDTH = 250; + +export default class GraphsTooltips extends React.PureComponent { + renderContent() { + const { tooltipIdx } = this.props; + + return this.props.series.map((serie, idx) => { + const point = serie.data[tooltipIdx]; + if (!point || (!point.y && point.y !== 0)) { + return null; + } + if (this.props.graph === DEFAULT_GRAPH) { + return ( + + ); + } else { + return ( + + ); + } + }); + } + + render() { + const { events, measuresHistory, tooltipIdx } = this.props; + const top = 30; + let left = this.props.tooltipPos + 60; + let placement = PopupPlacement.RightTop; + if (left > this.props.graphWidth - TOOLTIP_WIDTH - 50) { + left -= TOOLTIP_WIDTH; + placement = PopupPlacement.LeftTop; + } + const tooltipContent = this.renderContent().filter(isDefined); + const addSeparator = tooltipContent.length > 0; + return ( + +
+
+ +
+ + {tooltipContent} + {this.props.graph === 'coverage' && ( + + )} + {this.props.graph === 'duplications' && ( + + )} + {events && events.length > 0 && ( + + )} +
+
+
+ ); + } +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContent.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContent.tsx new file mode 100644 index 00000000000..7d9363b8f65 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContent.tsx @@ -0,0 +1,40 @@ +/* + * 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 * as React from 'react'; +import ChartLegendIcon from 'sonar-ui-common/components/icons/ChartLegendIcon'; + +interface Props { + name: string; + index: number; + translatedName: string; + value: string; +} + +export default function GraphsTooltipsContent({ name, index, translatedName, value }: Props) { + return ( + + + + + {value} + {translatedName} + + ); +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentCoverage.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentCoverage.tsx new file mode 100644 index 00000000000..a3eaf9e15d2 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentCoverage.tsx @@ -0,0 +1,70 @@ +/* + * 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 * as React from 'react'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { formatMeasure } from 'sonar-ui-common/helpers/measures'; +import { MeasureHistory } from '../../types/project-activity'; + +export interface GraphsTooltipsContentCoverageProps { + addSeparator: boolean; + measuresHistory: MeasureHistory[]; + tooltipIdx: number; +} + +export default function GraphsTooltipsContentCoverage({ + addSeparator, + measuresHistory, + tooltipIdx +}: GraphsTooltipsContentCoverageProps) { + const uncovered = measuresHistory.find(measure => measure.metric === 'uncovered_lines'); + const coverage = measuresHistory.find(measure => measure.metric === 'coverage'); + if (!uncovered || !uncovered.history[tooltipIdx] || !coverage || !coverage.history[tooltipIdx]) { + return null; + } + const uncoveredValue = uncovered.history[tooltipIdx].value; + const coverageValue = coverage.history[tooltipIdx].value; + return ( + + {addSeparator && ( + + +
+ + + )} + {uncoveredValue && ( + + + {formatMeasure(uncoveredValue, 'SHORT_INT')} + + {translate('metric.uncovered_lines.name')} + + )} + {coverageValue && ( + + + {formatMeasure(coverageValue, 'PERCENT')} + + {translate('metric.coverage.name')} + + )} + + ); +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentDuplication.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentDuplication.tsx new file mode 100644 index 00000000000..956b2fe3b62 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentDuplication.tsx @@ -0,0 +1,63 @@ +/* + * 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 * as React from 'react'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { formatMeasure } from 'sonar-ui-common/helpers/measures'; +import { MeasureHistory } from '../../types/project-activity'; + +export interface GraphsTooltipsContentDuplicationProps { + addSeparator: boolean; + measuresHistory: MeasureHistory[]; + tooltipIdx: number; +} + +export default function GraphsTooltipsContentDuplication({ + addSeparator, + measuresHistory, + tooltipIdx +}: GraphsTooltipsContentDuplicationProps) { + const duplicationDensity = measuresHistory.find( + measure => measure.metric === 'duplicated_lines_density' + ); + if (!duplicationDensity || !duplicationDensity.history[tooltipIdx]) { + return null; + } + const duplicationDensityValue = duplicationDensity.history[tooltipIdx].value; + if (!duplicationDensityValue) { + return null; + } + return ( + + {addSeparator && ( + + +
+ + + )} + + + {formatMeasure(duplicationDensityValue, 'PERCENT')} + + {translate('metric.duplicated_lines_density.name')} + + + ); +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentEvents.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentEvents.tsx new file mode 100644 index 00000000000..36ff950de11 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentEvents.tsx @@ -0,0 +1,54 @@ +/* + * 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 classNames from 'classnames'; +import * as React from 'react'; +import ProjectEventIcon from 'sonar-ui-common/components/icons/ProjectEventIcon'; +import { translate } from 'sonar-ui-common/helpers/l10n'; + +interface Props { + addSeparator: boolean; + events: T.AnalysisEvent[]; +} + +export default function GraphsTooltipsContentEvents({ addSeparator, events }: Props) { + return ( + + {addSeparator && ( + + +
+ + + )} + + + {translate('events')}: + {events.map(event => ( + + + + ))} + + + + ); +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentIssues.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentIssues.tsx new file mode 100644 index 00000000000..11f96a5b97e --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentIssues.tsx @@ -0,0 +1,60 @@ +/* + * 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 * as React from 'react'; +import ChartLegendIcon from 'sonar-ui-common/components/icons/ChartLegendIcon'; +import Rating from 'sonar-ui-common/components/ui/Rating'; +import { MeasureHistory } from '../../types/project-activity'; + +export interface GraphsTooltipsContentIssuesProps { + index: number; + measuresHistory: MeasureHistory[]; + name: string; + tooltipIdx: number; + translatedName: string; + value: string; +} + +const METRIC_RATING: T.Dict = { + bugs: 'reliability_rating', + vulnerabilities: 'security_rating', + code_smells: 'sqale_rating' +}; + +export default function GraphsTooltipsContentIssues(props: GraphsTooltipsContentIssuesProps) { + const rating = props.measuresHistory.find( + measure => measure.metric === METRIC_RATING[props.name] + ); + if (!rating || !rating.history[props.tooltipIdx]) { + return null; + } + const ratingValue = rating.history[props.tooltipIdx].value; + return ( + + + + + + {props.value} + {ratingValue && } + + {props.translatedName} + + ); +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/GraphsZoom.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsZoom.tsx new file mode 100644 index 00000000000..2777b68186b --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsZoom.tsx @@ -0,0 +1,62 @@ +/* + * 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 * as React from 'react'; +import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'; +import ZoomTimeLine from 'sonar-ui-common/components/charts/ZoomTimeLine'; +import { Serie } from '../../types/project-activity'; +import { hasHistoryData } from './utils'; + +interface Props { + graphEndDate?: Date; + graphStartDate?: Date; + leakPeriodDate?: Date; + loading: boolean; + metricsType: string; + series: Serie[]; + showAreas?: boolean; + updateGraphZoom: (from?: Date, to?: Date) => void; +} + +export default function GraphsZoom(props: Props) { + if (props.loading || !hasHistoryData(props.series)) { + return null; + } + + return ( +
+ + {({ width }) => ( + + )} + +
+ ); +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetric-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetric-test.tsx new file mode 100644 index 00000000000..d26e8e5f70c --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetric-test.tsx @@ -0,0 +1,40 @@ +/* + * 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 { mockMetric } from '../../../helpers/testMocks'; +import AddGraphMetric from '../AddGraphMetric'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetricPopup-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetricPopup-test.tsx new file mode 100644 index 00000000000..a555db13bcb --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetricPopup-test.tsx @@ -0,0 +1,64 @@ +/* + * 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 MultiSelect from '../../common/MultiSelect'; +import AddGraphMetricPopup, { AddGraphMetricPopupProps } from '../AddGraphMetricPopup'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); +}); + +it('should render correctly whith 6+ selected elements', () => { + const selectedElements = ['1', '2', '3', '4', '5', '6']; + expect(shallowRender({ selectedElements })).toMatchSnapshot(); +}); + +it('should render correctly with type filter', () => { + const metricsTypeFilter = ['filter1', 'filter2']; + expect(shallowRender({ metricsTypeFilter })).toMatchSnapshot(); +}); + +it('should prevent selection of unknown element', () => { + const elements = ['1', '2', '3']; + const onSelect = jest.fn(); + const wrapper = shallowRender({ elements, onSelect }); + wrapper + .find(MultiSelect) + .props() + .onSelect('unknown'); + + expect(onSelect).not.toHaveBeenCalled(); +}); + +function shallowRender(overrides: Partial = {}) { + return shallow( + element} + selectedElements={[]} + {...overrides} + /> + ); +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphHistory-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphHistory-test.tsx new file mode 100644 index 00000000000..5d2849dbd15 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphHistory-test.tsx @@ -0,0 +1,56 @@ +/* + * 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 GraphHistory from '../GraphHistory'; +import { DEFAULT_GRAPH } from '../utils'; + +const SERIES = [ + { + name: 'bugs', + translatedName: 'metric.bugs.name', + data: [ + { x: parseDate('2016-10-27T16:33:50+0200'), y: 5 }, + { x: parseDate('2016-10-27T12:21:15+0200'), y: 16 }, + { x: parseDate('2016-10-26T12:17:29+0200'), y: 12 } + ], + type: 'INT' + } +]; + +const DEFAULT_PROPS: GraphHistory['props'] = { + events: [], + graph: DEFAULT_GRAPH, + leakPeriodDate: parseDate('2017-05-16T13:50:02+0200'), + isCustom: false, + measuresHistory: [], + metricsType: 'INT', + removeCustomMetric: () => {}, + showAreas: true, + series: SERIES, + updateGraphZoom: () => {}, + updateSelectedDate: () => {}, + updateTooltip: () => {} +}; + +it('should correctly render a graph', () => { + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsHistory-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsHistory-test.tsx new file mode 100644 index 00000000000..4b0a219746a --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsHistory-test.tsx @@ -0,0 +1,119 @@ +/* + * 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. + */ +/* eslint-disable sonarjs/no-duplicate-string */ +import { shallow } from 'enzyme'; +import * as React from 'react'; +import { parseDate } from 'sonar-ui-common/helpers/dates'; +import GraphsHistory from '../GraphsHistory'; +import { DEFAULT_GRAPH } from '../utils'; + +const ANALYSES = [ + { + key: 'A1', + date: parseDate('2016-10-27T16:33:50+0200'), + events: [ + { + key: 'E1', + category: 'VERSION', + name: '6.5-SNAPSHOT' + } + ] + }, + { + key: 'A2', + date: parseDate('2016-10-27T12:21:15+0200'), + events: [] + }, + { + key: 'A3', + date: parseDate('2016-10-26T12:17:29+0200'), + events: [ + { + key: 'E2', + category: 'OTHER', + name: 'foo' + }, + { + key: 'E3', + category: 'VERSION', + name: '6.4' + } + ] + } +]; + +const SERIES = [ + { + name: 'bugs', + translatedName: 'metric.bugs.name', + data: [ + { x: parseDate('2016-10-27T16:33:50+0200'), y: 5 }, + { x: parseDate('2016-10-27T12:21:15+0200'), y: 16 }, + { x: parseDate('2016-10-26T12:17:29+0200'), y: 12 } + ], + type: 'INT' + } +]; + +const DEFAULT_PROPS: GraphsHistory['props'] = { + analyses: ANALYSES, + graph: DEFAULT_GRAPH, + graphs: [SERIES], + leakPeriodDate: parseDate('2017-05-16T13:50:02+0200'), + loading: false, + measuresHistory: [], + removeCustomMetric: () => {}, + series: SERIES, + updateGraphZoom: () => {}, + updateSelectedDate: () => {} +}; + +it('should correctly render a graph', () => { + expect(shallow()).toMatchSnapshot(); +}); + +it('should correctly render multiple graphs', () => { + expect(shallow()).toMatchSnapshot(); +}); + +it('should show a loading view instead of the graph', () => { + expect( + shallow().find('DeferredSpinner') + ).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/components/activity-graph/__tests__/GraphsLegendCustom-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendCustom-test.tsx new file mode 100644 index 00000000000..5b213e8141f --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendCustom-test.tsx @@ -0,0 +1,48 @@ +/* + * 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 GraphsLegendCustom from '../GraphsLegendCustom'; + +const SERIES = [ + { + name: 'bugs', + translatedName: 'Bugs', + data: [{ x: parseDate('2017-05-16T13:50:02+0200'), y: 1 }], + type: 'INT' + }, + { + name: 'my_metric', + translatedName: 'My Metric', + data: [{ x: parseDate('2017-05-16T13:50:02+0200'), y: 1 }], + type: 'INT' + }, + { + name: 'foo', + translatedName: 'Foo', + data: [], + type: 'INT' + } +]; + +it('should render correctly the list of series', () => { + expect(shallow( {}} series={SERIES} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendItem-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendItem-test.tsx new file mode 100644 index 00000000000..6553a9f90d4 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendItem-test.tsx @@ -0,0 +1,51 @@ +/* + * 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 { ClearButton } from 'sonar-ui-common/components/controls/buttons'; +import { click } from 'sonar-ui-common/helpers/testUtils'; +import GraphsLegendItem from '../GraphsLegendItem'; + +it('should render correctly a legend', () => { + expect(shallowRender()).toMatchSnapshot('default'); + expect( + shallowRender({ + className: 'myclass', + index: 1, + metric: 'foo', + name: 'Foo', + removeMetric: jest.fn() + }) + ).toMatchSnapshot('with legend'); + expect(shallowRender({ showWarning: true })).toMatchSnapshot('with warning'); +}); + +it('should correctly handle clicks', () => { + const removeMetric = jest.fn(); + const wrapper = shallowRender({ removeMetric }); + click(wrapper.find(ClearButton)); + expect(removeMetric).toBeCalledWith('bugs'); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendStatic-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendStatic-test.tsx new file mode 100644 index 00000000000..a0492576ddf --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendStatic-test.tsx @@ -0,0 +1,31 @@ +/* + * 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 GraphsLegendStatic from '../GraphsLegendStatic'; + +const SERIES = [ + { name: 'bugs', translatedName: 'Bugs', data: [] }, + { name: 'code_smells', translatedName: 'Code Smells', data: [] } +]; + +it('should render correctly the list of series', () => { + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltips-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltips-test.tsx new file mode 100644 index 00000000000..57c3f04d2b7 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltips-test.tsx @@ -0,0 +1,108 @@ +/* + * 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. + */ +/* eslint-disable sonarjs/no-duplicate-string */ +import { shallow } from 'enzyme'; +import * as React from 'react'; +import { parseDate } from 'sonar-ui-common/helpers/dates'; +import GraphsTooltips from '../GraphsTooltips'; +import { DEFAULT_GRAPH } from '../utils'; + +const SERIES_ISSUES = [ + { + name: 'bugs', + translatedName: 'Bugs', + data: [ + { + x: parseDate('2011-10-01T22:01:00.000Z'), + y: 3 + }, + { + x: parseDate('2011-10-25T10:27:41.000Z'), + y: 0 + } + ], + type: 'INT' + }, + { + name: 'code_smells', + translatedName: 'Code Smells', + data: [ + { + x: parseDate('2011-10-01T22:01:00.000Z'), + y: 18 + }, + { + x: parseDate('2011-10-25T10:27:41.000Z'), + y: 15 + } + ], + type: 'INT' + }, + { + name: 'vulnerabilities', + translatedName: 'Vulnerabilities', + data: [ + { + x: parseDate('2011-10-01T22:01:00.000Z'), + y: 0 + }, + { + x: parseDate('2011-10-25T10:27:41.000Z'), + y: 1 + } + ], + type: 'INT' + } +]; + +const DEFAULT_PROPS: GraphsTooltips['props'] = { + events: [], + formatValue: val => 'Formated.' + val, + graph: DEFAULT_GRAPH, + graphWidth: 500, + measuresHistory: [], + selectedDate: parseDate('2011-10-01T22:01:00.000Z'), + series: SERIES_ISSUES, + tooltipIdx: 0, + tooltipPos: 666 +}; + +it('should render correctly for issues graphs', () => { + expect(shallow()).toMatchSnapshot(); +}); + +it('should render correctly for random graphs', () => { + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); + +it('should not add separators if not needed', () => { + expect( + shallow() + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContent-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContent-test.tsx new file mode 100644 index 00000000000..cc8fb4689b0 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContent-test.tsx @@ -0,0 +1,33 @@ +/* + * 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 GraphsTooltipsContent from '../GraphsTooltipsContent'; + +const DEFAULT_PROPS = { + index: 1, + name: 'code_smells', + translatedName: 'Code Smells', + value: '1.2k' +}; + +it('should render correctly', () => { + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentCoverage-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentCoverage-test.tsx new file mode 100644 index 00000000000..a91d521c9c6 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentCoverage-test.tsx @@ -0,0 +1,65 @@ +/* + * 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. + */ +/* eslint-disable sonarjs/no-duplicate-string */ +import { shallow } from 'enzyme'; +import * as React from 'react'; +import { parseDate } from 'sonar-ui-common/helpers/dates'; +import GraphsTooltipsContentCoverage, { + GraphsTooltipsContentCoverageProps +} from '../GraphsTooltipsContentCoverage'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot('default'); + expect(shallowRender({ addSeparator: true })).toMatchSnapshot('with separator'); + expect(shallowRender({ tooltipIdx: -1 }).type()).toBeNull(); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentDuplication-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentDuplication-test.tsx new file mode 100644 index 00000000000..93313bb60ed --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentDuplication-test.tsx @@ -0,0 +1,51 @@ +/* + * 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 GraphsTooltipsContentDuplication, { + GraphsTooltipsContentDuplicationProps +} from '../GraphsTooltipsContentDuplication'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot('default'); + expect(shallowRender({ addSeparator: true })).toMatchSnapshot('with separator'); + expect(shallowRender({ tooltipIdx: -1 }).type()).toBeNull(); + expect(shallowRender({ measuresHistory: [] }).type()).toBeNull(); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentEvents-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentEvents-test.tsx new file mode 100644 index 00000000000..c797e07d434 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentEvents-test.tsx @@ -0,0 +1,33 @@ +/* + * 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 GraphsTooltipsContentEvents from '../GraphsTooltipsContentEvents'; + +const EVENTS = [ + { key: '1', category: 'VERSION', name: '6.5' }, + { key: '2', category: 'OTHER', name: 'Foo' } +]; + +it('should render correctly', () => { + expect( + shallow() + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentIssues-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentIssues-test.tsx new file mode 100644 index 00000000000..f2a064c27f5 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentIssues-test.tsx @@ -0,0 +1,59 @@ +/* + * 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, { + GraphsTooltipsContentIssuesProps +} from '../GraphsTooltipsContentIssues'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot('default'); + expect(shallowRender({ tooltipIdx: -1 }).type()).toBeNull(); +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/AddGraphMetric-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/AddGraphMetric-test.tsx.snap new file mode 100644 index 00000000000..7873de9ecd6 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/AddGraphMetric-test.tsx.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + + } +> + + +`; diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/AddGraphMetricPopup-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/AddGraphMetricPopup-test.tsx.snap new file mode 100644 index 00000000000..f914a1314a5 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/AddGraphMetricPopup-test.tsx.snap @@ -0,0 +1,90 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +
+ +
+`; + +exports[`should render correctly whith 6+ selected elements 1`] = ` +
+ + project_activity.graphs.custom.add_metric_info + + } + listSize={0} + onSearch={[MockFunction]} + onSelect={[Function]} + onUnselect={[MockFunction]} + placeholder="search.search_for_tags" + renderLabel={[Function]} + selectedElements={ + Array [ + "1", + "2", + "3", + "4", + "5", + "6", + ] + } + validateSearchInput={[Function]} + /> +
+`; + +exports[`should render correctly with type filter 1`] = ` +
+ + project_activity.graphs.custom.type_x_message.metric.type.filter1, metric.type.filter2 + + } + listSize={0} + onSearch={[MockFunction]} + onSelect={[Function]} + onUnselect={[MockFunction]} + placeholder="search.search_for_tags" + renderLabel={[Function]} + selectedElements={Array []} + validateSearchInput={[Function]} + /> +
+`; diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphHistory-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphHistory-test.tsx.snap new file mode 100644 index 00000000000..cb92ba1524d --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphHistory-test.tsx.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should correctly render a graph 1`] = ` +
+ +
+ + + +
+
+`; diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsHistory-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsHistory-test.tsx.snap new file mode 100644 index 00000000000..6c121cd5ca5 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsHistory-test.tsx.snap @@ -0,0 +1,180 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +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/components/activity-graph/__tests__/__snapshots__/GraphsLegendCustom-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendCustom-test.tsx.snap new file mode 100644 index 00000000000..9dede77b48a --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendCustom-test.tsx.snap @@ -0,0 +1,48 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly the list of series 1`] = ` +
+ + + + + + + + + + + +
+`; diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendItem-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendItem-test.tsx.snap new file mode 100644 index 00000000000..d374734cb54 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendItem-test.tsx.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly a legend: default 1`] = ` + + + + Bugs + + +`; + +exports[`should render correctly a legend: with legend 1`] = ` + + + + Foo + + + +`; + +exports[`should render correctly a legend: with warning 1`] = ` + + + + Bugs + + +`; diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendStatic-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendStatic-test.tsx.snap new file mode 100644 index 00000000000..d1198d5f498 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendStatic-test.tsx.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly the list of series 1`] = ` +
+ + +
+`; diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltips-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltips-test.tsx.snap new file mode 100644 index 00000000000..d90007b9ca1 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltips-test.tsx.snap @@ -0,0 +1,149 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should not add separators if not needed 1`] = ` + +
+
+ +
+ + + +
+
+
+`; + +exports[`should render correctly for issues graphs 1`] = ` + +
+
+ +
+ + + + + + +
+
+
+`; + +exports[`should render correctly for random graphs 1`] = ` + +
+
+ +
+ + + + + + +
+
+
+`; diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContent-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContent-test.tsx.snap new file mode 100644 index 00000000000..4bfd687cf88 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContent-test.tsx.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + + + + + + 1.2k + + + Code Smells + + +`; diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentCoverage-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentCoverage-test.tsx.snap new file mode 100644 index 00000000000..f83e91ef9f5 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentCoverage-test.tsx.snap @@ -0,0 +1,71 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly: default 1`] = ` + + + + 10short_number_suffix.k + + + metric.uncovered_lines.name + + + + + 80.3% + + + metric.coverage.name + + + +`; + +exports[`should render correctly: with separator 1`] = ` + + + +
+ + + + + 10short_number_suffix.k + + + metric.uncovered_lines.name + + + + + 80.3% + + + metric.coverage.name + + + +`; diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentDuplication-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentDuplication-test.tsx.snap new file mode 100644 index 00000000000..30fc8c8118a --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentDuplication-test.tsx.snap @@ -0,0 +1,45 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly: default 1`] = ` + + + + 10,245.0% + + + metric.duplicated_lines_density.name + + + +`; + +exports[`should render correctly: with separator 1`] = ` + + + +
+ + + + + 10,245.0% + + + metric.duplicated_lines_density.name + + + +`; diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.tsx.snap new file mode 100644 index 00000000000..b77d2327dac --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.tsx.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + + + +
+ + + + + + events + : + + + + + + + + + + +`; diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap new file mode 100644 index 00000000000..43f9d71b1dc --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly: default 1`] = ` + + + + + + + 1.2k + + + + + Bugs + + +`; diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/utils-test.ts.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/utils-test.ts.snap new file mode 100644 index 00000000000..13e84bb9557 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/utils-test.ts.snap @@ -0,0 +1,80 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generateCoveredLinesMetric should correctly generate covered lines metric: empty data 1`] = ` +Object { + "data": Array [], + "name": "covered_lines", + "translatedName": "project_activity.custom_metric.covered_lines", + "type": "INT", +} +`; + +exports[`generateCoveredLinesMetric should correctly generate covered lines metric: with data 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[`getGraphTypes should correctly return the graph types 1`] = ` +Array [ + "issues", + "coverage", + "duplications", + "custom", +] +`; + +exports[`getGraphTypes should correctly return the graph types 2`] = ` +Array [ + "issues", + "coverage", + "duplications", +] +`; diff --git a/server/sonar-web/src/main/js/components/activity-graph/__tests__/utils-test.ts b/server/sonar-web/src/main/js/components/activity-graph/__tests__/utils-test.ts new file mode 100644 index 00000000000..06a3fd398f7 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/utils-test.ts @@ -0,0 +1,200 @@ +/* + * 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. + */ +/* eslint-disable sonarjs/no-duplicate-string */ +import * as dates from 'sonar-ui-common/helpers/dates'; +import { MetricKey } from '../../../types/metrics'; +import { GraphType, Serie } from '../../../types/project-activity'; +import * as utils from '../utils'; + +jest.mock('date-fns/start_of_day', () => + jest.fn(date => { + const startDay = new Date(date); + startDay.setUTCHours(0, 0, 0, 0); + return startDay; + }) +); + +const HISTORY = [ + { + metric: MetricKey.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: MetricKey.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: MetricKey.uncovered_lines, name: 'Uncovered Lines', type: 'INT' }, + { id: '2', key: MetricKey.lines_to_cover, name: 'Line to Cover', type: 'PERCENT' } +]; + +const SERIE: Serie = { + data: [ + { x: dates.parseDate('2017-04-27T08:21:32.000Z'), y: 2 }, + { x: dates.parseDate('2017-04-28T08:21:32.000Z'), y: 2 } + ], + name: 'foo', + translatedName: 'Foo', + type: 'PERCENT' +}; + +describe('generateCoveredLinesMetric', () => { + it('should correctly generate covered lines metric', () => { + expect(utils.generateCoveredLinesMetric(HISTORY[1], HISTORY)).toMatchSnapshot('with data'); + expect(utils.generateCoveredLinesMetric(HISTORY[1], [])).toMatchSnapshot('empty data'); + }); +}); + +describe('generateSeries', () => { + it('should correctly generate the series', () => { + expect( + utils.generateSeries(HISTORY, GraphType.coverage, METRICS, [ + MetricKey.uncovered_lines, + MetricKey.lines_to_cover + ]) + ).toMatchSnapshot(); + }); + it('should correctly handle non-existent data', () => { + expect(utils.generateSeries(HISTORY, GraphType.coverage, METRICS, [])).toEqual([]); + }); +}); + +describe('getDisplayedHistoryMetrics', () => { + const customMetrics = ['foo', 'bar']; + it('should return only displayed metrics on the graph', () => { + expect(utils.getDisplayedHistoryMetrics(utils.DEFAULT_GRAPH, [])).toEqual([ + MetricKey.bugs, + MetricKey.code_smells, + MetricKey.vulnerabilities + ]); + expect(utils.getDisplayedHistoryMetrics(GraphType.coverage, customMetrics)).toEqual([ + MetricKey.lines_to_cover, + MetricKey.uncovered_lines + ]); + }); + it('should return all custom metrics for the custom graph', () => { + expect(utils.getDisplayedHistoryMetrics(GraphType.custom, customMetrics)).toEqual( + customMetrics + ); + }); +}); + +describe('getHistoryMetrics', () => { + const customMetrics = ['foo', 'bar']; + it('should return all metrics', () => { + expect(utils.getHistoryMetrics(utils.DEFAULT_GRAPH, [])).toEqual([ + MetricKey.bugs, + MetricKey.code_smells, + MetricKey.vulnerabilities, + MetricKey.reliability_rating, + MetricKey.security_rating, + MetricKey.sqale_rating + ]); + expect(utils.getHistoryMetrics(GraphType.coverage, customMetrics)).toEqual([ + MetricKey.lines_to_cover, + MetricKey.uncovered_lines, + GraphType.coverage + ]); + expect(utils.getHistoryMetrics(GraphType.custom, customMetrics)).toEqual(customMetrics); + }); +}); + +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(); + }); +}); + +describe('getGraphTypes', () => { + it('should correctly return the graph types', () => { + expect(utils.getGraphTypes()).toMatchSnapshot(); + expect(utils.getGraphTypes(true)).toMatchSnapshot(); + }); +}); + +describe('hasDataValues', () => { + it('should check for data value', () => { + expect(utils.hasDataValues(SERIE)).toBe(true); + expect(utils.hasDataValues({ ...SERIE, data: [] })).toBe(false); + }); +}); + +describe('getSeriesMetricType', () => { + it('should return the correct type', () => { + expect(utils.getSeriesMetricType([SERIE])).toBe('PERCENT'); + expect(utils.getSeriesMetricType([])).toBe('INT'); + }); +}); + +describe('hasHistoryDataValue', () => { + it('should return the correct type', () => { + expect(utils.hasHistoryDataValue([SERIE])).toBe(true); + expect(utils.hasHistoryDataValue([])).toBe(false); + }); +}); diff --git a/server/sonar-web/src/main/js/components/activity-graph/styles.css b/server/sonar-web/src/main/js/components/activity-graph/styles.css new file mode 100644 index 00000000000..277d3084262 --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/styles.css @@ -0,0 +1,69 @@ +/* + * 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. + */ +.activity-graph-container { + padding: 10px 0; +} + +.activity-graph-tooltip { + padding: var(--gridSize); +} + +.activity-graph-tooltip-line { + height: 20px; +} + +.activity-graph-tooltip-line + .activity-graph-tooltip-line { + padding-top: calc(var(--gridSize) / 2); +} + +.activity-graph-tooltip-issues-line { + height: 26px; + padding-bottom: calc(var(--gridSize) / 2); +} + +.activity-graph-tooltip-separator { + padding-left: calc(2 * var(--gridSize)); + padding-right: calc(2 * var(--gridSize)); +} + +.activity-graph-tooltip-separator hr { + margin-top: var(--gridSize); + margin-bottom: var(--gridSize); +} + +.activity-graph-tooltip-title, +.activity-graph-tooltip-value { + font-weight: bold; +} + +.activity-graph-legends { + flex-grow: 0; + padding-bottom: calc(2 * var(--gridSize)); + text-align: center; +} + +.activity-graph-legend-actionable { + display: inline-block; + padding: calc(var(--gridSize) / 2) var(--gridSize) calc(var(--gridSize) / 2) + calc(1.5 * var(--gridSize)); + border-width: 1px; + border-style: solid; + border-radius: calc(1.5 * var(--gridSize)); +} diff --git a/server/sonar-web/src/main/js/components/activity-graph/utils.ts b/server/sonar-web/src/main/js/components/activity-graph/utils.ts new file mode 100644 index 00000000000..149bc0f301f --- /dev/null +++ b/server/sonar-web/src/main/js/components/activity-graph/utils.ts @@ -0,0 +1,166 @@ +/* + * 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 { chunk, flatMap, groupBy, sortBy } from 'lodash'; +import { getLocalizedMetricName, translate } from 'sonar-ui-common/helpers/l10n'; +import { get, save } from 'sonar-ui-common/helpers/storage'; +import { localizeMetric } from '../../helpers/measures'; +import { MetricKey } from '../../types/metrics'; +import { GraphType, MeasureHistory, Serie } from '../../types/project-activity'; + +export const DEFAULT_GRAPH = GraphType.issues; + +const GRAPHS_METRICS_DISPLAYED: T.Dict = { + [GraphType.issues]: [MetricKey.bugs, MetricKey.code_smells, MetricKey.vulnerabilities], + [GraphType.coverage]: [MetricKey.lines_to_cover, MetricKey.uncovered_lines], + [GraphType.duplications]: [MetricKey.ncloc, MetricKey.duplicated_lines] +}; + +const GRAPHS_METRICS: T.Dict = { + [GraphType.issues]: GRAPHS_METRICS_DISPLAYED[GraphType.issues].concat([ + MetricKey.reliability_rating, + MetricKey.security_rating, + MetricKey.sqale_rating + ]), + [GraphType.coverage]: [...GRAPHS_METRICS_DISPLAYED[GraphType.coverage], MetricKey.coverage], + [GraphType.duplications]: [ + ...GRAPHS_METRICS_DISPLAYED[GraphType.duplications], + MetricKey.duplicated_lines_density + ] +}; + +export function isCustomGraph(graph: GraphType) { + return graph === GraphType.custom; +} + +export function getGraphTypes(ignoreCustom = false) { + const graphs = [GraphType.issues, GraphType.coverage, GraphType.duplications]; + return ignoreCustom ? graphs : [...graphs, GraphType.custom]; +} + +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 getSeriesMetricType(series: Serie[]) { + return series.length > 0 ? series[0].type : 'INT'; +} + +export function getDisplayedHistoryMetrics(graph: GraphType, customMetrics: string[]) { + return isCustomGraph(graph) ? customMetrics : GRAPHS_METRICS_DISPLAYED[graph]; +} + +export function getHistoryMetrics(graph: GraphType, customMetrics: string[]) { + return isCustomGraph(graph) ? customMetrics : GRAPHS_METRICS[graph]; +} + +export function hasHistoryDataValue(series: Serie[]) { + return series.some(serie => serie.data && serie.data.length > 1 && hasDataValues(serie)); +} + +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 generateCoveredLinesMetric( + uncoveredLines: MeasureHistory, + measuresHistory: MeasureHistory[] +) { + const linesToCover = measuresHistory.find(measure => measure.metric === MetricKey.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' + }; +} + +export function generateSeries( + measuresHistory: MeasureHistory[], + graph: GraphType, + metrics: T.Metric[] | T.Dict, + displayedMetrics: string[] +): Serie[] { + if (displayedMetrics.length <= 0 || measuresHistory === undefined) { + return []; + } + return sortBy( + measuresHistory + .filter(measure => displayedMetrics.indexOf(measure.metric) >= 0) + .map(measure => { + if (measure.metric === MetricKey.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) : localizeMetric(measure.metric), + type: metric ? metric.type : 'INT' + }; + }), + serie => + displayedMetrics.indexOf(serie.name === 'covered_lines' ? 'uncovered_lines' : serie.name) + ); +} + +export function saveActivityGraph( + namespace: string, + project: string, + graph: GraphType, + metrics: string[] = [] +) { + save(namespace, graph, project); + if (isCustomGraph(graph)) { + save(`${namespace}.custom`, metrics.join(','), project); + } +} + +export function getActivityGraph( + namespace: string, + project: string +): { graph: GraphType; customGraphs: string[] } { + const customGraphs = get(`${namespace}.custom`, project); + return { + graph: (get(namespace, project) as GraphType) || DEFAULT_GRAPH, + customGraphs: customGraphs ? customGraphs.split(',') : [] + }; +} + +function findMetric(key: string, metrics: T.Metric[] | T.Dict) { + if (Array.isArray(metrics)) { + return metrics.find(metric => metric.key === key); + } + return metrics[key]; +} diff --git a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.tsx b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.tsx deleted file mode 100644 index 610cf84184c..00000000000 --- a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.tsx +++ /dev/null @@ -1,201 +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 { minBy } from 'lodash'; -import * as React from 'react'; -import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'; -import AdvancedTimeline from 'sonar-ui-common/components/charts/AdvancedTimeline'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { formatMeasure } from 'sonar-ui-common/helpers/measures'; -import { - DEFAULT_GRAPH, - generateSeries, - getDisplayedHistoryMetrics, - getProjectActivityGraph, - getSeriesMetricType, - hasHistoryDataValue, - Serie, - splitSeriesInGraphs -} from '../../apps/projectActivity/utils'; -import { getBranchLikeQuery } from '../../helpers/branch-like'; -import { getShortType } from '../../helpers/measures'; -import { BranchLike } from '../../types/branch-like'; -import { Router, withRouter } from '../hoc/withRouter'; -import PreviewGraphTooltips from './PreviewGraphTooltips'; - -interface History { - [x: string]: Array<{ date: Date; value?: string }>; -} - -interface Props { - branchLike?: BranchLike; - history?: History; - metrics: T.Dict; - project: string; - renderWhenEmpty?: () => React.ReactNode; - router: Pick; -} - -interface State { - customMetrics: string[]; - graph: string; - selectedDate?: Date; - series: Serie[]; - tooltipIdx?: number; - tooltipXPos?: number; -} - -const GRAPH_PADDING = [4, 0, 4, 0]; -const MAX_GRAPH_NB = 1; -const MAX_SERIES_PER_GRAPH = 3; - -class PreviewGraph extends React.PureComponent { - constructor(props: Props) { - super(props); - const { graph, customGraphs: customMetrics } = getProjectActivityGraph(props.project); - const series = splitSeriesInGraphs( - this.getSeries(props.history, graph, customMetrics, props.metrics), - MAX_GRAPH_NB, - MAX_SERIES_PER_GRAPH - ); - this.state = { - customMetrics, - graph, - series: series.length > 0 ? series[0] : [] - }; - } - - componentDidUpdate(prevProps: Props) { - if (prevProps.history !== this.props.history || prevProps.metrics !== this.props.metrics) { - const { graph, customGraphs: customMetrics } = getProjectActivityGraph(this.props.project); - const series = splitSeriesInGraphs( - this.getSeries(this.props.history, graph, customMetrics, this.props.metrics), - MAX_GRAPH_NB, - MAX_SERIES_PER_GRAPH - ); - this.setState({ - customMetrics, - graph, - series: series.length > 0 ? series[0] : [] - }); - } - } - - formatValue = (tick: number | string) => { - return formatMeasure(tick, getShortType(getSeriesMetricType(this.state.series))); - }; - - getDisplayedMetrics = (graph: string, customMetrics: string[]) => { - const metrics = getDisplayedHistoryMetrics(graph, customMetrics); - if (!metrics || metrics.length <= 0) { - return getDisplayedHistoryMetrics(DEFAULT_GRAPH, customMetrics); - } - return metrics; - }; - - getSeries = ( - history: History | undefined, - graph: string, - customMetrics: string[], - metrics: T.Dict - ) => { - const myHistory = history; - if (!myHistory) { - return []; - } - const displayedMetrics = this.getDisplayedMetrics(graph, customMetrics); - const firstValid = minBy( - displayedMetrics.map(metric => myHistory[metric].find(p => p.value !== undefined)), - 'date' - ); - const measureHistory = displayedMetrics.map(metric => ({ - metric, - history: firstValid - ? myHistory[metric].filter(p => p.date >= firstValid.date) - : myHistory[metric] - })); - return generateSeries(measureHistory, graph, metrics, displayedMetrics); - }; - - handleClick = () => { - this.props.router.push({ - pathname: '/project/activity', - query: { id: this.props.project, ...getBranchLikeQuery(this.props.branchLike) } - }); - }; - - updateTooltip = (selectedDate?: Date, tooltipXPos?: number, tooltipIdx?: number) => - this.setState({ selectedDate, tooltipXPos, tooltipIdx }); - - renderTimeline() { - const { graph, selectedDate, series, tooltipIdx, tooltipXPos } = this.state; - return ( - - {({ width }) => ( -
- - {selectedDate !== undefined && - tooltipXPos !== undefined && - tooltipIdx !== undefined && ( - - )} -
- )} -
- ); - } - - render() { - const { series } = this.state; - if (!hasHistoryDataValue(series)) { - return this.props.renderWhenEmpty ? this.props.renderWhenEmpty() : null; - } - - return ( -
- {this.renderTimeline()} -
- ); - } -} - -export default withRouter(PreviewGraph); diff --git a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltips.tsx b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltips.tsx deleted file mode 100644 index 29dadfaec51..00000000000 --- a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltips.tsx +++ /dev/null @@ -1,80 +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 * as React from 'react'; -import { Popup, PopupPlacement } from 'sonar-ui-common/components/ui/popups'; -import { Serie } from '../../apps/projectActivity/utils'; -import DateFormatter from '../intl/DateFormatter'; -import PreviewGraphTooltipsContent from './PreviewGraphTooltipsContent'; - -interface Props { - formatValue: (value: number | string) => string; - graph: string; - graphWidth: number; - selectedDate: Date; - series: Serie[]; - tooltipIdx: number; - tooltipPos: number; -} - -const TOOLTIP_WIDTH = 160; - -export default class PreviewGraphTooltips extends React.PureComponent { - render() { - const { tooltipIdx } = this.props; - const top = 16; - let left = this.props.tooltipPos; - let placement = PopupPlacement.RightTop; - if (left > this.props.graphWidth - TOOLTIP_WIDTH) { - left -= TOOLTIP_WIDTH; - placement = PopupPlacement.LeftTop; - } - - return ( - -
-
- -
- - - {this.props.series.map((serie, idx) => { - const point = serie.data[tooltipIdx]; - if (!point || (!point.y && point.y !== 0)) { - return null; - } - return ( - - ); - })} - -
-
-
- ); - } -} diff --git a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltipsContent.tsx b/server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltipsContent.tsx deleted file mode 100644 index 29cfe3d3dc1..00000000000 --- a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltipsContent.tsx +++ /dev/null @@ -1,45 +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 * as React from 'react'; -import ChartLegendIcon from 'sonar-ui-common/components/icons/ChartLegendIcon'; - -interface Props { - index: number; - translatedName: string; - value: string; -} - -export default function PreviewGraphTooltipsContent({ index, translatedName, value }: Props) { - return ( - - - - - - {value} - - -
- {translatedName} -
- - - ); -} diff --git a/server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltips-test.tsx b/server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltips-test.tsx deleted file mode 100644 index c0b91d6282a..00000000000 --- a/server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltips-test.tsx +++ /dev/null @@ -1,77 +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 { DEFAULT_GRAPH } from '../../../apps/projectActivity/utils'; -import PreviewGraphTooltips from '../PreviewGraphTooltips'; - -const SERIES_ISSUES = [ - { - name: 'code_smells', - data: [ - { x: parseDate('2011-10-01T22:01:00.000Z'), y: 18 }, - { x: parseDate('2011-10-25T10:27:41.000Z'), y: 15 } - ], - translatedName: 'Code Smells', - type: 'INT' - }, - { - name: 'bugs', - data: [ - { x: parseDate('2011-10-01T22:01:00.000Z'), y: 3 }, - { x: parseDate('2011-10-25T10:27:41.000Z'), y: 0 } - ], - translatedName: 'Bugs', - type: 'INT' - }, - { - name: 'vulnerabilities', - data: [ - { x: parseDate('2011-10-01T22:01:00.000Z'), y: 0 }, - { x: parseDate('2011-10-25T10:27:41.000Z'), y: 1 } - ], - translatedName: 'Vulnerabilities', - type: 'INT' - } -]; - -const DEFAULT_PROPS: PreviewGraphTooltips['props'] = { - formatValue: (val: string) => 'Formated.' + val, - graph: DEFAULT_GRAPH, - graphWidth: 150, - selectedDate: parseDate('2011-10-01T22:01:00.000Z'), - series: SERIES_ISSUES, - tooltipIdx: 0, - tooltipPos: 25 -}; - -it('should render correctly', () => { - expect( - shallow( - - ) - ).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltipsContent-test.tsx b/server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltipsContent-test.tsx deleted file mode 100644 index f036ff4038e..00000000000 --- a/server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltipsContent-test.tsx +++ /dev/null @@ -1,32 +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 PreviewGraphTooltipsContent from '../PreviewGraphTooltipsContent'; - -const DEFAULT_PROPS = { - index: 1, - translatedName: 'Code Smells', - value: '1.2k' -}; - -it('should render correctly', () => { - expect(shallow()).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/components/preview-graph/__tests__/__snapshots__/PreviewGraphTooltips-test.tsx.snap b/server/sonar-web/src/main/js/components/preview-graph/__tests__/__snapshots__/PreviewGraphTooltips-test.tsx.snap deleted file mode 100644 index 3f3e69c14bc..00000000000 --- a/server/sonar-web/src/main/js/components/preview-graph/__tests__/__snapshots__/PreviewGraphTooltips-test.tsx.snap +++ /dev/null @@ -1,52 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` - -
-
- -
- - - - - - -
-
-
-`; diff --git a/server/sonar-web/src/main/js/components/preview-graph/__tests__/__snapshots__/PreviewGraphTooltipsContent-test.tsx.snap b/server/sonar-web/src/main/js/components/preview-graph/__tests__/__snapshots__/PreviewGraphTooltipsContent-test.tsx.snap deleted file mode 100644 index 9d4cbadfb97..00000000000 --- a/server/sonar-web/src/main/js/components/preview-graph/__tests__/__snapshots__/PreviewGraphTooltipsContent-test.tsx.snap +++ /dev/null @@ -1,28 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` - - - - - - 1.2k - - -
- Code Smells -
- - -`; diff --git a/server/sonar-web/src/main/js/types/project-activity.ts b/server/sonar-web/src/main/js/types/project-activity.ts new file mode 100644 index 00000000000..87128d02154 --- /dev/null +++ b/server/sonar-web/src/main/js/types/project-activity.ts @@ -0,0 +1,47 @@ +/* + * 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. + */ +export enum GraphType { + issues = 'issues', + coverage = 'coverage', + duplications = 'duplications', + custom = 'custom' +} + +export interface HistoryItem { + date: Date; + value?: string; +} + +export interface MeasureHistory { + metric: string; + history: HistoryItem[]; +} + +export interface Serie { + data: Point[]; + name: string; + translatedName: string; + type: string; +} + +export interface Point { + x: Date; + y: number | string | undefined; +} -- cgit v1.2.3