<div className={classNames(className, 'position-relative')}>
<div className="display-flex-end">
<div className="display-flex-column">
- <label className="text-bold little-spacer-bottom" htmlFor="graph-select">
+ <label className="text-bold little-spacer-bottom" id="graph-select-label">
{translate('project_activity.graphs.choose_type')}
</label>
<Select
- id="graph-select"
+ aria-labelledby="graph-select-label"
className="input-medium"
isSearchable={false}
onChange={this.handleGraphChange}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { UserEvent } from '@testing-library/user-event/dist/types/setup';
+import { times } from 'lodash';
+import * as React from 'react';
+import selectEvent from 'react-select-event';
+import { byLabelText, byPlaceholderText, byRole, byText } from 'testing-library-selector';
+import { parseDate } from '../../../helpers/dates';
+import {
+ mockAnalysisEvent,
+ mockHistoryItem,
+ mockMeasureHistory,
+ mockParsedAnalysis
+} from '../../../helpers/mocks/project-activity';
+import { mockMetric } from '../../../helpers/testMocks';
+import { renderComponent } from '../../../helpers/testReactTestingUtils';
+import { MetricKey } from '../../../types/metrics';
+import { GraphType, MeasureHistory } from '../../../types/project-activity';
+import { Metric } from '../../../types/types';
+import GraphsHeader from '../GraphsHeader';
+import GraphsHistory from '../GraphsHistory';
+import { generateSeries, getDisplayedHistoryMetrics, splitSeriesInGraphs } from '../utils';
+
+const ui = {
+ // Graph types.
+ graphTypeSelect: byLabelText('project_activity.graphs.choose_type'),
+
+ // Add metrics.
+ addMetricBtn: byRole('button', { name: 'project_activity.graphs.custom.add' }),
+ bugsCheckbox: byRole('checkbox', { name: MetricKey.bugs }),
+ newBugsCheckbox: byRole('checkbox', { name: MetricKey.new_bugs }),
+ burnedBudgetCheckbox: byRole('checkbox', { name: MetricKey.burned_budget }),
+ vulnerabilityCheckbox: byRole('checkbox', { name: MetricKey.vulnerabilities }),
+ hiddenOptionsAlert: byText('project_activity.graphs.custom.type_x_message', {
+ exact: false
+ }),
+ maxOptionsAlert: byText('project_activity.graphs.custom.add_metric_info'),
+ filterMetrics: byPlaceholderText('search.search_for_metrics'),
+
+ // Graphs.
+ graphs: byLabelText('project_activity.graphs.explanation_x', { exact: false }),
+ noDataText: byText('project_activity.graphs.custom.no_history'),
+
+ // Date filters.
+ fromDateInput: byLabelText('from_date'),
+ toDateInput: byLabelText('to_date'),
+ submitDatesBtn: byRole('button', { name: 'Submit dates' }),
+
+ // Data in table.
+ openInTableBtn: byRole('button', { name: 'project_activity.graphs.open_in_table' }),
+ closeDataTableBtn: byRole('button', { name: 'close' }),
+ dataTable: byRole('table'),
+ dataTableRows: byRole('row'),
+ dataTableColHeaders: byRole('columnheader'),
+ onlyFirst100Text: byText('project_activity.graphs.data_table.max_lines_warning.100'),
+ noDataTableText: byText('project_activity.graphs.data_table.no_data_warning_check_dates_x', {
+ exact: false
+ })
+};
+
+it('should correctly handle adding/removing custom metrics', async () => {
+ const user = userEvent.setup();
+ renderActivityGraph();
+
+ // Change graph type to "Custom".
+ await changeGraphType(GraphType.custom);
+
+ // Open the "Add metrics" dropdown button; select some metrics.
+ await toggleAddMetrics(user);
+
+ // We should not see DATA type or New Code metrics.
+ expect(ui.newBugsCheckbox.query()).not.toBeInTheDocument();
+ expect(ui.burnedBudgetCheckbox.query()).not.toBeInTheDocument();
+
+ // Select 3 Int types.
+ await clickOnMetric(user, MetricKey.bugs);
+ await clickOnMetric(user, MetricKey.code_smells);
+ await clickOnMetric(user, MetricKey.confirmed_issues);
+ // Select 1 Percent type.
+ await clickOnMetric(user, MetricKey.coverage);
+
+ // We should see 2 graphs, correctly labelled.
+ expect(ui.graphs.getAll()).toHaveLength(2);
+
+ // We cannot select anymore Int types. It should hide options, and show an alert.
+ expect(ui.vulnerabilityCheckbox.query()).not.toBeInTheDocument();
+ expect(ui.hiddenOptionsAlert.get()).toBeInTheDocument();
+
+ // Select 2 more Percent types.
+ await clickOnMetric(user, MetricKey.duplicated_lines_density);
+ await clickOnMetric(user, MetricKey.test_success_density);
+
+ // We cannot select anymore options. It should disable all remaining options, and
+ // show a different alert.
+ expect(ui.maxOptionsAlert.get()).toBeInTheDocument();
+ // See https://github.com/testing-library/jest-dom/issues/144 for why we cannot
+ // use isDisabled().
+ expect(ui.vulnerabilityCheckbox.get()).toHaveAttribute('aria-disabled', 'true');
+
+ // Disable a few options.
+ await clickOnMetric(user, MetricKey.bugs);
+ await clickOnMetric(user, MetricKey.code_smells);
+ await clickOnMetric(user, MetricKey.coverage);
+
+ // Search for option.
+ await searchForMetric(user, 'bug');
+ expect(ui.bugsCheckbox.get()).toBeInTheDocument();
+ expect(ui.vulnerabilityCheckbox.query()).not.toBeInTheDocument();
+ toggleAddMetrics(user);
+
+ // Disable final metrics by clicking on the legend items.
+ await removeMetric(user, MetricKey.confirmed_issues);
+ await removeMetric(user, MetricKey.duplicated_lines_density);
+ await removeMetric(user, MetricKey.test_success_density);
+
+ // Should show message that there's no data to be rendered.
+ expect(ui.noDataText.get()).toBeInTheDocument();
+});
+
+it('should render correctly when loading', async () => {
+ renderActivityGraph({ loading: true });
+ expect(await screen.findByLabelText('loading')).toBeInTheDocument();
+});
+
+it('shows the same data in a table', async () => {
+ const user = userEvent.setup();
+ renderActivityGraph();
+
+ await user.click(ui.openInTableBtn.get());
+ expect(ui.dataTable.get()).toBeInTheDocument();
+ expect(ui.dataTableColHeaders.getAll()).toHaveLength(5);
+ expect(ui.dataTableRows.getAll()).toHaveLength(101);
+ expect(screen.getByText('event.category.QUALITY_GATE', { exact: false })).toBeInTheDocument();
+ expect(screen.getByText('event.category.VERSION', { exact: false })).toBeInTheDocument();
+ expect(
+ screen.getByText('event.category.DEFINITION_CHANGE', { exact: false })
+ ).toBeInTheDocument();
+ expect(ui.onlyFirst100Text.get()).toBeInTheDocument();
+
+ // Change graph type and dates, check table updates correctly.
+ await user.click(ui.closeDataTableBtn.get());
+ await changeGraphType(GraphType.coverage);
+
+ await user.click(ui.openInTableBtn.get());
+ expect(ui.dataTable.get()).toBeInTheDocument();
+ expect(ui.dataTableColHeaders.getAll()).toHaveLength(4);
+ expect(ui.dataTableRows.getAll()).toHaveLength(101);
+});
+
+it('shows the same data in a table when filtered by date', async () => {
+ const user = userEvent.setup();
+ renderActivityGraph({
+ graphStartDate: parseDate('2017-01-01'),
+ graphEndDate: parseDate('2019-01-01')
+ });
+
+ await user.click(ui.openInTableBtn.get());
+ expect(ui.dataTable.get()).toBeInTheDocument();
+ expect(ui.dataTableColHeaders.getAll()).toHaveLength(5);
+ expect(ui.dataTableRows.getAll()).toHaveLength(2);
+ expect(ui.onlyFirst100Text.query()).not.toBeInTheDocument();
+});
+
+async function changeGraphType(type: GraphType) {
+ await selectEvent.select(ui.graphTypeSelect.get(), [`project_activity.graphs.${type}`]);
+}
+
+async function toggleAddMetrics(user: UserEvent) {
+ await user.click(ui.addMetricBtn.get());
+}
+
+async function clickOnMetric(user: UserEvent, name: MetricKey) {
+ await user.click(screen.getByRole('checkbox', { name }));
+}
+
+async function searchForMetric(user: UserEvent, text: string) {
+ await user.type(ui.filterMetrics.get(), text);
+}
+
+async function removeMetric(user: UserEvent, metric: MetricKey) {
+ await user.click(
+ screen.getByRole('button', { name: `project_activity.graphs.custom.remove_metric.${metric}` })
+ );
+}
+
+function renderActivityGraph(
+ graphsHistoryProps: Partial<GraphsHistory['props']> = {},
+ graphsHeaderProps: Partial<GraphsHeader['props']> = {}
+) {
+ const MAX_GRAPHS = 2;
+ const MAX_SERIES_PER_GRAPH = 3;
+ const HISTORY_COUNT = 100;
+
+ function ActivityGraph() {
+ const [selectedMetrics, setSelectedMetrics] = React.useState<string[]>([]);
+ const [graph, setGraph] = React.useState(GraphType.issues);
+ const [selectedDate, setSelectedDate] = React.useState<Date | undefined>(undefined);
+ const [fromDate, setFromDate] = React.useState<Date | undefined>(undefined);
+ const [toDate, setToDate] = React.useState<Date | undefined>(undefined);
+
+ const measuresHistory: MeasureHistory[] = [];
+ const metrics: Metric[] = [];
+ [
+ MetricKey.bugs,
+ MetricKey.code_smells,
+ MetricKey.confirmed_issues,
+ MetricKey.vulnerabilities,
+ MetricKey.blocker_violations,
+ MetricKey.lines_to_cover,
+ MetricKey.uncovered_lines,
+ MetricKey.coverage,
+ MetricKey.duplicated_lines_density,
+ MetricKey.test_success_density
+ ].forEach(metric => {
+ const history = times(HISTORY_COUNT, i => {
+ const date = parseDate('2016-01-01T00:00:00+0200');
+ date.setDate(date.getDate() + i);
+ return mockHistoryItem({ date, value: i.toString() });
+ });
+ history.push(
+ mockHistoryItem({ date: parseDate('2018-10-27T12:21:15+0200') }),
+ mockHistoryItem({ date: parseDate('2020-10-27T16:33:50+0200') })
+ );
+ measuresHistory.push(mockMeasureHistory({ metric, history }));
+ metrics.push(
+ mockMetric({
+ key: metric,
+ name: metric,
+ type: metric.includes('_density') || metric === MetricKey.coverage ? 'PERCENT' : 'INT'
+ })
+ );
+ });
+
+ // The following should be filtered out, and not be suggested as options.
+ metrics.push(
+ mockMetric({ key: MetricKey.new_bugs, name: MetricKey.new_bugs, type: 'INT' }),
+ mockMetric({ key: MetricKey.burned_budget, name: MetricKey.burned_budget, type: 'DATA' })
+ );
+
+ const series = generateSeries(
+ measuresHistory,
+ graph,
+ metrics,
+ getDisplayedHistoryMetrics(graph, selectedMetrics)
+ );
+ const graphs = splitSeriesInGraphs(series, MAX_GRAPHS, MAX_SERIES_PER_GRAPH);
+ const metricsTypeFilter =
+ graphs.length < MAX_GRAPHS
+ ? undefined
+ : graphs.filter(graph => graph.length < MAX_SERIES_PER_GRAPH).map(graph => graph[0].type);
+
+ const addCustomMetric = (metricKey: string) => {
+ setSelectedMetrics([...selectedMetrics, metricKey]);
+ };
+
+ const removeCustomMetric = (metricKey: string) => {
+ setSelectedMetrics(selectedMetrics.filter(m => m !== metricKey));
+ };
+
+ const updateGraph = (graphType: string) => {
+ setGraph(graphType as GraphType);
+ };
+
+ const updateSelectedDate = (date?: Date) => {
+ setSelectedDate(date);
+ };
+
+ const updateFromToDates = (from?: Date, to?: Date) => {
+ setFromDate(from);
+ setToDate(to);
+ };
+
+ return (
+ <>
+ <GraphsHeader
+ addCustomMetric={addCustomMetric}
+ graph={graph}
+ metrics={metrics}
+ metricsTypeFilter={metricsTypeFilter}
+ removeCustomMetric={removeCustomMetric}
+ selectedMetrics={selectedMetrics}
+ updateGraph={updateGraph}
+ {...graphsHeaderProps}
+ />
+ <GraphsHistory
+ analyses={[
+ mockParsedAnalysis({
+ date: parseDate('2018-10-27T12:21:15+0200'),
+ events: [
+ mockAnalysisEvent({ key: '1' }),
+ mockAnalysisEvent({
+ key: '2',
+ category: 'VERSION',
+ description: undefined,
+ qualityGate: undefined
+ }),
+ mockAnalysisEvent({
+ key: '3',
+ category: 'DEFINITION_CHANGE',
+ definitionChange: {
+ projects: [{ changeType: 'ADDED', key: 'foo', name: 'Foo' }]
+ },
+ qualityGate: undefined
+ })
+ ]
+ })
+ ]}
+ graph={graph}
+ graphEndDate={toDate}
+ graphStartDate={fromDate}
+ graphs={graphs}
+ loading={false}
+ measuresHistory={[]}
+ removeCustomMetric={removeCustomMetric}
+ selectedDate={selectedDate}
+ series={series}
+ updateGraphZoom={updateFromToDates}
+ updateSelectedDate={updateSelectedDate}
+ {...graphsHistoryProps}
+ />
+ </>
+ );
+ }
+
+ return renderComponent(<ActivityGraph />);
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import { mockMetric } from '../../../helpers/testMocks';
-import AddGraphMetric from '../AddGraphMetric';
-
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<AddGraphMetric['props']> = {}) {
- return shallow<AddGraphMetric>(
- <AddGraphMetric
- addMetric={jest.fn()}
- metrics={[mockMetric()]}
- removeMetric={jest.fn()}
- selectedMetrics={[]}
- {...props}
- />
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import MultiSelect from '../../common/MultiSelect';
-import AddGraphMetricPopup, { AddGraphMetricPopupProps } from '../AddGraphMetricPopup';
-
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should render correctly whith 6+ selected elements', () => {
- const selectedElements = ['1', '2', '3', '4', '5', '6'];
- expect(shallowRender({ selectedElements })).toMatchSnapshot();
-});
-
-it('should render correctly with type filter', () => {
- const metricsTypeFilter = ['filter1', 'filter2'];
- expect(shallowRender({ metricsTypeFilter })).toMatchSnapshot();
-});
-
-it('should prevent selection of unknown element', () => {
- const elements = ['1', '2', '3'];
- const onSelect = jest.fn();
- const wrapper = shallowRender({ elements, onSelect });
- wrapper
- .find(MultiSelect)
- .props()
- .onSelect('unknown');
-
- expect(onSelect).not.toHaveBeenCalled();
-});
-
-function shallowRender(overrides: Partial<AddGraphMetricPopupProps> = {}) {
- return shallow(
- <AddGraphMetricPopup
- elements={[]}
- filterSelected={jest.fn()}
- onSearch={jest.fn()}
- onSelect={jest.fn()}
- onUnselect={jest.fn()}
- renderLabel={element => element}
- selectedElements={[]}
- {...overrides}
- />
- );
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { screen } from '@testing-library/react';
+import { times } from 'lodash';
+import * as React from 'react';
+import { parseDate } from '../../../helpers/dates';
+import { mockHistoryItem, mockMeasureHistory } from '../../../helpers/mocks/project-activity';
+import { mockMetric } from '../../../helpers/testMocks';
+import { renderComponent } from '../../../helpers/testReactTestingUtils';
+import { MetricKey } from '../../../types/metrics';
+import { GraphType, MeasureHistory } from '../../../types/project-activity';
+import { Metric } from '../../../types/types';
+import DataTableModal, { DataTableModalProps } from '../DataTableModal';
+import { generateSeries, getDisplayedHistoryMetrics } from '../utils';
+
+it('should render correctly if there are no series', () => {
+ renderDataTableModal({ series: [] });
+ expect(
+ screen.getByText('project_activity.graphs.data_table.no_data_warning')
+ ).toBeInTheDocument();
+});
+
+it('should render correctly if there is too much data', () => {
+ renderDataTableModal({ series: mockSeries(101) });
+ expect(
+ screen.getByText('project_activity.graphs.data_table.max_lines_warning.100')
+ ).toBeInTheDocument();
+});
+
+it('should render correctly if there is no data and we have a start date', () => {
+ renderDataTableModal({ graphStartDate: parseDate('3022-01-01') });
+ expect(
+ screen.getByText('project_activity.graphs.data_table.no_data_warning_check_dates_x', {
+ exact: false
+ })
+ ).toBeInTheDocument();
+});
+
+it('should render correctly if there is no data and we have an end date', () => {
+ renderDataTableModal({ graphEndDate: parseDate('2015-01-01') });
+ expect(
+ screen.getByText('project_activity.graphs.data_table.no_data_warning_check_dates_y', {
+ exact: false
+ })
+ ).toBeInTheDocument();
+});
+
+it('should render correctly if there is no data and we have a date range', () => {
+ renderDataTableModal({
+ graphEndDate: parseDate('2015-01-01'),
+ graphStartDate: parseDate('2014-01-01')
+ });
+ expect(
+ screen.getByText('project_activity.graphs.data_table.no_data_warning_check_dates_x_y', {
+ exact: false
+ })
+ ).toBeInTheDocument();
+});
+
+function renderDataTableModal(props: Partial<DataTableModalProps> = {}) {
+ return renderComponent(
+ <DataTableModal analyses={[]} series={mockSeries()} onClose={jest.fn()} {...props} />
+ );
+}
+
+function mockSeries(n = 10) {
+ const measuresHistory: MeasureHistory[] = [];
+ const metrics: Metric[] = [];
+ [MetricKey.bugs, MetricKey.code_smells, MetricKey.vulnerabilities].forEach(metric => {
+ const history = times(n, i => {
+ const date = parseDate('2016-01-01T00:00:00+0200');
+ date.setDate(date.getDate() + 365 * i);
+ return mockHistoryItem({ date, value: i.toString() });
+ });
+ measuresHistory.push(mockMeasureHistory({ metric, history }));
+ metrics.push(
+ mockMetric({
+ key: metric,
+ name: metric,
+ type: 'INT'
+ })
+ );
+ });
+
+ return generateSeries(
+ measuresHistory,
+ GraphType.issues,
+ metrics,
+ getDisplayedHistoryMetrics(GraphType.issues, [
+ MetricKey.bugs,
+ MetricKey.code_smells,
+ MetricKey.vulnerabilities
+ ])
+ );
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 { mockAnalysisEvent } from '../../../helpers/mocks/project-activity';
-import { BranchLike } from '../../../types/branch-like';
-import EventInner, { EventInnerProps } from '../EventInner';
-
-jest.mock('../../../app/components/componentContext/ComponentContext', () => {
- const { mockBranch } = jest.requireActual('../../../helpers/mocks/branch-like');
- return {
- ComponentContext: {
- Consumer: ({
- children
- }: {
- children: (props: { branchLike: BranchLike }) => React.ReactNode;
- }) => {
- return children({ branchLike: mockBranch() });
- }
- }
- };
-});
-
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot('default');
- expect(
- shallowRender({
- event: mockAnalysisEvent({
- category: 'VERSION',
- description: undefined,
- qualityGate: undefined
- })
- })
- ).toMatchSnapshot('no description');
- expect(shallowRender({ event: mockAnalysisEvent() })).toMatchSnapshot('rich quality gate');
- expect(
- shallowRender({
- event: mockAnalysisEvent({
- category: 'DEFINITION_CHANGE',
- definitionChange: {
- projects: [{ changeType: 'ADDED', key: 'foo', name: 'Foo' }]
- },
- qualityGate: undefined
- })
- })
- .find('Consumer')
- .dive()
- ).toMatchSnapshot('definition change');
-});
-
-function shallowRender(props: Partial<EventInnerProps> = {}) {
- return shallow(
- <EventInner
- event={mockAnalysisEvent({ category: 'VERSION', qualityGate: undefined })}
- {...props}
- />
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 '../../../helpers/dates';
-import GraphHistory from '../GraphHistory';
-import { DEFAULT_GRAPH } from '../utils';
-
-const SERIES = [
- {
- name: 'bugs',
- translatedName: 'metric.bugs.name',
- data: [
- { x: parseDate('2016-10-27T16:33:50+0200'), y: 5 },
- { x: parseDate('2016-10-27T12:21:15+0200'), y: 16 },
- { x: parseDate('2016-10-26T12:17:29+0200'), y: 12 }
- ],
- type: 'INT'
- }
-];
-
-it('should correctly render a graph', () => {
- expect(shallowRender()).toMatchSnapshot();
- expect(shallowRender({ isCustom: true })).toMatchSnapshot('custom');
-});
-
-function shallowRender(overrides: Partial<GraphHistory['props']> = {}) {
- return shallow(
- <GraphHistory
- analyses={[]}
- ariaLabel="foo"
- graph={DEFAULT_GRAPH}
- leakPeriodDate={parseDate('2017-05-16T13:50:02+0200')}
- isCustom={false}
- measuresHistory={[]}
- metricsType="INT"
- removeCustomMetric={jest.fn()}
- showAreas={true}
- series={SERIES}
- updateGraphZoom={jest.fn()}
- updateSelectedDate={jest.fn()}
- updateTooltip={jest.fn()}
- {...overrides}
- />
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 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 '../../../helpers/dates';
-import GraphsHistory from '../GraphsHistory';
-import { DEFAULT_GRAPH } from '../utils';
-
-const ANALYSES = [
- {
- key: 'A1',
- date: parseDate('2016-10-27T16:33:50+0200'),
- events: [
- {
- key: 'E1',
- category: 'VERSION',
- name: '6.5-SNAPSHOT'
- }
- ]
- },
- {
- key: 'A2',
- date: parseDate('2016-10-27T12:21:15+0200'),
- events: []
- },
- {
- key: 'A3',
- date: parseDate('2016-10-26T12:17:29+0200'),
- events: [
- {
- key: 'E2',
- category: 'OTHER',
- name: 'foo'
- },
- {
- key: 'E3',
- category: 'VERSION',
- name: '6.4'
- }
- ]
- }
-];
-
-const SERIES = [
- {
- name: 'bugs',
- translatedName: 'metric.bugs.name',
- data: [
- { x: parseDate('2016-10-27T16:33:50+0200'), y: 5 },
- { x: parseDate('2016-10-27T12:21:15+0200'), y: 16 },
- { x: parseDate('2016-10-26T12:17:29+0200'), y: 12 }
- ],
- type: 'INT'
- }
-];
-
-const DEFAULT_PROPS: GraphsHistory['props'] = {
- analyses: ANALYSES,
- graph: DEFAULT_GRAPH,
- graphs: [SERIES],
- leakPeriodDate: parseDate('2017-05-16T13:50:02+0200'),
- loading: false,
- measuresHistory: [],
- removeCustomMetric: () => {},
- series: SERIES,
- updateGraphZoom: () => {},
- updateSelectedDate: () => {}
-};
-
-it('should correctly render a graph', () => {
- expect(shallow(<GraphsHistory {...DEFAULT_PROPS} />)).toMatchSnapshot();
-});
-
-it('should correctly render multiple graphs', () => {
- expect(shallow(<GraphsHistory {...DEFAULT_PROPS} graphs={[SERIES, SERIES]} />)).toMatchSnapshot();
-});
-
-it('should show a loading view instead of the graph', () => {
- expect(
- shallow(<GraphsHistory {...DEFAULT_PROPS} loading={true} />).find('DeferredSpinner')
- ).toHaveLength(1);
-});
-
-it('should show that there is no history data', () => {
- expect(shallow(<GraphsHistory {...DEFAULT_PROPS} series={[]} />)).toMatchSnapshot();
- expect(
- shallow(
- <GraphsHistory
- {...DEFAULT_PROPS}
- series={[
- {
- name: 'bugs',
- translatedName: 'metric.bugs.name',
- data: [{ x: parseDate('2016-10-27T16:33:50+0200'), y: undefined }],
- type: 'INT'
- }
- ]}
- />
- )
- ).toMatchSnapshot();
-});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<Dropdown
- className="display-inline-block"
- closeOnClick={false}
- closeOnClickOutside={true}
- 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>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<div
- className="menu abs-width-300"
->
- <MultiSelect
- allowNewElements={false}
- allowSelection={true}
- elements={Array []}
- filterSelected={[MockFunction]}
- footerNode=""
- legend="project_activity.graphs.custom.select_metric"
- listSize={0}
- onSearch={[MockFunction]}
- onSelect={[Function]}
- onUnselect={[MockFunction]}
- placeholder="search.search_for_metrics"
- renderLabel={[Function]}
- selectedElements={Array []}
- validateSearchInput={[Function]}
- />
-</div>
-`;
-
-exports[`should render correctly whith 6+ selected elements 1`] = `
-<div
- className="menu abs-width-300"
->
- <MultiSelect
- allowNewElements={false}
- allowSelection={false}
- elements={Array []}
- filterSelected={[MockFunction]}
- footerNode={
- <Alert
- className="spacer-left spacer-right spacer-top"
- variant="info"
- >
- project_activity.graphs.custom.add_metric_info
- </Alert>
- }
- legend="project_activity.graphs.custom.select_metric"
- listSize={0}
- onSearch={[MockFunction]}
- onSelect={[Function]}
- onUnselect={[MockFunction]}
- placeholder="search.search_for_metrics"
- renderLabel={[Function]}
- selectedElements={
- Array [
- "1",
- "2",
- "3",
- "4",
- "5",
- "6",
- ]
- }
- validateSearchInput={[Function]}
- />
-</div>
-`;
-
-exports[`should render correctly with type filter 1`] = `
-<div
- className="menu abs-width-300"
->
- <MultiSelect
- allowNewElements={false}
- allowSelection={true}
- elements={Array []}
- filterSelected={[MockFunction]}
- footerNode={
- <Alert
- className="spacer-left spacer-right spacer-top"
- variant="info"
- >
- project_activity.graphs.custom.type_x_message.metric.type.filter1, metric.type.filter2
- </Alert>
- }
- legend="project_activity.graphs.custom.select_metric"
- listSize={0}
- onSearch={[MockFunction]}
- onSelect={[Function]}
- onUnselect={[MockFunction]}
- placeholder="search.search_for_metrics"
- renderLabel={[Function]}
- selectedElements={Array []}
- validateSearchInput={[Function]}
- />
-</div>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly: default 1`] = `
-<Tooltip
- overlay="Lorem ipsum dolor sit amet"
->
- <span
- className="text-middle"
- >
- <span
- className="note little-spacer-right"
- >
- event.category.VERSION
- :
- </span>
- <strong
- className="spacer-right"
- >
- Lorem ipsum
- </strong>
- </span>
-</Tooltip>
-`;
-
-exports[`should render correctly: definition change 1`] = `
-<DefinitionChangeEventInner
- branchLike={
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-6.7",
- }
- }
- event={
- Object {
- "category": "DEFINITION_CHANGE",
- "definitionChange": Object {
- "projects": Array [
- Object {
- "changeType": "ADDED",
- "key": "foo",
- "name": "Foo",
- },
- ],
- },
- "description": "Lorem ipsum dolor sit amet",
- "key": "E11",
- "name": "Lorem ipsum",
- "qualityGate": undefined,
- }
- }
-/>
-`;
-
-exports[`should render correctly: no description 1`] = `
-<Tooltip
- overlay={null}
->
- <span
- className="text-middle"
- >
- <span
- className="note little-spacer-right"
- >
- event.category.VERSION
- :
- </span>
- <strong
- className="spacer-right"
- >
- Lorem ipsum
- </strong>
- </span>
-</Tooltip>
-`;
-
-exports[`should render correctly: rich quality gate 1`] = `
-<RichQualityGateEventInner
- event={
- Object {
- "category": "QUALITY_GATE",
- "description": "Lorem ipsum dolor sit amet",
- "key": "E11",
- "name": "Lorem ipsum",
- "qualityGate": Object {
- "failing": Array [
- Object {
- "branch": "master",
- "key": "foo",
- "name": "Foo",
- },
- Object {
- "branch": "feature/bar",
- "key": "bar",
- "name": "Bar",
- },
- ],
- "status": "ERROR",
- "stillFailing": true,
- },
- }
- }
-/>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should correctly render a graph 1`] = `
-<div
- className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center"
->
- <GraphsLegendStatic
- series={
- Array [
- Object {
- "data": Array [
- Object {
- "x": 2016-10-27T14:33:50.000Z,
- "y": 5,
- },
- Object {
- "x": 2016-10-27T10:21:15.000Z,
- "y": 16,
- },
- Object {
- "x": 2016-10-26T10:17:29.000Z,
- "y": 12,
- },
- ],
- "name": "bugs",
- "translatedName": "metric.bugs.name",
- "type": "INT",
- },
- ]
- }
- />
- <div
- className="flex-1"
- >
- <AutoSizer>
- <Component />
- </AutoSizer>
- </div>
- <div
- className="little-spacer-top big-spacer-bottom"
- >
- <div
- className="display-flex-justify-end little-padded-right"
- >
- <ModalButton
- modal={[Function]}
- >
- <Component />
- </ModalButton>
- </div>
- </div>
-</div>
-`;
-
-exports[`should correctly render a graph: custom 1`] = `
-<div
- className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center"
->
- <GraphsLegendCustom
- removeMetric={[MockFunction]}
- series={
- Array [
- Object {
- "data": Array [
- Object {
- "x": 2016-10-27T14:33:50.000Z,
- "y": 5,
- },
- Object {
- "x": 2016-10-27T10:21:15.000Z,
- "y": 16,
- },
- Object {
- "x": 2016-10-26T10:17:29.000Z,
- "y": 12,
- },
- ],
- "name": "bugs",
- "translatedName": "metric.bugs.name",
- "type": "INT",
- },
- ]
- }
- />
- <div
- className="flex-1"
- >
- <AutoSizer>
- <Component />
- </AutoSizer>
- </div>
- <div
- className="little-spacer-top big-spacer-bottom"
- >
- <div
- className="display-flex-justify-end little-padded-right"
- >
- <ModalButton
- modal={[Function]}
- >
- <Component />
- </ModalButton>
- </div>
- </div>
-</div>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should correctly render a graph 1`] = `
-<div
- className="display-flex-justify-center display-flex-column display-flex-stretch flex-grow"
->
- <GraphHistory
- analyses={
- Array [
- Object {
- "date": 2016-10-27T14:33:50.000Z,
- "events": Array [
- Object {
- "category": "VERSION",
- "key": "E1",
- "name": "6.5-SNAPSHOT",
- },
- ],
- "key": "A1",
- },
- Object {
- "date": 2016-10-27T10:21:15.000Z,
- "events": Array [],
- "key": "A2",
- },
- Object {
- "date": 2016-10-26T10:17:29.000Z,
- "events": Array [
- Object {
- "category": "OTHER",
- "key": "E2",
- "name": "foo",
- },
- Object {
- "category": "VERSION",
- "key": "E3",
- "name": "6.4",
- },
- ],
- "key": "A3",
- },
- ]
- }
- ariaLabel="project_activity.graphs.explanation_x.metric.bugs.name"
- graph="issues"
- isCustom={false}
- key="0"
- leakPeriodDate={2017-05-16T11:50:02.000Z}
- measuresHistory={Array []}
- metricsType="INT"
- removeCustomMetric={[Function]}
- series={
- Array [
- Object {
- "data": Array [
- Object {
- "x": 2016-10-27T14:33:50.000Z,
- "y": 5,
- },
- Object {
- "x": 2016-10-27T10:21:15.000Z,
- "y": 16,
- },
- Object {
- "x": 2016-10-26T10:17:29.000Z,
- "y": 12,
- },
- ],
- "name": "bugs",
- "translatedName": "metric.bugs.name",
- "type": "INT",
- },
- ]
- }
- showAreas={false}
- updateGraphZoom={[Function]}
- updateSelectedDate={[Function]}
- updateTooltip={[Function]}
- />
-</div>
-`;
-
-exports[`should correctly render multiple graphs 1`] = `
-<div
- className="display-flex-justify-center display-flex-column display-flex-stretch flex-grow"
->
- <GraphHistory
- analyses={
- Array [
- Object {
- "date": 2016-10-27T14:33:50.000Z,
- "events": Array [
- Object {
- "category": "VERSION",
- "key": "E1",
- "name": "6.5-SNAPSHOT",
- },
- ],
- "key": "A1",
- },
- Object {
- "date": 2016-10-27T10:21:15.000Z,
- "events": Array [],
- "key": "A2",
- },
- Object {
- "date": 2016-10-26T10:17:29.000Z,
- "events": Array [
- Object {
- "category": "OTHER",
- "key": "E2",
- "name": "foo",
- },
- Object {
- "category": "VERSION",
- "key": "E3",
- "name": "6.4",
- },
- ],
- "key": "A3",
- },
- ]
- }
- ariaLabel="project_activity.graphs.explanation_x.metric.bugs.name"
- graph="issues"
- isCustom={false}
- key="0"
- leakPeriodDate={2017-05-16T11:50:02.000Z}
- measuresHistory={Array []}
- metricsType="INT"
- removeCustomMetric={[Function]}
- series={
- Array [
- Object {
- "data": Array [
- Object {
- "x": 2016-10-27T14:33:50.000Z,
- "y": 5,
- },
- Object {
- "x": 2016-10-27T10:21:15.000Z,
- "y": 16,
- },
- Object {
- "x": 2016-10-26T10:17:29.000Z,
- "y": 12,
- },
- ],
- "name": "bugs",
- "translatedName": "metric.bugs.name",
- "type": "INT",
- },
- ]
- }
- showAreas={false}
- updateGraphZoom={[Function]}
- updateSelectedDate={[Function]}
- updateTooltip={[Function]}
- />
- <GraphHistory
- analyses={
- Array [
- Object {
- "date": 2016-10-27T14:33:50.000Z,
- "events": Array [
- Object {
- "category": "VERSION",
- "key": "E1",
- "name": "6.5-SNAPSHOT",
- },
- ],
- "key": "A1",
- },
- Object {
- "date": 2016-10-27T10:21:15.000Z,
- "events": Array [],
- "key": "A2",
- },
- Object {
- "date": 2016-10-26T10:17:29.000Z,
- "events": Array [
- Object {
- "category": "OTHER",
- "key": "E2",
- "name": "foo",
- },
- Object {
- "category": "VERSION",
- "key": "E3",
- "name": "6.4",
- },
- ],
- "key": "A3",
- },
- ]
- }
- ariaLabel="project_activity.graphs.explanation_x.metric.bugs.name"
- graph="issues"
- isCustom={false}
- key="1"
- leakPeriodDate={2017-05-16T11:50:02.000Z}
- measuresHistory={Array []}
- metricsType="INT"
- removeCustomMetric={[Function]}
- series={
- Array [
- Object {
- "data": Array [
- Object {
- "x": 2016-10-27T14:33:50.000Z,
- "y": 5,
- },
- Object {
- "x": 2016-10-27T10:21:15.000Z,
- "y": 16,
- },
- Object {
- "x": 2016-10-26T10:17:29.000Z,
- "y": 12,
- },
- ],
- "name": "bugs",
- "translatedName": "metric.bugs.name",
- "type": "INT",
- },
- ]
- }
- showAreas={false}
- updateGraphZoom={[Function]}
- updateSelectedDate={[Function]}
- updateTooltip={[Function]}
- />
-</div>
-`;
-
-exports[`should show that there is no history data 1`] = `
-<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=""
- 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="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=""
- 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>
-`;
<a
aria-checked={thirdState ? 'mixed' : checked}
aria-label={label}
+ aria-disabled={disabled}
className={classNames('link-checkbox', this.props.className, {
disabled
})}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { Analysis, AnalysisEvent, ParsedAnalysis } from '../../types/project-activity';
+import {
+ Analysis,
+ AnalysisEvent,
+ HistoryItem,
+ MeasureHistory,
+ ParsedAnalysis
+} from '../../types/project-activity';
+import { parseDate } from '../dates';
export function mockAnalysis(overrides: Partial<Analysis> = {}): Analysis {
return {
...overrides
};
}
+
+export function mockMeasureHistory(overrides: Partial<MeasureHistory> = {}): MeasureHistory {
+ return {
+ metric: 'code_smells',
+ history: [
+ mockHistoryItem(),
+ mockHistoryItem({ date: parseDate('2018-10-27T12:21:15+0200'), value: '1749' }),
+ mockHistoryItem({ date: parseDate('2020-10-27T16:33:50+0200'), value: '500' })
+ ],
+ ...overrides
+ };
+}
+
+export function mockHistoryItem(overrides: Partial<HistoryItem> = {}): HistoryItem {
+ return {
+ date: parseDate('2016-10-26T12:17:29+0200'),
+ value: '2286',
+ ...overrides
+ };
+}