aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/__mocks__/react-virtualized.tsx39
-rw-r--r--server/sonar-web/public/images/activity-chart.svg1
-rw-r--r--server/sonar-web/src/main/js/app/styles/init/misc.css4
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.ts.snap53
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.ts3
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.ts165
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.tsx35
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.tsx39
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentCoverage-test.tsx63
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentIssues-test.tsx59
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAnalysesList-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentCoverage-test.tsx.snap66
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentDuplication-test.tsx.snap27
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap62
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.tsx.snap4
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css74
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/utils.ts172
-rw-r--r--server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx5
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.tsx)2
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/AddGraphMetricPopup.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetricPopup.tsx)2
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphHistory.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/GraphHistory.tsx)46
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.tsx)64
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.tsx)60
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphsLegendCustom.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendCustom.tsx)5
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphsLegendItem.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendItem.tsx)2
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphsLegendStatic.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.tsx)4
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphsTooltips.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.tsx)9
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContent.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContent.tsx)4
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentCoverage.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentCoverage.tsx)20
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentDuplication.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentDuplication.tsx)14
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentEvents.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentEvents.tsx)9
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentIssues.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentIssues.tsx)10
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/GraphsZoom.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.tsx)5
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetric-test.tsx (renamed from server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltipsContent-test.tsx)24
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetricPopup-test.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/AddGraphMetricPopup-test.tsx)2
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphHistory-test.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphHistory-test.tsx)2
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsHistory-test.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.tsx)4
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendCustom-test.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendCustom-test.tsx)0
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendItem-test.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendItem-test.tsx)43
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendStatic-test.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendStatic-test.tsx)0
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltips-test.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltips-test.tsx)3
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContent-test.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContent-test.tsx)0
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentCoverage-test.tsx65
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentDuplication-test.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentDuplication-test.tsx)48
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentEvents-test.tsx (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentEvents-test.tsx)0
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentIssues-test.tsx59
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/AddGraphMetric-test.tsx.snap35
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/AddGraphMetricPopup-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/AddGraphMetricPopup-test.tsx.snap)0
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphHistory-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphHistory-test.tsx.snap)4
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsHistory-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsHistory-test.tsx.snap)46
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendCustom-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendCustom-test.tsx.snap)2
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendItem-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendItem-test.tsx.snap)12
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendStatic-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendStatic-test.tsx.snap)2
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltips-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltips-test.tsx.snap)12
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContent-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContent-test.tsx.snap)4
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentCoverage-test.tsx.snap71
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentDuplication-test.tsx.snap45
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.tsx.snap)4
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap34
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/utils-test.ts.snap80
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/__tests__/utils-test.ts200
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/styles.css69
-rw-r--r--server/sonar-web/src/main/js/components/activity-graph/utils.ts166
-rw-r--r--server/sonar-web/src/main/js/components/preview-graph/PreviewGraph.tsx201
-rw-r--r--server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltips.tsx80
-rw-r--r--server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltips-test.tsx77
-rw-r--r--server/sonar-web/src/main/js/components/preview-graph/__tests__/__snapshots__/PreviewGraphTooltips-test.tsx.snap52
-rw-r--r--server/sonar-web/src/main/js/components/preview-graph/__tests__/__snapshots__/PreviewGraphTooltipsContent-test.tsx.snap28
-rw-r--r--server/sonar-web/src/main/js/types/project-activity.ts (renamed from server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltipsContent.tsx)44
72 files changed, 1255 insertions, 1399 deletions
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 @@
+<svg width="53" height="57" xmlns="http://www.w3.org/2000/svg"><g transform="translate(2 2)" fill="none" fill-rule="evenodd"><rect x="5" y="34" width="10" height="18" rx="2"/><path d="M14.054 34.935v16.13H5.946v-16.13h8.108m0-1.935H5.946A1.94 1.94 0 004 34.935v16.13A1.94 1.94 0 005.946 53h8.108A1.94 1.94 0 0016 51.065v-16.13A1.94 1.94 0 0014.054 33z" fill="#236A97" fill-rule="nonzero"/><rect x="20" y="26" width="10" height="26" rx="2"/><path d="M29.054 26.931v24.138h-8.108V26.931h8.108m0-1.931h-8.108A1.939 1.939 0 0019 26.931v24.138c0 1.066.871 1.931 1.946 1.931h8.108A1.939 1.939 0 0031 51.069V26.931A1.939 1.939 0 0029.054 25z" fill="#236A97" fill-rule="nonzero"/><rect x="34" y="10" width="10" height="43" rx="2"/><path d="M43.054 10.927v40.146h-8.108V10.927h8.108m0-1.927h-8.108A1.936 1.936 0 0033 10.927v40.146c0 1.064.871 1.927 1.946 1.927h8.108A1.936 1.936 0 0045 51.073V10.927A1.936 1.936 0 0043.054 9z" fill="#236A97" fill-rule="nonzero"/><path stroke="#236A97" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" d="M5 24L19.986 9.998l4.93 4.532L40 0"/><path stroke="#236A97" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" d="M40 6V0h-6M0 21v32h49"/></g></svg> \ No newline at end of file
diff --git a/server/sonar-web/src/main/js/app/styles/init/misc.css b/server/sonar-web/src/main/js/app/styles/init/misc.css
index cba59a7718d..2342b4676e3 100644
--- a/server/sonar-web/src/main/js/app/styles/init/misc.css
+++ b/server/sonar-web/src/main/js/app/styles/init/misc.css
@@ -402,6 +402,10 @@ th.huge-spacer-right {
flex: 0 0 auto;
}
+.flex-grow {
+ flex-grow: 1;
+}
+
.flex-shrink {
flex-shrink: 1;
min-width: 0;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.ts.snap b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.ts.snap
index 1bc54255ce1..0f23892e414 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.ts.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/utils-test.ts.snap
@@ -1,58 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`generateCoveredLinesMetric should correctly generate covered lines metric 1`] = `
-Object {
- "data": Array [
- Object {
- "x": 2017-04-27T08:21:32.000Z,
- "y": 88,
- },
- Object {
- "x": 2017-04-30T23:06:24.000Z,
- "y": 50,
- },
- ],
- "name": "covered_lines",
- "translatedName": "project_activity.custom_metric.covered_lines",
- "type": "INT",
-}
-`;
-
-exports[`generateSeries should correctly generate the series 1`] = `
-Array [
- Object {
- "data": Array [
- Object {
- "x": 2017-04-27T08:21:32.000Z,
- "y": 88,
- },
- Object {
- "x": 2017-04-30T23:06:24.000Z,
- "y": 50,
- },
- ],
- "name": "covered_lines",
- "translatedName": "project_activity.custom_metric.covered_lines",
- "type": "INT",
- },
- Object {
- "data": Array [
- Object {
- "x": 2017-04-27T08:21:32.000Z,
- "y": 100,
- },
- Object {
- "x": 2017-04-30T23:06:24.000Z,
- "y": 100,
- },
- ],
- "name": "lines_to_cover",
- "translatedName": "Line to Cover",
- "type": "PERCENT",
- },
-]
-`;
-
exports[`getAnalysesByVersionByDay should also filter analysis based on the query 1`] = `
Array [
Object {
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.ts b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.ts
index 140520be2fa..f0c1feefab6 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.ts
+++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.ts
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { parseDate } from 'sonar-ui-common/helpers/dates';
+import { DEFAULT_GRAPH } from '../../../components/activity-graph/utils';
import * as actions from '../actions';
const ANALYSES = [
@@ -69,7 +70,7 @@ const emptyState = {
measuresHistory: [],
measures: [],
metrics: [],
- query: { category: '', graph: '', project: '', customMetrics: [] }
+ query: { category: '', graph: DEFAULT_GRAPH, project: '', customMetrics: [] }
};
const state = { ...emptyState, analyses: ANALYSES };
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.ts
index f690a16b717..0928b0b9334 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.ts
+++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.ts
@@ -18,6 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as dates from 'sonar-ui-common/helpers/dates';
+import { DEFAULT_GRAPH } from '../../../components/activity-graph/utils';
+import { GraphType } from '../../../types/project-activity';
import * as utils from '../utils';
jest.mock('date-fns/start_of_day', () =>
@@ -67,78 +69,34 @@ const ANALYSES = [
{ key: 'AVvtGF3IY6vCuQNDdwxI', date: dates.parseDate('2017-05-09T12:03:59.000Z'), events: [] }
];
-const HISTORY = [
- {
- metric: 'lines_to_cover',
- history: [
- { date: dates.parseDate('2017-04-27T08:21:32.000Z'), value: '100' },
- { date: dates.parseDate('2017-04-30T23:06:24.000Z'), value: '100' }
- ]
- },
- {
- metric: 'uncovered_lines',
- history: [
- { date: dates.parseDate('2017-04-27T08:21:32.000Z'), value: '12' },
- { date: dates.parseDate('2017-04-30T23:06:24.000Z'), value: '50' }
- ]
- }
-];
-
-const METRICS = [
- { id: '1', key: 'uncovered_lines', name: 'Uncovered Lines', type: 'INT' },
- { id: '2', key: 'lines_to_cover', name: 'Line to Cover', type: 'PERCENT' }
-];
-
const QUERY = {
category: '',
from: dates.parseDate('2017-04-27T08:21:32.000Z'),
- graph: utils.DEFAULT_GRAPH,
+ graph: DEFAULT_GRAPH,
project: 'foo',
to: undefined,
selectedDate: undefined,
customMetrics: ['foo', 'bar', 'baz']
};
-describe('generateCoveredLinesMetric', () => {
- it('should correctly generate covered lines metric', () => {
- expect(utils.generateCoveredLinesMetric(HISTORY[1], HISTORY)).toMatchSnapshot();
- });
-});
-
-describe('generateSeries', () => {
- it('should correctly generate the series', () => {
- expect(
- utils.generateSeries(HISTORY, 'coverage', METRICS, ['uncovered_lines', 'lines_to_cover'])
- ).toMatchSnapshot();
- });
-});
-
describe('getAnalysesByVersionByDay', () => {
it('should correctly map analysis by versions and by days', () => {
expect(
utils.getAnalysesByVersionByDay(ANALYSES, {
- category: '',
- customMetrics: [],
- graph: utils.DEFAULT_GRAPH,
- project: 'foo'
+ category: ''
})
).toMatchSnapshot();
});
it('should also filter analysis based on the query', () => {
expect(
utils.getAnalysesByVersionByDay(ANALYSES, {
- category: 'QUALITY_PROFILE',
- customMetrics: [],
- graph: utils.DEFAULT_GRAPH,
- project: 'foo'
+ category: 'QUALITY_PROFILE'
})
).toMatchSnapshot();
expect(
utils.getAnalysesByVersionByDay(ANALYSES, {
category: '',
- customMetrics: [],
- graph: utils.DEFAULT_GRAPH,
- project: 'foo',
+
to: dates.parseDate('2017-06-09T11:12:27.000Z'),
from: dates.parseDate('2017-05-18T14:13:07.000Z')
})
@@ -170,54 +128,13 @@ describe('getAnalysesByVersionByDay', () => {
}
],
{
- category: '',
- customMetrics: [],
- graph: utils.DEFAULT_GRAPH,
- project: 'foo'
+ category: ''
}
)
).toMatchSnapshot();
});
});
-describe('getDisplayedHistoryMetrics', () => {
- const customMetrics = ['foo', 'bar'];
- it('should return only displayed metrics on the graph', () => {
- expect(utils.getDisplayedHistoryMetrics(utils.DEFAULT_GRAPH, [])).toEqual([
- 'bugs',
- 'code_smells',
- 'vulnerabilities'
- ]);
- expect(utils.getDisplayedHistoryMetrics('coverage', customMetrics)).toEqual([
- 'lines_to_cover',
- 'uncovered_lines'
- ]);
- });
- it('should return all custom metrics for the custom graph', () => {
- expect(utils.getDisplayedHistoryMetrics('custom', customMetrics)).toEqual(customMetrics);
- });
-});
-
-describe('getHistoryMetrics', () => {
- const customMetrics = ['foo', 'bar'];
- it('should return all metrics', () => {
- expect(utils.getHistoryMetrics(utils.DEFAULT_GRAPH, [])).toEqual([
- 'bugs',
- 'code_smells',
- 'vulnerabilities',
- 'reliability_rating',
- 'security_rating',
- 'sqale_rating'
- ]);
- expect(utils.getHistoryMetrics('coverage', customMetrics)).toEqual([
- 'lines_to_cover',
- 'uncovered_lines',
- 'coverage'
- ]);
- expect(utils.getHistoryMetrics('custom', customMetrics)).toEqual(customMetrics);
- });
-});
-
describe('parseQuery', () => {
it('should parse query with default values', () => {
expect(
@@ -236,11 +153,13 @@ describe('serializeQuery', () => {
from: '2017-04-27T08:21:32+0000',
project: 'foo'
});
- expect(utils.serializeQuery({ ...QUERY, graph: 'coverage', category: 'test' })).toEqual({
- from: '2017-04-27T08:21:32+0000',
- project: 'foo',
- category: 'test'
- });
+ expect(utils.serializeQuery({ ...QUERY, graph: GraphType.coverage, category: 'test' })).toEqual(
+ {
+ from: '2017-04-27T08:21:32+0000',
+ project: 'foo',
+ category: 'test'
+ }
+ );
});
});
@@ -252,59 +171,17 @@ describe('serializeUrlQuery', () => {
custom_metrics: 'foo,bar,baz'
});
expect(
- utils.serializeUrlQuery({ ...QUERY, graph: 'coverage', category: 'test', customMetrics: [] })
+ utils.serializeUrlQuery({
+ ...QUERY,
+ graph: GraphType.coverage,
+ category: 'test',
+ customMetrics: []
+ })
).toEqual({
from: '2017-04-27T08:21:32+0000',
id: 'foo',
- graph: 'coverage',
+ graph: GraphType.coverage,
category: 'test'
});
});
});
-
-describe('hasHistoryData', () => {
- it('should correctly detect if there is history data', () => {
- expect(
- utils.hasHistoryData([
- {
- name: 'foo',
- translatedName: 'foo',
- type: 'INT',
- data: [
- { x: dates.parseDate('2017-04-27T08:21:32.000Z'), y: 2 },
- { x: dates.parseDate('2017-04-30T23:06:24.000Z'), y: 2 }
- ]
- }
- ])
- ).toBeTruthy();
- expect(
- utils.hasHistoryData([
- {
- name: 'foo',
- translatedName: 'foo',
- type: 'INT',
- data: []
- },
- {
- name: 'bar',
- translatedName: 'bar',
- type: 'INT',
- data: [
- { x: dates.parseDate('2017-04-27T08:21:32.000Z'), y: 2 },
- { x: dates.parseDate('2017-04-30T23:06:24.000Z'), y: 2 }
- ]
- }
- ])
- ).toBeTruthy();
- expect(
- utils.hasHistoryData([
- {
- name: 'bar',
- translatedName: 'bar',
- type: 'INT',
- data: [{ x: dates.parseDate('2017-04-27T08:21:32.000Z'), y: 2 }]
- }
- ])
- ).toBeFalsy();
- });
-});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx
index d586c683458..7f9c038babe 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx
@@ -23,7 +23,8 @@ import { parseDate } from 'sonar-ui-common/helpers/dates';
import { translate } from 'sonar-ui-common/helpers/l10n';
import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
-import { MeasureHistory, Query } from '../utils';
+import { MeasureHistory } from '../../../types/project-activity';
+import { Query } from '../utils';
import './projectActivity.css';
import ProjectActivityAnalysesList from './ProjectActivityAnalysesList';
import ProjectActivityGraphs from './ProjectActivityGraphs';
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.tsx
index 3e68ce9187f..c4960998b5a 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.tsx
@@ -24,16 +24,18 @@ import { parseDate } from 'sonar-ui-common/helpers/dates';
import { getAllMetrics } from '../../../api/metrics';
import * as api from '../../../api/projectActivity';
import { getAllTimeMachineData } from '../../../api/time-machine';
+import {
+ DEFAULT_GRAPH,
+ getActivityGraph,
+ getHistoryMetrics,
+ isCustomGraph
+} from '../../../components/activity-graph/utils';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { BranchLike } from '../../../types/branch-like';
+import { GraphType, MeasureHistory } from '../../../types/project-activity';
import * as actions from '../actions';
import {
customMetricsChanged,
- DEFAULT_GRAPH,
- getHistoryMetrics,
- getProjectActivityGraph,
- isCustomGraph,
- MeasureHistory,
parseQuery,
Query,
serializeQuery,
@@ -59,6 +61,8 @@ export interface State {
query: Query;
}
+export const PROJECT_ACTIVITY_GRAPH = 'sonar_project_activity.graph';
+
export default class ProjectActivityAppContainer extends React.PureComponent<Props, State> {
mounted = false;
@@ -78,7 +82,10 @@ export default class ProjectActivityAppContainer extends React.PureComponent<Pro
componentDidMount() {
this.mounted = true;
if (this.shouldRedirect()) {
- const { graph, customGraphs } = getProjectActivityGraph(this.props.component.key);
+ const { graph, customGraphs } = getActivityGraph(
+ PROJECT_ACTIVITY_GRAPH,
+ this.props.component.key
+ );
const newQuery = { ...this.state.query, graph };
if (isCustomGraph(newQuery.graph)) {
newQuery.customMetrics = customGraphs;
@@ -100,7 +107,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent<Pro
const query = parseQuery(this.props.location.query);
if (query.graph !== this.state.query.graph || customMetricsChanged(this.state.query, query)) {
if (this.state.initialized) {
- this.updateGraphData(query.graph, query.customMetrics);
+ this.updateGraphData(query.graph || DEFAULT_GRAPH, query.customMetrics);
} else {
this.firstLoadData(query, this.props.component);
}
@@ -136,7 +143,10 @@ export default class ProjectActivityAppContainer extends React.PureComponent<Pro
deleteAnalysis = (analysis: string) => {
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<Pro
}
firstLoadData(query: Query, component: T.Component) {
- const graphMetrics = getHistoryMetrics(query.graph, query.customMetrics);
+ const graphMetrics = getHistoryMetrics(query.graph || DEFAULT_GRAPH, query.customMetrics);
const topLevelComponent = this.getTopLevelComponent(component);
Promise.all([
this.fetchActivity(topLevelComponent, 1, 100, serializeQuery(query)),
@@ -271,7 +281,7 @@ export default class ProjectActivityAppContainer extends React.PureComponent<Pro
);
}
- updateGraphData = (graph: string, customMetrics: string[]) => {
+ 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<Pro
key => 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<Props, St
addCustomMetric = (metric: string) => {
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<Props, St
return (
<div className="project-activity-layout-page-main-inner boxed-group boxed-group-inner">
- <ProjectActivityGraphsHeader
+ <GraphsHeader
addCustomMetric={this.addCustomMetric}
+ className="big-spacer-bottom"
graph={query.graph}
metrics={metrics}
metricsTypeFilter={this.getMetricsTypeFilter()}
@@ -209,7 +205,6 @@ export default class ProjectActivityGraphs extends React.PureComponent<Props, St
/>
<GraphsHistory
analyses={this.props.analyses}
- eventFilter={query.category}
graph={query.graph}
graphEndDate={graphEndDate}
graphStartDate={graphStartDate}
@@ -230,7 +225,7 @@ export default class ProjectActivityGraphs extends React.PureComponent<Props, St
loading={loading}
metricsType={getSeriesMetricType(series)}
series={series}
- showAreas={['coverage', 'duplications'].includes(query.graph)}
+ showAreas={[GraphType.coverage, GraphType.duplications].includes(query.graph)}
updateGraphZoom={this.updateGraphZoom}
/>
</div>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentCoverage-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentCoverage-test.tsx
deleted file mode 100644
index 4b9ba0a7e95..00000000000
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentCoverage-test.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2020 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import { parseDate } from 'sonar-ui-common/helpers/dates';
-import GraphsTooltipsContentCoverage from '../GraphsTooltipsContentCoverage';
-
-const MEASURES_COVERAGE = [
- {
- metric: 'coverage',
- history: [
- { date: parseDate('2011-10-01T22:01:00.000Z') },
- { date: parseDate('2011-10-25T10:27:41.000Z'), value: '80.3' }
- ]
- },
- {
- metric: 'lines_to_cover',
- history: [
- { date: parseDate('2011-10-01T22:01:00.000Z'), value: '60545' },
- { date: parseDate('2011-10-25T10:27:41.000Z'), value: '65215' }
- ]
- },
- {
- metric: 'uncovered_lines',
- history: [
- { date: parseDate('2011-10-01T22:01:00.000Z'), value: '40564' },
- { date: parseDate('2011-10-25T10:27:41.000Z'), value: '10245' }
- ]
- }
-];
-
-const DEFAULT_PROPS = {
- addSeparator: true,
- measuresHistory: MEASURES_COVERAGE,
- tooltipIdx: 1
-};
-
-it('should render correctly', () => {
- expect(shallow(<GraphsTooltipsContentCoverage {...DEFAULT_PROPS} />)).toMatchSnapshot();
-});
-
-it('should render correctly when data is missing', () => {
- expect(
- shallow(<GraphsTooltipsContentCoverage {...DEFAULT_PROPS} tooltipIdx={0} />)
- ).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentIssues-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentIssues-test.tsx
deleted file mode 100644
index babe1217bae..00000000000
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentIssues-test.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2020 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import { parseDate } from 'sonar-ui-common/helpers/dates';
-import GraphsTooltipsContentIssues from '../GraphsTooltipsContentIssues';
-
-const MEASURES_ISSUES = [
- {
- metric: 'bugs',
- history: [
- { date: parseDate('2011-10-01T22:01:00.000Z'), value: '500' },
- { date: parseDate('2011-10-25T10:27:41.000Z'), value: '1.2k' }
- ]
- },
- {
- metric: 'reliability_rating',
- history: [
- { date: parseDate('2011-10-01T22:01:00.000Z') },
- { date: parseDate('2011-10-25T10:27:41.000Z'), value: '5.0' }
- ]
- }
-];
-
-const DEFAULT_PROPS = {
- index: 2,
- measuresHistory: MEASURES_ISSUES,
- name: 'bugs',
- tooltipIdx: 1,
- translatedName: 'Bugs',
- value: '1.2k'
-};
-
-it('should render correctly', () => {
- expect(shallow(<GraphsTooltipsContentIssues {...DEFAULT_PROPS} />)).toMatchSnapshot();
-});
-
-it('should render correctly when rating data is missing', () => {
- expect(
- shallow(<GraphsTooltipsContentIssues {...DEFAULT_PROPS} tooltipIdx={0} value="500" />)
- ).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAnalysesList-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAnalysesList-test.tsx
index 27f010105ab..d4f5e2ad6e4 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAnalysesList-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityAnalysesList-test.tsx
@@ -20,9 +20,9 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { parseDate } from 'sonar-ui-common/helpers/dates';
+import { DEFAULT_GRAPH } from '../../../../components/activity-graph/utils';
import { mockParsedAnalysis } from '../../../../helpers/testMocks';
import { ComponentQualifier } from '../../../../types/component';
-import { DEFAULT_GRAPH } from '../../utils';
import ProjectActivityAnalysesList from '../ProjectActivityAnalysesList';
jest.mock('date-fns/start_of_day', () => (date: Date) => {
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-test.tsx
index d1678a19e02..24c3b387b81 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-test.tsx
@@ -20,7 +20,7 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { parseDate } from 'sonar-ui-common/helpers/dates';
-import { DEFAULT_GRAPH } from '../../utils';
+import { DEFAULT_GRAPH } from '../../../../components/activity-graph/utils';
import ProjectActivityApp from '../ProjectActivityApp';
const ANALYSES = [
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.tsx
index 3b36620845f..613cbd2959d 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.tsx
@@ -20,7 +20,7 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { parseDate } from 'sonar-ui-common/helpers/dates';
-import { DEFAULT_GRAPH } from '../../utils';
+import { DEFAULT_GRAPH } from '../../../../components/activity-graph/utils';
import ProjectActivityGraphs from '../ProjectActivityGraphs';
const ANALYSES = [
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentCoverage-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentCoverage-test.tsx.snap
deleted file mode 100644
index e68fa2aacd4..00000000000
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentCoverage-test.tsx.snap
+++ /dev/null
@@ -1,66 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<tbody>
- <tr>
- <td
- className="project-activity-graph-tooltip-separator"
- colSpan={3}
- >
- <hr />
- </td>
- </tr>
- <tr
- className="project-activity-graph-tooltip-line"
- >
- <td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
- colSpan={2}
- >
- 10short_number_suffix.k
- </td>
- <td>
- metric.uncovered_lines.name
- </td>
- </tr>
- <tr
- className="project-activity-graph-tooltip-line"
- >
- <td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
- colSpan={2}
- >
- 80.3%
- </td>
- <td>
- metric.coverage.name
- </td>
- </tr>
-</tbody>
-`;
-
-exports[`should render correctly when data is missing 1`] = `
-<tbody>
- <tr>
- <td
- className="project-activity-graph-tooltip-separator"
- colSpan={3}
- >
- <hr />
- </td>
- </tr>
- <tr
- className="project-activity-graph-tooltip-line"
- >
- <td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
- colSpan={2}
- >
- 41short_number_suffix.k
- </td>
- <td>
- metric.uncovered_lines.name
- </td>
- </tr>
-</tbody>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentDuplication-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentDuplication-test.tsx.snap
deleted file mode 100644
index a8ba47bbcc8..00000000000
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentDuplication-test.tsx.snap
+++ /dev/null
@@ -1,27 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<tbody>
- <tr>
- <td
- className="project-activity-graph-tooltip-separator"
- colSpan={3}
- >
- <hr />
- </td>
- </tr>
- <tr
- className="project-activity-graph-tooltip-line"
- >
- <td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
- colSpan={2}
- >
- 10,245.0%
- </td>
- <td>
- metric.duplicated_lines_density.name
- </td>
- </tr>
-</tbody>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap
deleted file mode 100644
index 2cb9fd48c09..00000000000
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap
+++ /dev/null
@@ -1,62 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<tr
- className="project-activity-graph-tooltip-issues-line"
- key="bugs"
->
- <td
- className="thin"
- >
- <ChartLegendIcon
- className="spacer-right"
- index={2}
- />
- </td>
- <td
- className="text-right spacer-right"
- >
- <span
- className="project-activity-graph-tooltip-value"
- >
- 1.2k
- </span>
- <Rating
- className="spacer-left"
- small={true}
- value="5.0"
- />
- </td>
- <td>
- Bugs
- </td>
-</tr>
-`;
-
-exports[`should render correctly when rating data is missing 1`] = `
-<tr
- className="project-activity-graph-tooltip-issues-line"
- key="bugs"
->
- <td
- className="thin"
- >
- <ChartLegendIcon
- className="spacer-right"
- index={2}
- />
- </td>
- <td
- className="text-right spacer-right"
- >
- <span
- className="project-activity-graph-tooltip-value"
- >
- 500
- </span>
- </td>
- <td>
- Bugs
- </td>
-</tr>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.tsx.snap
index 21d5f179b4b..b817fd80c5e 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.tsx.snap
@@ -4,8 +4,9 @@ exports[`should render correctly the graph and legends 1`] = `
<div
className="project-activity-layout-page-main-inner boxed-group boxed-group-inner"
>
- <ProjectActivityGraphsHeader
+ <GraphsHeader
addCustomMetric={[Function]}
+ className="big-spacer-bottom"
graph="issues"
metrics={
Array [
@@ -58,7 +59,6 @@ exports[`should render correctly the graph and legends 1`] = `
},
]
}
- eventFilter=""
graph="issues"
graphs={
Array [
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css b/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
index 9f6d55740fe..81c7d7aeaa1 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
@@ -81,8 +81,8 @@
}
.project-activity-analysis.selected {
- background-color: #ecf6fe;
cursor: default;
+ background-color: var(--rowHoverHighlight);
}
.project-activity-analysis:focus {
@@ -90,7 +90,7 @@
}
.project-activity-analysis:hover {
- background-color: #ecf6fe;
+ background-color: var(--rowHoverHighlight);
}
.project-activity-analysis + .project-activity-analysis {
@@ -172,79 +172,11 @@
text-overflow: ellipsis;
}
-.project-activity-graphs {
- flex-grow: 1;
- display: flex;
- flex-direction: column;
- align-items: stretch;
- justify-content: center;
-}
-
-.project-activity-graph-container {
- padding: 10px 0;
- flex-grow: 1;
- display: flex;
- flex-direction: column;
- align-items: stretch;
- justify-content: center;
-}
-
-.project-activity-graph {
- flex: 1;
- overflow: hidden;
-}
-
-.project-activity-graph-legends {
- flex-grow: 0;
- padding-bottom: 16px;
- text-align: center;
-}
-
-.project-activity-graph-legend-actionable {
- display: inline-block;
- padding: 4px 8px 4px 12px;
- border-width: 1px;
- border-style: solid;
- border-radius: 12px;
-}
-
-.project-activity-graph-tooltip {
- padding: 8px;
-}
-
-.project-activity-graph-tooltip-line {
- height: 20px;
-}
-
-.project-activity-graph-tooltip-line + .project-activity-graph-tooltip-line {
- padding-top: 4px;
-}
-
.Select .project-activity-event-icon,
-.project-activity-graph-tooltip-line .project-activity-event-icon {
+.activity-graph-tooltip-line .project-activity-event-icon {
margin-top: 1px;
}
-.project-activity-graph-tooltip-issues-line {
- height: 26px;
- padding-bottom: 4px;
-}
-
-.project-activity-graph-tooltip-separator {
- padding-left: 16px;
- padding-right: 16px;
-}
-
-.project-activity-graph-tooltip-separator hr {
- margin-top: 8px;
- margin-bottom: 8px;
-}
-
-.project-activity-graph-tooltip-title,
-.project-activity-graph-tooltip-value {
- font-weight: bold;
-}
-
.baseline-marker {
position: absolute;
top: -10px;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/utils.ts b/server/sonar-web/src/main/js/apps/projectActivity/utils.ts
index 2f527e73bbf..e01201e5c44 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/utils.ts
+++ b/server/sonar-web/src/main/js/apps/projectActivity/utils.ts
@@ -18,9 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as startOfDay from 'date-fns/start_of_day';
-import { chunk, flatMap, groupBy, isEqual, sortBy } from 'lodash';
+import { isEqual } from 'lodash';
import { parseDate } from 'sonar-ui-common/helpers/dates';
-import { getLocalizedMetricName, translate } from 'sonar-ui-common/helpers/l10n';
import {
cleanQuery,
parseAsArray,
@@ -30,61 +29,21 @@ import {
serializeString,
serializeStringArray
} from 'sonar-ui-common/helpers/query';
-import { get } from 'sonar-ui-common/helpers/storage';
+import { DEFAULT_GRAPH } from '../../components/activity-graph/utils';
+import { GraphType } from '../../types/project-activity';
export interface Query {
category: string;
customMetrics: string[];
from?: Date;
- graph: string;
+ graph: GraphType;
project: string;
selectedDate?: Date;
to?: Date;
}
-export interface Point {
- x: Date;
- y: number | string | undefined;
-}
-
-export interface Serie {
- data: Point[];
- name: string;
- translatedName: string;
- type: string;
-}
-
-export interface HistoryItem {
- date: Date;
- value?: string;
-}
-
-export interface MeasureHistory {
- metric: string;
- history: HistoryItem[];
-}
-
export const EVENT_TYPES = ['VERSION', 'QUALITY_GATE', 'QUALITY_PROFILE', 'OTHER'];
export const APPLICATION_EVENT_TYPES = ['QUALITY_GATE', 'DEFINITION_CHANGE', 'OTHER'];
-export const DEFAULT_GRAPH = 'issues';
-export const GRAPH_TYPES = ['issues', 'coverage', 'duplications', 'custom'];
-export const GRAPHS_METRICS_DISPLAYED: T.Dict<string[]> = {
- issues: ['bugs', 'code_smells', 'vulnerabilities'],
- coverage: ['lines_to_cover', 'uncovered_lines'],
- duplications: ['ncloc', 'duplicated_lines']
-};
-export const GRAPHS_METRICS: T.Dict<string[]> = {
- issues: GRAPHS_METRICS_DISPLAYED['issues'].concat([
- 'reliability_rating',
- 'security_rating',
- 'sqale_rating'
- ]),
- coverage: GRAPHS_METRICS_DISPLAYED['coverage'].concat(['coverage']),
- duplications: GRAPHS_METRICS_DISPLAYED['duplications'].concat(['duplicated_lines_density'])
-};
-
-export const PROJECT_ACTIVITY_GRAPH = 'sonar_project_activity.graph';
-export const PROJECT_ACTIVITY_GRAPH_CUSTOM = 'sonar_project_activity.graph.custom';
export function activityQueryChanged(prevQuery: Query, nextQuery: Query) {
return prevQuery.category !== nextQuery.category || datesQueryChanged(prevQuery, nextQuery);
@@ -98,105 +57,24 @@ export function datesQueryChanged(prevQuery: Query, nextQuery: Query) {
return !isEqual(prevQuery.from, nextQuery.from) || !isEqual(prevQuery.to, nextQuery.to);
}
-export function hasDataValues(serie: Serie) {
- return serie.data.some(point => Boolean(point.y || point.y === 0));
-}
-
-export function hasHistoryData(series: Serie[]) {
- return series.some(serie => serie.data && serie.data.length > 1);
-}
-
-export function hasHistoryDataValue(series: Serie[]) {
- return series.some(serie => serie.data && serie.data.length > 1 && hasDataValues(serie));
-}
-
export function historyQueryChanged(prevQuery: Query, nextQuery: Query) {
return prevQuery.graph !== nextQuery.graph;
}
-export function isCustomGraph(graph: string) {
- return graph === 'custom';
-}
-
export function selectedDateQueryChanged(prevQuery: Query, nextQuery: Query) {
return !isEqual(prevQuery.selectedDate, nextQuery.selectedDate);
}
-export function generateCoveredLinesMetric(
- uncoveredLines: MeasureHistory,
- measuresHistory: MeasureHistory[]
-) {
- const linesToCover = measuresHistory.find(measure => measure.metric === 'lines_to_cover');
- return {
- data: linesToCover
- ? uncoveredLines.history.map((analysis, idx) => ({
- x: analysis.date,
- y: Number(linesToCover.history[idx].value) - Number(analysis.value)
- }))
- : [],
- name: 'covered_lines',
- translatedName: translate('project_activity.custom_metric.covered_lines'),
- type: 'INT'
- };
-}
-
-function findMetric(key: string, metrics: T.Metric[] | T.Dict<T.Metric>) {
- if (Array.isArray(metrics)) {
- return metrics.find(metric => metric.key === key);
- }
- return metrics[key];
-}
-
-export function generateSeries(
- measuresHistory: MeasureHistory[],
- graph: string,
- metrics: T.Metric[] | T.Dict<T.Metric>,
- displayedMetrics: string[]
-): Serie[] {
- if (displayedMetrics.length <= 0 || typeof measuresHistory === 'undefined') {
- return [];
- }
- return sortBy(
- measuresHistory
- .filter(measure => displayedMetrics.indexOf(measure.metric) >= 0)
- .map(measure => {
- if (measure.metric === 'uncovered_lines' && !isCustomGraph(graph)) {
- return generateCoveredLinesMetric(measure, measuresHistory);
- }
- const metric = findMetric(measure.metric, metrics);
- return {
- data: measure.history.map(analysis => ({
- x: analysis.date,
- y: metric && metric.type === 'LEVEL' ? analysis.value : Number(analysis.value)
- })),
- name: measure.metric,
- translatedName: metric ? getLocalizedMetricName(metric) : measure.metric,
- type: metric ? metric.type : 'INT'
- };
- }),
- serie =>
- displayedMetrics.indexOf(serie.name === 'covered_lines' ? 'uncovered_lines' : serie.name)
- );
-}
-
-export function splitSeriesInGraphs(series: Serie[], maxGraph: number, maxSeries: number) {
- return flatMap(
- groupBy(series, serie => serie.type),
- type => chunk(type, maxSeries)
- ).slice(0, maxGraph);
-}
-
-export function getSeriesMetricType(series: Serie[]) {
- return series.length > 0 ? series[0].type : 'INT';
-}
-
interface AnalysesByDay {
byDay: T.Dict<T.ParsedAnalysis[]>;
version: string | null;
key: string | null;
}
-export function getAnalysesByVersionByDay(analyses: T.ParsedAnalysis[], query: Query) {
+export function getAnalysesByVersionByDay(
+ analyses: T.ParsedAnalysis[],
+ query: Pick<Query, 'category' | 'from' | 'to'>
+) {
return analyses.reduce<AnalysesByDay[]>((acc, analysis) => {
let currentVersion = acc[acc.length - 1];
const versionEvent = analysis.events.find(event => event.category === 'VERSION');
@@ -237,31 +115,6 @@ export function getAnalysesByVersionByDay(analyses: T.ParsedAnalysis[], query: Q
}, []);
}
-export function getDisplayedHistoryMetrics(graph: string, customMetrics: string[]) {
- return isCustomGraph(graph) ? customMetrics : GRAPHS_METRICS_DISPLAYED[graph];
-}
-
-export function getHistoryMetrics(graph: string, customMetrics: string[]) {
- return isCustomGraph(graph) ? customMetrics : GRAPHS_METRICS[graph];
-}
-
-export function getProjectActivityGraph(project: string) {
- const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM, project);
- return {
- graph: get(PROJECT_ACTIVITY_GRAPH, project) || 'issues',
- customGraphs: customGraphs ? customGraphs.split(',') : []
- };
-}
-
-function parseGraph(value?: string) {
- const graph = parseAsString(value);
- return GRAPH_TYPES.includes(graph) ? graph : DEFAULT_GRAPH;
-}
-
-function serializeGraph(value: string) {
- return value === DEFAULT_GRAPH ? undefined : value;
-}
-
export function parseQuery(urlQuery: T.RawQuery): Query {
return {
category: parseAsString(urlQuery['category']),
@@ -294,3 +147,12 @@ export function serializeUrlQuery(query: Query): T.RawQuery {
selected_date: serializeDate(query.selectedDate)
});
}
+
+function parseGraph(value?: string) {
+ const graph = parseAsString(value);
+ return Object.keys(GraphType).includes(graph) ? (graph as GraphType) : DEFAULT_GRAPH;
+}
+
+function serializeGraph(value?: GraphType) {
+ return value === DEFAULT_GRAPH ? undefined : value;
+}
diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx
index 327ce48bfed..f6118da854a 100644
--- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx
@@ -156,10 +156,7 @@ export default class BranchAnalysisList extends React.PureComponent<Props, State
const { analyses, loading, range } = this.state;
const byVersionByDay = getAnalysesByVersionByDay(analyses, {
- category: '',
- customMetrics: [],
- graph: '',
- project: this.props.component
+ category: ''
});
const hasFilteredData =
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.tsx b/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx
index ed7c51a041d..3e54e23ad9e 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetric.tsx
@@ -23,7 +23,7 @@ import { Button } from 'sonar-ui-common/components/controls/buttons';
import Dropdown from 'sonar-ui-common/components/controls/Dropdown';
import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon';
import { getLocalizedMetricName, translate } from 'sonar-ui-common/helpers/l10n';
-import { isDiffMetric } from '../../../../helpers/measures';
+import { isDiffMetric } from '../../helpers/measures';
import AddGraphMetricPopup from './AddGraphMetricPopup';
interface Props {
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetricPopup.tsx b/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetricPopup.tsx
index c8eac085ca5..7311c36c7b1 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetricPopup.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/AddGraphMetricPopup.tsx
@@ -20,7 +20,7 @@
import * as React from 'react';
import { Alert } from 'sonar-ui-common/components/ui/Alert';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
-import MultiSelect from '../../../../components/common/MultiSelect';
+import MultiSelect from '../common/MultiSelect';
export interface AddGraphMetricPopupProps {
elements: string[];
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphHistory.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphHistory.tsx
index e6658b63bcb..da94b4e0a08 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphHistory.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphHistory.tsx
@@ -21,8 +21,8 @@ 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 { getShortType } from '../../helpers/measures';
+import { MeasureHistory, Serie } from '../../types/project-activity';
import GraphsLegendCustom from './GraphsLegendCustom';
import GraphsLegendStatic from './GraphsLegendStatic';
import GraphsTooltips from './GraphsTooltips';
@@ -33,15 +33,15 @@ interface Props {
graphEndDate?: Date;
graphStartDate?: Date;
leakPeriodDate?: Date;
- isCustom: boolean;
+ isCustom?: boolean;
measuresHistory: MeasureHistory[];
metricsType: string;
- removeCustomMetric: (metric: string) => void;
+ removeCustomMetric?: (metric: string) => void;
showAreas: boolean;
series: Serie[];
selectedDate?: Date;
- updateGraphZoom: (from?: Date, to?: Date) => void;
- updateSelectedDate: (selectedDate?: Date) => void;
+ updateGraphZoom?: (from?: Date, to?: Date) => void;
+ updateSelectedDate?: (selectedDate?: Date) => void;
updateTooltip: (selectedDate?: Date) => void;
}
@@ -67,30 +67,42 @@ export default class GraphHistory extends React.PureComponent<Props, State> {
};
render() {
- const { graph, selectedDate, series } = this.props;
+ const {
+ events,
+ graph,
+ graphEndDate,
+ graphStartDate,
+ isCustom,
+ leakPeriodDate,
+ measuresHistory,
+ metricsType,
+ selectedDate,
+ series,
+ showAreas
+ } = this.props;
const { tooltipIdx, tooltipXPos } = this.state;
return (
- <div className="project-activity-graph-container">
- {this.props.isCustom ? (
+ <div className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center">
+ {isCustom && this.props.removeCustomMetric ? (
<GraphsLegendCustom removeMetric={this.props.removeCustomMetric} series={series} />
) : (
<GraphsLegendStatic series={series} />
)}
- <div className="project-activity-graph">
+ <div className="flex-1">
<AutoSizer>
{({ height, width }) => (
<div>
<AdvancedTimeline
- endDate={this.props.graphEndDate}
+ endDate={graphEndDate}
formatYTick={this.formatValue}
height={height}
- leakPeriodDate={this.props.leakPeriodDate}
- metricType={this.props.metricsType}
+ leakPeriodDate={leakPeriodDate}
+ metricType={metricsType}
selectedDate={selectedDate}
series={series}
- showAreas={this.props.showAreas}
- startDate={this.props.graphStartDate}
+ showAreas={showAreas}
+ startDate={graphStartDate}
updateSelectedDate={this.props.updateSelectedDate}
updateTooltip={this.updateTooltip}
updateZoom={this.props.updateGraphZoom}
@@ -100,11 +112,11 @@ export default class GraphHistory extends React.PureComponent<Props, State> {
tooltipIdx !== undefined &&
tooltipXPos !== undefined && (
<GraphsTooltips
- events={this.props.events}
+ events={events}
formatValue={this.formatTooltipValue}
graph={graph}
graphWidth={width}
- measuresHistory={this.props.measuresHistory}
+ measuresHistory={measuresHistory}
selectedDate={selectedDate}
series={series}
tooltipIdx={tooltipIdx}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx
index 7c4fdea6230..2122ed21b21 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx
@@ -17,23 +17,27 @@
* 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 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';
+import { GraphType } from '../../types/project-activity';
+import AddGraphMetric from './AddGraphMetric';
+import './styles.css';
+import { getGraphTypes, isCustomGraph } from './utils';
interface Props {
- addCustomMetric: (metric: string) => void;
- removeCustomMetric: (metric: string) => void;
- graph: string;
+ addCustomMetric?: (metric: string) => void;
+ className?: string;
+ removeCustomMetric?: (metric: string) => void;
+ graph: GraphType;
metrics: T.Metric[];
metricsTypeFilter?: string[];
- selectedMetrics: string[];
+ selectedMetrics?: string[];
updateGraph: (graphType: string) => void;
}
-export default class ProjectActivityGraphsHeader extends React.PureComponent<Props> {
+export default class GraphsHeader extends React.PureComponent<Props> {
handleGraphChange = (option: { value: string }) => {
if (option.value !== this.props.graph) {
this.props.updateGraph(option.value);
@@ -41,32 +45,46 @@ export default class ProjectActivityGraphsHeader extends React.PureComponent<Pro
};
render() {
- const selectOptions = GRAPH_TYPES.map(graph => ({
- label: translate('project_activity.graphs', graph),
- value: graph
+ const {
+ addCustomMetric,
+ className,
+ graph,
+ metrics,
+ metricsTypeFilter,
+ removeCustomMetric,
+ selectedMetrics = []
+ } = this.props;
+
+ const types = getGraphTypes(addCustomMetric === undefined || removeCustomMetric === undefined);
+
+ const selectOptions = types.map(type => ({
+ label: translate('project_activity.graphs', type),
+ value: type
}));
return (
- <header className="page-header">
+ <div className={classNames(className, 'position-relative')}>
<Select
className="pull-left input-medium"
clearable={false}
onChange={this.handleGraphChange}
options={selectOptions}
searchable={false}
- value={this.props.graph}
+ value={graph}
/>
- {isCustomGraph(this.props.graph) && (
- <AddGraphMetric
- addMetric={this.props.addCustomMetric}
- className="pull-left spacer-left"
- metrics={this.props.metrics}
- metricsTypeFilter={this.props.metricsTypeFilter}
- removeMetric={this.props.removeCustomMetric}
- selectedMetrics={this.props.selectedMetrics}
- />
- )}
- </header>
+ {isCustomGraph(graph) &&
+ addCustomMetric !== undefined &&
+ removeCustomMetric !== undefined && (
+ <AddGraphMetric
+ addMetric={addCustomMetric}
+ className="pull-left spacer-left"
+ metrics={metrics}
+ metricsTypeFilter={metricsTypeFilter}
+ removeMetric={removeCustomMetric}
+ selectedMetrics={selectedMetrics}
+ />
+ )}
+ </div>
);
}
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx
index 8ac065c55c5..d19242f24a7 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsHistory.tsx
@@ -21,30 +21,26 @@ 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 { 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[];
- eventFilter: string;
- graph: string;
+ graph: GraphType;
graphs: Serie[][];
graphEndDate?: Date;
graphStartDate?: Date;
leakPeriodDate?: Date;
loading: boolean;
measuresHistory: MeasureHistory[];
- removeCustomMetric: (metric: string) => void;
+ removeCustomMetric?: (metric: string) => void;
selectedDate?: Date;
series: Serie[];
- updateGraphZoom: (from?: Date, to?: Date) => void;
- updateSelectedDate: (selectedDate?: Date) => void;
+ updateGraphZoom?: (from?: Date, to?: Date) => void;
+ updateSelectedDate?: (selectedDate?: Date) => void;
}
interface State {
@@ -69,9 +65,7 @@ export default class GraphsHistory extends React.PureComponent<Props, State> {
const { selectedDate } = this.state;
const { analyses } = this.props;
if (analyses && selectedDate) {
- const analysis = analyses.find(
- analysis => analysis.date.valueOf() === selectedDate.valueOf()
- );
+ const analysis = analyses.find(a => a.date.valueOf() === selectedDate.valueOf());
if (analysis) {
return analysis.events;
}
@@ -89,9 +83,9 @@ export default class GraphsHistory extends React.PureComponent<Props, State> {
if (loading) {
return (
- <div className="project-activity-graph-container">
+ <div className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center">
<div className="text-center">
- <DeferredSpinner className="" loading={loading} />
+ <DeferredSpinner loading={loading} />
</div>
</div>
);
@@ -99,22 +93,30 @@ export default class GraphsHistory extends React.PureComponent<Props, State> {
if (!hasHistoryData(series)) {
return (
- <div className="project-activity-graph-container">
- <div className="note text-center">
- {translate(
- isCustom
- ? 'project_activity.graphs.custom.no_history'
- : 'component_measures.no_history'
- )}
+ <div className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center">
+ <div className="display-flex-center display-flex-justify-center">
+ <img
+ alt="" /* Make screen readers ignore this image; it's purely eye candy. */
+ className="spacer-right"
+ height={52}
+ src={`${getBaseUrl()}/images/activity-chart.svg`}
+ />
+ <div className="big-spacer-left big text-muted" style={{ maxWidth: 300 }}>
+ {translate(
+ isCustom
+ ? 'project_activity.graphs.custom.no_history'
+ : 'component_measures.no_history'
+ )}
+ </div>
</div>
</div>
);
}
const events = this.getSelectedDateEvents();
- const showAreas = ['coverage', 'duplications'].includes(graph);
+ const showAreas = [GraphType.coverage, GraphType.duplications].includes(graph);
return (
- <div className="project-activity-graphs">
- {this.props.graphs.map((series, idx) => (
+ <div className="display-flex-justify-center display-flex-column display-flex-stretch flex-grow">
+ {this.props.graphs.map((graphSeries, idx) => (
<GraphHistory
events={events}
graph={graph}
@@ -124,10 +126,10 @@ export default class GraphsHistory extends React.PureComponent<Props, State> {
key={idx}
leakPeriodDate={this.props.leakPeriodDate}
measuresHistory={this.props.measuresHistory}
- metricsType={getSeriesMetricType(series)}
+ metricsType={getSeriesMetricType(graphSeries)}
removeCustomMetric={this.props.removeCustomMetric}
selectedDate={this.state.selectedDate}
- series={series}
+ series={graphSeries}
showAreas={showAreas}
updateGraphZoom={this.props.updateGraphZoom}
updateSelectedDate={this.props.updateSelectedDate}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendCustom.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendCustom.tsx
index 67b552883c6..aaf71573351 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendCustom.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendCustom.tsx
@@ -20,8 +20,9 @@
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 { Serie } from '../../types/project-activity';
import GraphsLegendItem from './GraphsLegendItem';
+import { hasDataValues } from './utils';
interface Props {
removeMetric: (metric: string) => void;
@@ -30,7 +31,7 @@ interface Props {
export default function GraphsLegendCustom({ removeMetric, series }: Props) {
return (
- <div className="project-activity-graph-legends">
+ <div className="activity-graph-legends">
{series.map((serie, idx) => {
const hasData = hasDataValues(serie);
const legendItem = (
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendItem.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendItem.tsx
index 49052648fca..cd0bcb9bc7a 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendItem.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendItem.tsx
@@ -42,7 +42,7 @@ export default class GraphsLegendItem extends React.PureComponent<Props> {
render() {
const isActionable = this.props.removeMetric != null;
const legendClass = classNames(
- { 'project-activity-graph-legend-actionable': isActionable },
+ { 'activity-graph-legend-actionable': isActionable },
this.props.className
);
return (
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendStatic.tsx
index b3973e58d5e..9d723ad7071 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsLegendStatic.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Serie } from '../utils';
+import { Serie } from '../../types/project-activity';
import GraphsLegendItem from './GraphsLegendItem';
interface Props {
@@ -27,7 +27,7 @@ interface Props {
export default function GraphsLegendStatic({ series }: Props) {
return (
- <div className="project-activity-graph-legends">
+ <div className="activity-graph-legends">
{series.map((serie, idx) => (
<GraphsLegendItem
className="big-spacer-left big-spacer-right"
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltips.tsx
index 60d0ee93743..5cf39c6296e 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltips.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltips.tsx
@@ -20,13 +20,14 @@
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 { 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[];
@@ -93,8 +94,8 @@ export default class GraphsTooltips extends React.PureComponent<Props> {
className="disabled-pointer-events"
placement={placement}
style={{ top, left, width: TOOLTIP_WIDTH }}>
- <div className="project-activity-graph-tooltip">
- <div className="project-activity-graph-tooltip-title spacer-bottom">
+ <div className="activity-graph-tooltip">
+ <div className="activity-graph-tooltip-title spacer-bottom">
<DateTimeFormatter date={this.props.selectedDate} />
</div>
<table className="width-100">
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContent.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContent.tsx
index 32450e15e69..7d9363b8f65 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContent.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContent.tsx
@@ -29,11 +29,11 @@ interface Props {
export default function GraphsTooltipsContent({ name, index, translatedName, value }: Props) {
return (
- <tr className="project-activity-graph-tooltip-line" key={name}>
+ <tr className="activity-graph-tooltip-line" key={name}>
<td className="thin">
<ChartLegendIcon className="spacer-right" index={index} />
</td>
- <td className="project-activity-graph-tooltip-value text-right spacer-right thin">{value}</td>
+ <td className="activity-graph-tooltip-value text-right spacer-right thin">{value}</td>
<td>{translatedName}</td>
</tr>
);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentCoverage.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentCoverage.tsx
index 8a82fd94581..a3eaf9e15d2 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentCoverage.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentCoverage.tsx
@@ -20,9 +20,9 @@
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';
+import { MeasureHistory } from '../../types/project-activity';
-interface Props {
+export interface GraphsTooltipsContentCoverageProps {
addSeparator: boolean;
measuresHistory: MeasureHistory[];
tooltipIdx: number;
@@ -32,7 +32,7 @@ export default function GraphsTooltipsContentCoverage({
addSeparator,
measuresHistory,
tooltipIdx
-}: Props) {
+}: 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]) {
@@ -44,26 +44,22 @@ export default function GraphsTooltipsContentCoverage({
<tbody>
{addSeparator && (
<tr>
- <td className="project-activity-graph-tooltip-separator" colSpan={3}>
+ <td className="activity-graph-tooltip-separator" colSpan={3}>
<hr />
</td>
</tr>
)}
{uncoveredValue && (
- <tr className="project-activity-graph-tooltip-line">
- <td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
- colSpan={2}>
+ <tr className="activity-graph-tooltip-line">
+ <td className="activity-graph-tooltip-value text-right spacer-right thin" colSpan={2}>
{formatMeasure(uncoveredValue, 'SHORT_INT')}
</td>
<td>{translate('metric.uncovered_lines.name')}</td>
</tr>
)}
{coverageValue && (
- <tr className="project-activity-graph-tooltip-line">
- <td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
- colSpan={2}>
+ <tr className="activity-graph-tooltip-line">
+ <td className="activity-graph-tooltip-value text-right spacer-right thin" colSpan={2}>
{formatMeasure(coverageValue, 'PERCENT')}
</td>
<td>{translate('metric.coverage.name')}</td>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentDuplication.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentDuplication.tsx
index a82966d3ee0..956b2fe3b62 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentDuplication.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentDuplication.tsx
@@ -20,9 +20,9 @@
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';
+import { MeasureHistory } from '../../types/project-activity';
-interface Props {
+export interface GraphsTooltipsContentDuplicationProps {
addSeparator: boolean;
measuresHistory: MeasureHistory[];
tooltipIdx: number;
@@ -32,7 +32,7 @@ export default function GraphsTooltipsContentDuplication({
addSeparator,
measuresHistory,
tooltipIdx
-}: Props) {
+}: GraphsTooltipsContentDuplicationProps) {
const duplicationDensity = measuresHistory.find(
measure => measure.metric === 'duplicated_lines_density'
);
@@ -47,15 +47,13 @@ export default function GraphsTooltipsContentDuplication({
<tbody>
{addSeparator && (
<tr>
- <td className="project-activity-graph-tooltip-separator" colSpan={3}>
+ <td className="activity-graph-tooltip-separator" colSpan={3}>
<hr />
</td>
</tr>
)}
- <tr className="project-activity-graph-tooltip-line">
- <td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
- colSpan={2}>
+ <tr className="activity-graph-tooltip-line">
+ <td className="activity-graph-tooltip-value text-right spacer-right thin" colSpan={2}>
{formatMeasure(duplicationDensityValue, 'PERCENT')}
</td>
<td>{translate('metric.duplicated_lines_density.name')}</td>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentEvents.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentEvents.tsx
index b81effd4697..36ff950de11 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentEvents.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentEvents.tsx
@@ -17,6 +17,7 @@
* 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';
@@ -31,17 +32,19 @@ export default function GraphsTooltipsContentEvents({ addSeparator, events }: Pr
<tbody>
{addSeparator && (
<tr>
- <td className="project-activity-graph-tooltip-separator" colSpan={3}>
+ <td className="activity-graph-tooltip-separator" colSpan={3}>
<hr />
</td>
</tr>
)}
- <tr className="project-activity-graph-tooltip-line">
+ <tr className="activity-graph-tooltip-line">
<td colSpan={3}>
<span>{translate('events')}:</span>
{events.map(event => (
<span className="spacer-left" key={event.key}>
- <ProjectEventIcon className={'project-activity-event-icon ' + event.category} />
+ <ProjectEventIcon
+ className={classNames('project-activity-event-icon', event.category)}
+ />
</span>
))}
</td>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentIssues.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentIssues.tsx
index dc2a3c784ad..11f96a5b97e 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsTooltipsContentIssues.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentIssues.tsx
@@ -20,9 +20,9 @@
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';
+import { MeasureHistory } from '../../types/project-activity';
-interface Props {
+export interface GraphsTooltipsContentIssuesProps {
index: number;
measuresHistory: MeasureHistory[];
name: string;
@@ -37,7 +37,7 @@ const METRIC_RATING: T.Dict<string> = {
code_smells: 'sqale_rating'
};
-export default function GraphsTooltipsContentIssues(props: Props) {
+export default function GraphsTooltipsContentIssues(props: GraphsTooltipsContentIssuesProps) {
const rating = props.measuresHistory.find(
measure => measure.metric === METRIC_RATING[props.name]
);
@@ -46,12 +46,12 @@ export default function GraphsTooltipsContentIssues(props: Props) {
}
const ratingValue = rating.history[props.tooltipIdx].value;
return (
- <tr className="project-activity-graph-tooltip-issues-line" key={props.name}>
+ <tr className="activity-graph-tooltip-issues-line" key={props.name}>
<td className="thin">
<ChartLegendIcon className="spacer-right" index={props.index} />
</td>
<td className="text-right spacer-right">
- <span className="project-activity-graph-tooltip-value">{props.value}</span>
+ <span className="activity-graph-tooltip-value">{props.value}</span>
{ratingValue && <Rating className="spacer-left" small={true} value={ratingValue} />}
</td>
<td>{props.translatedName}</td>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.tsx b/server/sonar-web/src/main/js/components/activity-graph/GraphsZoom.tsx
index fc15730d9c3..2777b68186b 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/GraphsZoom.tsx
@@ -20,7 +20,8 @@
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';
+import { Serie } from '../../types/project-activity';
+import { hasHistoryData } from './utils';
interface Props {
graphEndDate?: Date;
@@ -39,7 +40,7 @@ export default function GraphsZoom(props: Props) {
}
return (
- <div className="project-activity-graph-zoom">
+ <div className="activity-graph-zoom">
<AutoSizer disableHeight={true}>
{({ width }) => (
<ZoomTimeLine
diff --git a/server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltipsContent-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetric-test.tsx
index f036ff4038e..d26e8e5f70c 100644
--- a/server/sonar-web/src/main/js/components/preview-graph/__tests__/PreviewGraphTooltipsContent-test.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetric-test.tsx
@@ -17,16 +17,24 @@
* 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'
-};
+import { mockMetric } from '../../../helpers/testMocks';
+import AddGraphMetric from '../AddGraphMetric';
it('should render correctly', () => {
- expect(shallow(<PreviewGraphTooltipsContent {...DEFAULT_PROPS} />)).toMatchSnapshot();
+ expect(shallowRender()).toMatchSnapshot();
});
+
+function shallowRender(props: Partial<AddGraphMetric['props']> = {}) {
+ return shallow<AddGraphMetric>(
+ <AddGraphMetric
+ addMetric={jest.fn()}
+ metrics={[mockMetric()]}
+ removeMetric={jest.fn()}
+ selectedMetrics={[]}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/AddGraphMetricPopup-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetricPopup-test.tsx
index 4803e977878..a555db13bcb 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/AddGraphMetricPopup-test.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetricPopup-test.tsx
@@ -19,7 +19,7 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import MultiSelect from '../../../../../components/common/MultiSelect';
+import MultiSelect from '../../common/MultiSelect';
import AddGraphMetricPopup, { AddGraphMetricPopupProps } from '../AddGraphMetricPopup';
it('should render correctly', () => {
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphHistory-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphHistory-test.tsx
index 269a5d7f281..5d2849dbd15 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphHistory-test.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphHistory-test.tsx
@@ -20,8 +20,8 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { parseDate } from 'sonar-ui-common/helpers/dates';
-import { DEFAULT_GRAPH } from '../../utils';
import GraphHistory from '../GraphHistory';
+import { DEFAULT_GRAPH } from '../utils';
const SERIES = [
{
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsHistory-test.tsx
index 71b58b49cbd..4b0a219746a 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsHistory-test.tsx
@@ -17,11 +17,12 @@
* 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 { DEFAULT_GRAPH } from '../../utils';
import GraphsHistory from '../GraphsHistory';
+import { DEFAULT_GRAPH } from '../utils';
const ANALYSES = [
{
@@ -73,7 +74,6 @@ const SERIES = [
const DEFAULT_PROPS: GraphsHistory['props'] = {
analyses: ANALYSES,
- eventFilter: '',
graph: DEFAULT_GRAPH,
graphs: [SERIES],
leakPeriodDate: parseDate('2017-05-16T13:50:02+0200'),
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendCustom-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendCustom-test.tsx
index 5b213e8141f..5b213e8141f 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendCustom-test.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendCustom-test.tsx
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendItem-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendItem-test.tsx
index b8c2b41dd5b..6553a9f90d4 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendItem-test.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendItem-test.tsx
@@ -19,30 +19,33 @@
*/
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(shallow(<GraphsLegendItem index={2} metric="bugs" name="Bugs" />)).toMatchSnapshot();
-});
-
-it('should render correctly an actionable legend', () => {
+ expect(shallowRender()).toMatchSnapshot('default');
expect(
- shallow(
- <GraphsLegendItem
- className="myclass"
- index={1}
- metric="foo"
- name="Foo"
- removeMetric={() => {}}
- />
- )
- ).toMatchSnapshot();
+ shallowRender({
+ className: 'myclass',
+ index: 1,
+ metric: 'foo',
+ name: 'Foo',
+ removeMetric: jest.fn()
+ })
+ ).toMatchSnapshot('with legend');
+ expect(shallowRender({ showWarning: true })).toMatchSnapshot('with warning');
});
-it('should render correctly legends with warning', () => {
- expect(
- shallow(
- <GraphsLegendItem className="myclass" index={1} metric="foo" name="Foo" showWarning={true} />
- )
- ).toMatchSnapshot();
+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<GraphsLegendItem['props']> = {}) {
+ return shallow<GraphsLegendItem>(
+ <GraphsLegendItem index={2} metric="bugs" name="Bugs" {...props} />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendStatic-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendStatic-test.tsx
index a0492576ddf..a0492576ddf 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendStatic-test.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendStatic-test.tsx
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltips-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltips-test.tsx
index f61e98eff69..57c3f04d2b7 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltips-test.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltips-test.tsx
@@ -17,11 +17,12 @@
* 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 { DEFAULT_GRAPH } from '../../utils';
import GraphsTooltips from '../GraphsTooltips';
+import { DEFAULT_GRAPH } from '../utils';
const SERIES_ISSUES = [
{
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContent-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContent-test.tsx
index cc8fb4689b0..cc8fb4689b0 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContent-test.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContent-test.tsx
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<GraphsTooltipsContentCoverageProps> = {}) {
+ return shallow<GraphsTooltipsContentCoverageProps>(
+ <GraphsTooltipsContentCoverage
+ addSeparator={false}
+ measuresHistory={[
+ {
+ metric: 'coverage',
+ history: [
+ { date: parseDate('2011-10-01T22:01:00.000Z') },
+ { date: parseDate('2011-10-25T10:27:41.000Z'), value: '80.3' }
+ ]
+ },
+ {
+ metric: 'lines_to_cover',
+ history: [
+ { date: parseDate('2011-10-01T22:01:00.000Z'), value: '60545' },
+ { date: parseDate('2011-10-25T10:27:41.000Z'), value: '65215' }
+ ]
+ },
+ {
+ metric: 'uncovered_lines',
+ history: [
+ { date: parseDate('2011-10-01T22:01:00.000Z'), value: '40564' },
+ { date: parseDate('2011-10-25T10:27:41.000Z'), value: '10245' }
+ ]
+ }
+ ]}
+ tooltipIdx={1}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentDuplication-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentDuplication-test.tsx
index bbdc94273f5..93313bb60ed 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentDuplication-test.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentDuplication-test.tsx
@@ -20,30 +20,32 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { parseDate } from 'sonar-ui-common/helpers/dates';
-import GraphsTooltipsContentDuplication from '../GraphsTooltipsContentDuplication';
-
-const MEASURES_DUPLICATION = [
- {
- metric: 'duplicated_lines_density',
- history: [
- { date: parseDate('2011-10-01T22:01:00.000Z') },
- { date: parseDate('2011-10-25T10:27:41.000Z'), value: '10245' }
- ]
- }
-];
-
-const DEFAULT_PROPS = {
- addSeparator: true,
- measuresHistory: MEASURES_DUPLICATION,
- tooltipIdx: 1
-};
+import GraphsTooltipsContentDuplication, {
+ GraphsTooltipsContentDuplicationProps
+} from '../GraphsTooltipsContentDuplication';
it('should render correctly', () => {
- expect(shallow(<GraphsTooltipsContentDuplication {...DEFAULT_PROPS} />)).toMatchSnapshot();
+ expect(shallowRender()).toMatchSnapshot('default');
+ expect(shallowRender({ addSeparator: true })).toMatchSnapshot('with separator');
+ expect(shallowRender({ tooltipIdx: -1 }).type()).toBeNull();
+ expect(shallowRender({ measuresHistory: [] }).type()).toBeNull();
});
-it('should render null when data is missing', () => {
- expect(
- shallow(<GraphsTooltipsContentDuplication {...DEFAULT_PROPS} tooltipIdx={0} />).type()
- ).toBeNull();
-});
+function shallowRender(props: Partial<GraphsTooltipsContentDuplicationProps> = {}) {
+ return shallow<GraphsTooltipsContentDuplicationProps>(
+ <GraphsTooltipsContentDuplication
+ addSeparator={false}
+ measuresHistory={[
+ {
+ metric: 'duplicated_lines_density',
+ history: [
+ { date: parseDate('2011-10-01T22:01:00.000Z') },
+ { date: parseDate('2011-10-25T10:27:41.000Z'), value: '10245' }
+ ]
+ }
+ ]}
+ tooltipIdx={1}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentEvents-test.tsx b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentEvents-test.tsx
index c797e07d434..c797e07d434 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsTooltipsContentEvents-test.tsx
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentEvents-test.tsx
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<GraphsTooltipsContentIssuesProps> = {}) {
+ return shallow<GraphsTooltipsContentIssuesProps>(
+ <GraphsTooltipsContentIssues
+ index={2}
+ measuresHistory={[
+ {
+ metric: 'bugs',
+ history: [
+ { date: parseDate('2011-10-01T22:01:00.000Z'), value: '500' },
+ { date: parseDate('2011-10-25T10:27:41.000Z'), value: '1.2k' }
+ ]
+ },
+ {
+ metric: 'reliability_rating',
+ history: [
+ { date: parseDate('2011-10-01T22:01:00.000Z') },
+ { date: parseDate('2011-10-25T10:27:41.000Z'), value: '5.0' }
+ ]
+ }
+ ]}
+ name="bugs"
+ tooltipIdx={1}
+ translatedName="Bugs"
+ value="1.2k"
+ {...props}
+ />
+ );
+}
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`] = `
+<Dropdown
+ className="display-inline-block"
+ overlay={
+ <AddGraphMetricPopup
+ elements={
+ Array [
+ "coverage",
+ ]
+ }
+ filterSelected={[Function]}
+ onSearch={[Function]}
+ onSelect={[Function]}
+ onUnselect={[Function]}
+ renderLabel={[Function]}
+ selectedElements={Array []}
+ />
+ }
+>
+ <Button
+ className="spacer-left"
+ >
+ <span
+ className="text-ellipsis text-middle"
+ >
+ project_activity.graphs.custom.add
+ </span>
+ <DropdownIcon
+ className="text-top little-spacer-left"
+ />
+ </Button>
+</Dropdown>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/AddGraphMetricPopup-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/AddGraphMetricPopup-test.tsx.snap
index f914a1314a5..f914a1314a5 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/__tests__/__snapshots__/AddGraphMetricPopup-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/AddGraphMetricPopup-test.tsx.snap
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphHistory-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphHistory-test.tsx.snap
index afee3742c9e..cb92ba1524d 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphHistory-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphHistory-test.tsx.snap
@@ -2,7 +2,7 @@
exports[`should correctly render a graph 1`] = `
<div
- className="project-activity-graph-container"
+ className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center"
>
<GraphsLegendStatic
series={
@@ -30,7 +30,7 @@ exports[`should correctly render a graph 1`] = `
}
/>
<div
- className="project-activity-graph"
+ className="flex-1"
>
<AutoSizer>
<Component />
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsHistory-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsHistory-test.tsx.snap
index a057f0e7015..6c121cd5ca5 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsHistory-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsHistory-test.tsx.snap
@@ -2,7 +2,7 @@
exports[`should correctly render a graph 1`] = `
<div
- className="project-activity-graphs"
+ className="display-flex-justify-center display-flex-column display-flex-stretch flex-grow"
>
<GraphHistory
events={Array []}
@@ -46,7 +46,7 @@ exports[`should correctly render a graph 1`] = `
exports[`should correctly render multiple graphs 1`] = `
<div
- className="project-activity-graphs"
+ className="display-flex-justify-center display-flex-column display-flex-stretch flex-grow"
>
<GraphHistory
events={Array []}
@@ -127,24 +127,54 @@ exports[`should correctly render multiple graphs 1`] = `
exports[`should show that there is no history data 1`] = `
<div
- className="project-activity-graph-container"
+ className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center"
>
<div
- className="note text-center"
+ className="display-flex-center display-flex-justify-center"
>
- component_measures.no_history
+ <img
+ alt=""
+ className="spacer-right"
+ height={52}
+ src="/images/activity-chart.svg"
+ />
+ <div
+ className="big-spacer-left big text-muted"
+ style={
+ Object {
+ "maxWidth": 300,
+ }
+ }
+ >
+ component_measures.no_history
+ </div>
</div>
</div>
`;
exports[`should show that there is no history data 2`] = `
<div
- className="project-activity-graph-container"
+ className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center"
>
<div
- className="note text-center"
+ className="display-flex-center display-flex-justify-center"
>
- component_measures.no_history
+ <img
+ alt=""
+ className="spacer-right"
+ height={52}
+ src="/images/activity-chart.svg"
+ />
+ <div
+ className="big-spacer-left big text-muted"
+ style={
+ Object {
+ "maxWidth": 300,
+ }
+ }
+ >
+ component_measures.no_history
+ </div>
</div>
</div>
`;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendCustom-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendCustom-test.tsx.snap
index 230b69d63ac..9dede77b48a 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendCustom-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendCustom-test.tsx.snap
@@ -2,7 +2,7 @@
exports[`should render correctly the list of series 1`] = `
<div
- className="project-activity-graph-legends"
+ className="activity-graph-legends"
>
<span
className="spacer-left spacer-right"
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendItem-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendItem-test.tsx.snap
index 68c3c7d6f24..d374734cb54 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendItem-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendItem-test.tsx.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render correctly a legend 1`] = `
+exports[`should render correctly a legend: default 1`] = `
<span
className=""
>
@@ -16,9 +16,9 @@ exports[`should render correctly a legend 1`] = `
</span>
`;
-exports[`should render correctly an actionable legend 1`] = `
+exports[`should render correctly a legend: with legend 1`] = `
<span
- className="project-activity-graph-legend-actionable myclass"
+ className="activity-graph-legend-actionable myclass"
>
<ChartLegendIcon
className="text-middle spacer-right"
@@ -41,9 +41,9 @@ exports[`should render correctly an actionable legend 1`] = `
</span>
`;
-exports[`should render correctly legends with warning 1`] = `
+exports[`should render correctly a legend: with warning 1`] = `
<span
- className="myclass"
+ className=""
>
<AlertWarnIcon
className="spacer-right"
@@ -51,7 +51,7 @@ exports[`should render correctly legends with warning 1`] = `
<span
className="text-middle"
>
- Foo
+ Bugs
</span>
</span>
`;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendStatic-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendStatic-test.tsx.snap
index da1d0b847ee..d1198d5f498 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendStatic-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendStatic-test.tsx.snap
@@ -2,7 +2,7 @@
exports[`should render correctly the list of series 1`] = `
<div
- className="project-activity-graph-legends"
+ className="activity-graph-legends"
>
<GraphsLegendItem
className="big-spacer-left big-spacer-right"
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltips-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltips-test.tsx.snap
index 65dedadf6b9..d90007b9ca1 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltips-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltips-test.tsx.snap
@@ -13,10 +13,10 @@ exports[`should not add separators if not needed 1`] = `
}
>
<div
- className="project-activity-graph-tooltip"
+ className="activity-graph-tooltip"
>
<div
- className="project-activity-graph-tooltip-title spacer-bottom"
+ className="activity-graph-tooltip-title spacer-bottom"
>
<DateTimeFormatter
date={2011-10-01T22:01:00.000Z}
@@ -49,10 +49,10 @@ exports[`should render correctly for issues graphs 1`] = `
}
>
<div
- className="project-activity-graph-tooltip"
+ className="activity-graph-tooltip"
>
<div
- className="project-activity-graph-tooltip-title spacer-bottom"
+ className="activity-graph-tooltip-title spacer-bottom"
>
<DateTimeFormatter
date={2011-10-01T22:01:00.000Z}
@@ -108,10 +108,10 @@ exports[`should render correctly for random graphs 1`] = `
}
>
<div
- className="project-activity-graph-tooltip"
+ className="activity-graph-tooltip"
>
<div
- className="project-activity-graph-tooltip-title spacer-bottom"
+ className="activity-graph-tooltip-title spacer-bottom"
>
<DateTimeFormatter
date={2011-10-25T10:27:41.000Z}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContent-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContent-test.tsx.snap
index d7ad675e304..4bfd687cf88 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContent-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContent-test.tsx.snap
@@ -2,7 +2,7 @@
exports[`should render correctly 1`] = `
<tr
- className="project-activity-graph-tooltip-line"
+ className="activity-graph-tooltip-line"
key="code_smells"
>
<td
@@ -14,7 +14,7 @@ exports[`should render correctly 1`] = `
/>
</td>
<td
- className="project-activity-graph-tooltip-value text-right spacer-right thin"
+ className="activity-graph-tooltip-value text-right spacer-right thin"
>
1.2k
</td>
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`] = `
+<tbody>
+ <tr
+ className="activity-graph-tooltip-line"
+ >
+ <td
+ className="activity-graph-tooltip-value text-right spacer-right thin"
+ colSpan={2}
+ >
+ 10short_number_suffix.k
+ </td>
+ <td>
+ metric.uncovered_lines.name
+ </td>
+ </tr>
+ <tr
+ className="activity-graph-tooltip-line"
+ >
+ <td
+ className="activity-graph-tooltip-value text-right spacer-right thin"
+ colSpan={2}
+ >
+ 80.3%
+ </td>
+ <td>
+ metric.coverage.name
+ </td>
+ </tr>
+</tbody>
+`;
+
+exports[`should render correctly: with separator 1`] = `
+<tbody>
+ <tr>
+ <td
+ className="activity-graph-tooltip-separator"
+ colSpan={3}
+ >
+ <hr />
+ </td>
+ </tr>
+ <tr
+ className="activity-graph-tooltip-line"
+ >
+ <td
+ className="activity-graph-tooltip-value text-right spacer-right thin"
+ colSpan={2}
+ >
+ 10short_number_suffix.k
+ </td>
+ <td>
+ metric.uncovered_lines.name
+ </td>
+ </tr>
+ <tr
+ className="activity-graph-tooltip-line"
+ >
+ <td
+ className="activity-graph-tooltip-value text-right spacer-right thin"
+ colSpan={2}
+ >
+ 80.3%
+ </td>
+ <td>
+ metric.coverage.name
+ </td>
+ </tr>
+</tbody>
+`;
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`] = `
+<tbody>
+ <tr
+ className="activity-graph-tooltip-line"
+ >
+ <td
+ className="activity-graph-tooltip-value text-right spacer-right thin"
+ colSpan={2}
+ >
+ 10,245.0%
+ </td>
+ <td>
+ metric.duplicated_lines_density.name
+ </td>
+ </tr>
+</tbody>
+`;
+
+exports[`should render correctly: with separator 1`] = `
+<tbody>
+ <tr>
+ <td
+ className="activity-graph-tooltip-separator"
+ colSpan={3}
+ >
+ <hr />
+ </td>
+ </tr>
+ <tr
+ className="activity-graph-tooltip-line"
+ >
+ <td
+ className="activity-graph-tooltip-value text-right spacer-right thin"
+ colSpan={2}
+ >
+ 10,245.0%
+ </td>
+ <td>
+ metric.duplicated_lines_density.name
+ </td>
+ </tr>
+</tbody>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.tsx.snap b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.tsx.snap
index 1df7668557e..b77d2327dac 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.tsx.snap
@@ -4,14 +4,14 @@ exports[`should render correctly 1`] = `
<tbody>
<tr>
<td
- className="project-activity-graph-tooltip-separator"
+ className="activity-graph-tooltip-separator"
colSpan={3}
>
<hr />
</td>
</tr>
<tr
- className="project-activity-graph-tooltip-line"
+ className="activity-graph-tooltip-line"
>
<td
colSpan={3}
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`] = `
+<tr
+ className="activity-graph-tooltip-issues-line"
+ key="bugs"
+>
+ <td
+ className="thin"
+ >
+ <ChartLegendIcon
+ className="spacer-right"
+ index={2}
+ />
+ </td>
+ <td
+ className="text-right spacer-right"
+ >
+ <span
+ className="activity-graph-tooltip-value"
+ >
+ 1.2k
+ </span>
+ <Rating
+ className="spacer-left"
+ small={true}
+ value="5.0"
+ />
+ </td>
+ <td>
+ Bugs
+ </td>
+</tr>
+`;
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<string[]> = {
+ [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<string[]> = {
+ [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<T.Metric>,
+ 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<T.Metric>) {
+ 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<T.Metric>;
- project: string;
- renderWhenEmpty?: () => React.ReactNode;
- router: Pick<Router, 'push'>;
-}
-
-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<Props, State> {
- 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<T.Metric>
- ) => {
- 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 (
- <AutoSizer disableHeight={true}>
- {({ width }) => (
- <div>
- <AdvancedTimeline
- height={80}
- hideGrid={true}
- hideXAxis={true}
- metricType={getSeriesMetricType(series)}
- padding={GRAPH_PADDING}
- series={series}
- showAreas={['coverage', 'duplications'].includes(graph)}
- updateTooltip={this.updateTooltip}
- width={width}
- />
- {selectedDate !== undefined &&
- tooltipXPos !== undefined &&
- tooltipIdx !== undefined && (
- <PreviewGraphTooltips
- formatValue={this.formatValue}
- graph={graph}
- graphWidth={width}
- selectedDate={selectedDate}
- series={series}
- tooltipIdx={tooltipIdx}
- tooltipPos={tooltipXPos}
- />
- )}
- </div>
- )}
- </AutoSizer>
- );
- }
-
- render() {
- const { series } = this.state;
- if (!hasHistoryDataValue(series)) {
- return this.props.renderWhenEmpty ? this.props.renderWhenEmpty() : null;
- }
-
- return (
- <div
- aria-label={translate('overview.project_activity.click_to_see')}
- className="overview-analysis-graph big-spacer-bottom spacer-top"
- onClick={this.handleClick}
- role="link"
- tabIndex={0}>
- {this.renderTimeline()}
- </div>
- );
- }
-}
-
-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<Props> {
- 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 (
- <Popup
- className="overview-analysis-graph-popup disabled-pointer-events"
- placement={placement}
- style={{ top, left, width: TOOLTIP_WIDTH }}>
- <div className="overview-analysis-graph-tooltip">
- <div className="overview-analysis-graph-tooltip-title">
- <DateFormatter date={this.props.selectedDate} long={true} />
- </div>
- <table className="width-100">
- <tbody>
- {this.props.series.map((serie, idx) => {
- const point = serie.data[tooltipIdx];
- if (!point || (!point.y && point.y !== 0)) {
- return null;
- }
- return (
- <PreviewGraphTooltipsContent
- index={idx}
- key={serie.name}
- translatedName={serie.translatedName}
- value={this.props.formatValue(point.y)}
- />
- );
- })}
- </tbody>
- </table>
- </div>
- </Popup>
- );
- }
-}
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(
- <PreviewGraphTooltips
- {...DEFAULT_PROPS}
- graph="random"
- selectedDate={parseDate('2011-10-25T10:27:41.000Z')}
- tooltipIdx={1}
- />
- )
- ).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`] = `
-<Popup
- className="overview-analysis-graph-popup disabled-pointer-events"
- placement="left-top"
- style={
- Object {
- "left": -135,
- "top": 16,
- "width": 160,
- }
- }
->
- <div
- className="overview-analysis-graph-tooltip"
- >
- <div
- className="overview-analysis-graph-tooltip-title"
- >
- <DateFormatter
- date={2011-10-25T10:27:41.000Z}
- long={true}
- />
- </div>
- <table
- className="width-100"
- >
- <tbody>
- <PreviewGraphTooltipsContent
- index={0}
- key="code_smells"
- translatedName="Code Smells"
- value="Formated.15"
- />
- <PreviewGraphTooltipsContent
- index={1}
- key="bugs"
- translatedName="Bugs"
- value="Formated.0"
- />
- <PreviewGraphTooltipsContent
- index={2}
- key="vulnerabilities"
- translatedName="Vulnerabilities"
- value="Formated.1"
- />
- </tbody>
- </table>
- </div>
-</Popup>
-`;
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`] = `
-<tr
- className="overview-analysis-graph-tooltip-line"
->
- <td
- className="thin"
- >
- <ChartLegendIcon
- className="little-spacer-right"
- index={1}
- />
- </td>
- <td
- className="overview-analysis-graph-tooltip-value text-right little-spacer-right thin"
- >
- 1.2k
- </td>
- <td>
- <div
- className="text-ellipsis overview-analysis-graph-tooltip-description"
- >
- Code Smells
- </div>
- </td>
-</tr>
-`;
diff --git a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltipsContent.tsx b/server/sonar-web/src/main/js/types/project-activity.ts
index 29cfe3d3dc1..87128d02154 100644
--- a/server/sonar-web/src/main/js/components/preview-graph/PreviewGraphTooltipsContent.tsx
+++ b/server/sonar-web/src/main/js/types/project-activity.ts
@@ -17,29 +17,31 @@
* 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';
+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[];
+}
-interface Props {
- index: number;
+export interface Serie {
+ data: Point[];
+ name: string;
translatedName: string;
- value: string;
+ type: string;
}
-export default function PreviewGraphTooltipsContent({ index, translatedName, value }: Props) {
- return (
- <tr className="overview-analysis-graph-tooltip-line">
- <td className="thin">
- <ChartLegendIcon className="little-spacer-right" index={index} />
- </td>
- <td className="overview-analysis-graph-tooltip-value text-right little-spacer-right thin">
- {value}
- </td>
- <td>
- <div className="text-ellipsis overview-analysis-graph-tooltip-description">
- {translatedName}
- </div>
- </td>
- </tr>
- );
+export interface Point {
+ x: Date;
+ y: number | string | undefined;
}