--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 styled from '@emotion/styled';
+import classNames from 'classnames';
+import tw from 'twin.macro';
+import { themeColor } from '../helpers/theme';
+
+export const NewCodeLegendIcon = styled.span`
+ ${tw`sw-align-middle`}
+ ${tw`sw-box-border`}
+ ${tw`sw-h-3`}
+ ${tw`sw-inline-block`}
+ ${tw`sw-w-3`}
+ background-color: ${themeColor('newCodeLegend')};
+ border: 1px solid ${themeColor('newCodeLegendBorder')};
+`;
+
+const NewCodeLegendText = styled.span`
+ ${tw`sw-align-middle`}
+ ${tw`sw-body-sm`}
+ ${tw`sw-ml-1`}
+ color: ${themeColor('graphCursorLineColor')};
+`;
+
+export function NewCodeLegend(props: { className?: string; text: string }) {
+ const { className, text } = props;
+
+ return (
+ <span className={classNames(className, 'sw-whitespace-nowrap')}>
+ <NewCodeLegendIcon />
+ <NewCodeLegendText>{text}</NewCodeLegendText>
+ </span>
+ );
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 tailwindBaseConfig from '../../../../tailwind.base.config';
+import { render } from '../../helpers/testUtils';
+import { NewCodeLegend } from '../NewCodeLegend';
+
+it('should render NewCodeLegend', () => {
+ render(<NewCodeLegend text="the text" />);
+
+ expect(screen.getByText('the text')).toHaveStyle({
+ 'font-size': tailwindBaseConfig.theme.fontSize.sm[0],
+ 'line-height': tailwindBaseConfig.theme.fontSize.sm[1],
+ 'margin-left': tailwindBaseConfig.theme.spacing[1],
+ });
+});
export * from './MainMenuItem';
export * from './MetricsRatingBadge';
export * from './NavBarTabs';
+export * from './NewCodeLegend';
export { QualityGateIndicator } from './QualityGateIndicator';
export * from './SizeIndicator';
export * from './SonarQubeLogo';
export * from './colors';
export * from './constants';
export * from './positioning';
+export * from './theme';
export * from './components';
export * from './helpers';
export * from './theme';
+export * from './types/measures';
export * from './types/theme';
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-export { default as lightTheme } from './light';
+
+export * from './light';
+export * from './withTheme';
darker: COLORS.red[800],
};
-const lightTheme = {
+export const lightTheme = {
id: 'light-theme',
highlightTheme: 'atom-one-light.css',
logo: 'sonarcloud-logo-black.svg',
// graph - chart
graphPointCircleColor: COLORS.white,
- 'graphLineColor.0': COLORS.blue[500],
- 'graphLineColor.1': COLORS.blue[700],
+ 'graphLineColor.0': COLORS.blue[700],
+ 'graphLineColor.1': COLORS.blue[500],
'graphLineColor.2': COLORS.blue[300],
- 'graphLineColor.3': COLORS.blue[900],
+ 'graphLineColor.3': COLORS.blue[800],
graphGridColor: COLORS.grey[50],
graphCursorLineColor: COLORS.blueGrey[400],
newCodeHighlight: COLORS.indigo[300],
'bubble.4': [...COLORS.orange[500], 0.3],
'bubble.5': [...COLORS.red[500], 0.3],
- // leak legend
- leakLegend: [...COLORS.indigo[300], 0.15],
- leakLegendBorder: COLORS.indigo[100],
+ // new code legend
+ newCodeLegend: [...COLORS.indigo[300], 0.15],
+ newCodeLegendBorder: COLORS.indigo[200],
// hotspot
hotspotStatus: COLORS.blueGrey[25],
GitLabPipeline: '/images/alms/gitlab.svg',
},
};
-
-export default lightTheme;
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { useTheme } from '@emotion/react';
+import { Theme } from '../types/theme';
+
+export interface ThemeProp {
+ theme: Theme;
+}
+
+export function withTheme<P>(
+ WrappedComponent: React.ComponentType<P & ThemeProp>
+): React.ComponentType<P> {
+ return function WrappedComponentWithTheme(props: P) {
+ const theme = useTheme();
+
+ return <WrappedComponent theme={theme} {...props} />;
+ };
+}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
.white-page {
background-color: #fff !important;
}
}
.page {
- position: relative;
z-index: var(--normalZIndex);
padding: 10px 20px;
}
* 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 GraphsHeader from '../../../components/activity-graph/GraphsHeader';
import GraphsHistory from '../../../components/activity-graph/GraphsHistory';
const series = generateSeries(measuresHistory, graph, metrics, displayedMetrics);
const graphs = splitSeriesInGraphs(series, MAX_GRAPH_NB, MAX_SERIES_PER_GRAPH);
let shownLeakPeriodDate;
+
if (leakPeriodDate !== undefined) {
const startDate = measuresHistory.reduce((oldest: Date, { history }) => {
if (history.length > 0) {
const date = parseDate(history[0].date);
+
return oldest.getTime() > date.getTime() ? date : oldest;
- } else {
- return oldest;
}
+
+ return oldest;
}, new Date());
+
shownLeakPeriodDate =
startDate.getTime() > leakPeriodDate.getTime() ? startDate : leakPeriodDate;
}
<div className="overview-panel-content">
<div className="display-flex-row">
<div className="display-flex-column flex-1">
- <div className="overview-panel-padded display-flex-column flex-1">
+ <div className="overview-panel-big-padded display-flex-column flex-1">
<GraphsHeader graph={graph} metrics={metrics} onUpdateGraph={props.onGraphChange} />
<GraphsHistory
analyses={[]}
* 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 * as React from 'react';
-import selectEvent from 'react-select-event';
import { getMeasuresWithPeriodAndMetrics } from '../../../../api/measures';
import { getProjectActivity } from '../../../../api/projectActivity';
import {
import { mockLoggedInUser, mockPeriod } from '../../../../helpers/testMocks';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import { ComponentQualifier } from '../../../../types/component';
-import { MetricKey } from '../../../../types/metrics';
+import { MetricKey, MetricType } from '../../../../types/metrics';
import { GraphType } from '../../../../types/project-activity';
import { CaycStatus, Measure, Metric } from '../../../../types/types';
import BranchOverview, { BRANCH_OVERVIEW_ACTIVITY_GRAPH, NO_CI_DETECTED } from '../BranchOverview';
let type;
if (/(coverage|duplication)$/.test(key)) {
- type = 'PERCENT';
+ type = MetricType.Percent;
} else if (/_rating$/.test(key)) {
- type = 'RATING';
+ type = MetricType.Rating;
} else {
- type = 'INT';
+ type = MetricType.Integer;
}
metrics.push(mockMetric({ key, id: key, name: key, type }));
measures.push(
it('should correctly handle graph type storage', async () => {
renderBranchOverview();
+
expect(getActivityGraph).toHaveBeenCalledWith(BRANCH_OVERVIEW_ACTIVITY_GRAPH, 'foo');
- const select = await screen.findByLabelText('project_activity.graphs.choose_type');
- await selectEvent.select(select, `project_activity.graphs.${GraphType.issues}`);
+ const dropdownButton = await screen.findByLabelText('project_activity.graphs.choose_type');
+
+ await userEvent.click(dropdownButton);
+
+ const issuesItem = await screen.findByRole('menuitem', {
+ name: `project_activity.graphs.${GraphType.issues}`,
+ });
+
+ expect(issuesItem).toBeInTheDocument();
+
+ await userEvent.click(issuesItem);
expect(saveActivityGraph).toHaveBeenCalledWith(
BRANCH_OVERVIEW_ACTIVITY_GRAPH,
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
.overview {
animation: fadeIn 0.5s forwards;
}
.overview-panel .activity-graph-legends {
justify-content: right;
- margin-top: -30px;
-}
-
-.overview-panel .activity-graph-new-code-legend {
- position: relative;
- z-index: var(--aboveNormalZIndex);
- width: 12px;
- overflow: hidden;
- margin-top: 1px;
- margin-left: calc(2 * var(--gridSize));
- text-indent: -9999px;
-}
-
-.overview-panel .activity-graph-new-code-legend::after {
- margin: 0;
+ margin-top: -38px;
}
.overview-analysis {
* 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, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { keyBy, times } from 'lodash';
jest.clearAllMocks();
projectActivityHandler.reset();
timeMachineHandler.reset();
+
timeMachineHandler.setMeasureHistory(
[
MetricKey.bugs,
it('should correctly show the baseline marker', async () => {
const { ui } = getPageObject();
+
renderProjectActivityAppContainer(
mockComponent({
leakPeriodDate: parseDate('2017-03-01T22:00:00.000Z').toDateString(),
],
})
);
+
await ui.appLoaded();
expect(ui.baseline.get()).toBeInTheDocument();
it('should only show certain security hotspot-related metrics for a project', async () => {
const { ui } = getPageObject();
+
renderProjectActivityAppContainer(
mockComponent({
breadcrumbs: [
'should only show certain security hotspot-related metrics for a %s',
async (qualifier) => {
const { ui } = getPageObject();
+
renderProjectActivityAppContainer(
mockComponent({
qualifier,
await ui.changeGraphType(GraphType.custom);
await ui.openMetricsDropdown();
expect(ui.metricCheckbox(MetricKey.security_review_rating).get()).toBeInTheDocument();
+
expect(
ui.metricCheckbox(MetricKey.security_hotspots_reviewed).query()
).not.toBeInTheDocument();
const { ui } = getPageObject();
const initialValue = '1.1-SNAPSHOT';
const updatedValue = '1.1--SNAPSHOT';
+
renderProjectActivityAppContainer(
mockComponent({
breadcrumbs: [
configuration: { showHistory: true },
})
);
+
await ui.appLoaded();
await ui.addVersionEvent('1.1.0.1', initialValue);
const { ui } = getPageObject();
const initialValue = 'Custom event name';
const updatedValue = 'Custom event updated name';
+
renderProjectActivityAppContainer(
mockComponent({
breadcrumbs: [
configuration: { showHistory: true },
})
);
+
await ui.appLoaded();
await act(async () => {
it('should correctly allow deletion of specific analyses', async () => {
const { ui } = getPageObject();
+
renderProjectActivityAppContainer(
mockComponent({
breadcrumbs: [
configuration: { showHistory: true },
})
);
+
await ui.appLoaded();
// Most recent analysis is not deletable.
it('should load all analyses', async () => {
const count = 1000;
+
projectActivityHandler.setAnalysesList(
times(count, (i) => {
return mockAnalysis({
});
})
);
+
const { ui } = getPageObject();
renderProjectActivityAppContainer();
await ui.appLoaded();
it('should correctly fetch the top level component when dealing with sub portfolios', async () => {
const { ui } = getPageObject();
+
renderProjectActivityAppContainer(
mockComponent({
key: 'unknown',
],
})
);
+
await ui.appLoaded();
// If it didn't fail, it means we correctly queried for project "foo".
});
})
);
+
const { ui } = getPageObject();
renderProjectActivityAppContainer();
await ui.appLoaded();
await ui.appLoaded();
expect(ui.bugsPopupCell.query()).not.toBeInTheDocument();
+
await act(async () => {
await ui.showDetails('1.1.0.1');
});
+
expect(ui.bugsPopupCell.get()).toBeInTheDocument();
});
function getPageObject() {
const user = userEvent.setup();
+
const ui = {
// Graph types.
graphTypeSelect: byLabelText('project_activity.graphs.choose_type'),
// Misc.
loading: byLabelText('loading'),
baseline: byText('project_activity.new_code_period_start'),
- bugsPopupCell: byRole('cell', { name: 'bugs' }),
+ bugsPopupCell: byRole('cell', { name: MetricKey.bugs }),
};
return {
expect(ui.loading.query()).not.toBeInTheDocument();
});
},
+
async changeGraphType(type: GraphType) {
await selectEvent.select(ui.graphTypeSelect.get(), [`project_activity.graphs.${type}`]);
},
+
async openMetricsDropdown() {
await user.click(ui.addMetricBtn.get());
},
+
async toggleMetric(metric: MetricKey) {
await user.click(ui.metricCheckbox(metric).get());
},
+
async closeMetricsDropdown() {
await user.keyboard('{Escape}');
},
+
async openCogMenu(id: string) {
await user.click(ui.cogBtn(id).get());
},
+
async deleteAnalysis(id: string) {
await user.click(ui.cogBtn(id).get());
await user.click(ui.deleteAnalysisBtn.get());
await user.click(ui.deleteBtn.get());
},
+
async addVersionEvent(id: string, value: string) {
await user.click(ui.cogBtn(id).get());
await user.click(ui.addVersionEvenBtn.get());
await user.type(ui.nameInput.get(), value);
await user.click(ui.saveBtn.get());
},
+
async addCustomEvent(id: string, value: string) {
await user.click(ui.cogBtn(id).get());
await user.click(ui.addCustomEventBtn.get());
await user.type(ui.nameInput.get(), value);
await user.click(ui.saveBtn.get());
},
+
async updateEvent(index: number, value: string) {
await user.click(ui.editEventBtn.getAll()[index]);
await user.clear(ui.nameInput.get());
await user.type(ui.nameInput.get(), value);
await user.click(ui.changeBtn.get());
},
+
async deleteEvent(index: number) {
await user.click(ui.deleteEventBtn.getAll()[index]);
await user.click(ui.deleteBtn.get());
},
+
async showDetails(id: string) {
await user.click(ui.seeDetailsBtn(id).get());
},
+
async filterByCategory(
category: ProjectAnalysisEventCategory | ApplicationAnalysisEventCategory
) {
await selectEvent.select(ui.categorySelect.get(), [`event.category.${category}`]);
},
+
async setDateRange(from?: string, to?: string) {
const dateInput = dateInputEvent(user);
if (from) {
await dateInput.pickDate(ui.fromDateInput.get(), parseDate(from));
}
+
if (to) {
await dateInput.pickDate(ui.toDateInput.get(), parseDate(to));
}
},
+
async resetDateFilters() {
await user.click(ui.resetDatesBtn.get());
},
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import * as React from 'react';
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
-import AdvancedTimeline from '../../components/charts/AdvancedTimeline';
+import { AdvancedTimeline } from '../../components/charts/AdvancedTimeline';
import { translate } from '../../helpers/l10n';
import { formatMeasure, getShortType } from '../../helpers/measures';
import { MeasureHistory, ParsedAnalysis, Serie } from '../../types/project-activity';
-import { Button } from '../controls/buttons';
import ModalButton from '../controls/ModalButton';
+import { Button } from '../controls/buttons';
import DataTableModal from './DataTableModal';
import GraphsLegendCustom from './GraphsLegendCustom';
import GraphsLegendStatic from './GraphsLegendStatic';
-import GraphsTooltips from './GraphsTooltips';
+import { GraphsTooltips } from './GraphsTooltips';
import { getAnalysisEventsForDate } from './utils';
interface Props {
showAreas,
graphDescription,
} = this.props;
+
+ const modalProp = ({ onClose }: { onClose: () => void }) => (
+ <DataTableModal
+ analyses={analyses}
+ graphEndDate={graphEndDate}
+ graphStartDate={graphStartDate}
+ series={series}
+ onClose={onClose}
+ />
+ );
+
const { tooltipIdx, tooltipXPos } = this.state;
const events = getAnalysisEventsForDate(analyses, selectedDate);
return (
- <div className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center">
+ <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} />
) : (
{({ height, width }) => (
<div>
<AdvancedTimeline
- displayNewCodeLegend={true}
endDate={graphEndDate}
formatYTick={this.formatValue}
height={height}
</AutoSizer>
</div>
{canShowDataAsTable && (
- <ModalButton
- modal={({ onClose }) => (
- <DataTableModal
- analyses={analyses}
- graphEndDate={graphEndDate}
- graphStartDate={graphStartDate}
- series={series}
- onClose={onClose}
- />
- )}
- >
+ <ModalButton modal={modalProp}>
{({ onClick }) => (
<Button className="a11y-hidden" onClick={onClick}>
{translate('project_activity.graphs.open_in_table')}
* 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 {
+ ButtonSecondary,
+ ChevronDownIcon,
+ Dropdown,
+ ItemButton,
+ PopupPlacement,
+ PopupZLevel,
+ TextMuted,
+} from 'design-system';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
import { GraphType } from '../../types/project-activity';
render() {
const { className, graph, metrics, metricsTypeFilter, selectedMetrics = [] } = this.props;
- const types = getGraphTypes(
- this.props.onAddCustomMetric === undefined || this.props.onRemoveCustomMetric === undefined
- );
+ const noCustomGraph =
+ this.props.onAddCustomMetric === undefined || this.props.onRemoveCustomMetric === undefined;
+
+ const types = getGraphTypes(noCustomGraph);
+
+ const overlayItems: JSX.Element[] = [];
+
+ const selectOptions: Array<{
+ label: string;
+ value: GraphType;
+ }> = [];
- const selectOptions = types.map((type) => ({
- label: translate('project_activity.graphs', type),
- value: type,
- }));
+ types.forEach((type) => {
+ const label = translate('project_activity.graphs', type);
+
+ selectOptions.push({ label, value: type });
+
+ overlayItems.push(
+ <ItemButton key={label} onClick={() => this.handleGraphChange({ value: type })}>
+ {label}
+ </ItemButton>
+ );
+ });
+
+ const selectedOption = selectOptions.find((option) => option.value === graph);
+ const selectedLabel = selectedOption?.label ?? '';
return (
- <div className={classNames(className, 'position-relative')}>
+ <div className={className}>
<div className="display-flex-end">
<div className="display-flex-column">
- <label className="text-bold little-spacer-bottom" id="graph-select-label">
- {translate('project_activity.graphs.choose_type')}
- </label>
- <Select
- aria-labelledby="graph-select-label"
- className="input-medium"
- isSearchable={false}
- onChange={this.handleGraphChange}
- options={selectOptions}
- value={selectOptions.find((option) => option.value === graph)}
- />
+ {noCustomGraph ? (
+ <Dropdown
+ id="activity-graph-type"
+ size="auto"
+ placement={PopupPlacement.BottomLeft}
+ zLevel={PopupZLevel.Content}
+ overlay={overlayItems}
+ >
+ <ButtonSecondary
+ aria-label={translate('project_activity.graphs.choose_type')}
+ className={
+ 'sw-body-sm sw-flex sw-flex-row sw-justify-between sw-pl-3 sw-pr-2 sw-w-32 ' +
+ 'sw-z-normal' // needed because the legends overlap part of the button
+ }
+ >
+ <TextMuted text={selectedLabel} />
+ <ChevronDownIcon className="sw-ml-1 sw-mr-0 sw-pr-0" />
+ </ButtonSecondary>
+ </Dropdown>
+ ) : (
+ <Select
+ aria-label={translate('project_activity.graphs.choose_type')}
+ className="input-medium"
+ isSearchable={false}
+ onChange={this.handleGraphChange}
+ options={selectOptions}
+ value={selectedOption}
+ />
+ )}
</div>
{isCustomGraph(graph) &&
this.props.onAddCustomMetric !== undefined &&
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { NewCodeLegend } from 'design-system';
import * as React from 'react';
import Tooltip from '../../components/controls/Tooltip';
import { translate } from '../../helpers/l10n';
import { Serie } from '../../types/project-activity';
-import GraphsLegendItem from './GraphsLegendItem';
+import { GraphsLegendItem } from './GraphsLegendItem';
import { hasDataValues } from './utils';
export interface GraphsLegendCustomProps {
export default function GraphsLegendCustom(props: GraphsLegendCustomProps) {
const { series } = props;
+
return (
<ul className="activity-graph-legends">
{series.map((serie, idx) => {
const hasData = hasDataValues(serie);
+
const legendItem = (
<GraphsLegendItem
index={idx}
showWarning={!hasData}
/>
);
+
if (!hasData) {
return (
<Tooltip
</Tooltip>
);
}
+
return (
- <li className="spacer-left spacer-right" key={serie.name}>
+ <li className="sw-ml-3" key={serie.name}>
{legendItem}
</li>
);
})}
+ <li key={translate('hotspot.filters.period.since_leak_period')}>
+ <NewCodeLegend
+ className="sw-ml-3 sw-mr-4"
+ text={translate('hotspot.filters.period.since_leak_period')}
+ />
+ </li>
</ul>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { useTheme } from '@emotion/react';
import classNames from 'classnames';
+import { Theme, themeColor } from 'design-system';
import * as React from 'react';
import { ClearButton } from '../../components/controls/buttons';
import AlertWarnIcon from '../../components/icons/AlertWarnIcon';
-import ChartLegendIcon from '../../components/icons/ChartLegendIcon';
+import { ChartLegendIcon } from '../../components/icons/ChartLegendIcon';
import { translateWithParameters } from '../../helpers/l10n';
interface Props {
removeMetric?: (metric: string) => void;
}
-export default class GraphsLegendItem extends React.PureComponent<Props> {
- handleClick = () => {
- if (this.props.removeMetric) {
- this.props.removeMetric(this.props.metric);
- }
- };
+export function GraphsLegendItem({
+ className,
+ index,
+ metric,
+ name,
+ removeMetric,
+ showWarning,
+}: Props) {
+ const theme = useTheme() as Theme;
+
+ const isActionable = removeMetric !== undefined;
- render() {
- const { className, name, index, showWarning } = this.props;
- const isActionable = this.props.removeMetric != null;
- const legendClass = classNames({ 'activity-graph-legend-actionable': isActionable }, className);
+ const legendClass = classNames({ 'activity-graph-legend-actionable': isActionable }, className);
- return (
- <span className={legendClass}>
- {showWarning ? (
- <AlertWarnIcon className="spacer-right" />
- ) : (
- <ChartLegendIcon className="text-middle spacer-right" index={index} />
- )}
- <span className="text-middle">{name}</span>
- {isActionable && (
- <ClearButton
- className="button-tiny spacer-left text-middle"
- aria-label={translateWithParameters(
- 'project_activity.graphs.custom.remove_metric',
- name
- )}
- iconProps={{ size: 12 }}
- onClick={this.handleClick}
- />
- )}
+ return (
+ <span className={legendClass}>
+ {showWarning ? (
+ <AlertWarnIcon className="sw-mr-2" />
+ ) : (
+ <ChartLegendIcon className="sw-align-middle sw-mr-2" index={index} />
+ )}
+ <span
+ className="sw-align-middle sw-body-sm"
+ style={{ color: themeColor('graphCursorLineColor')({ theme }) }}
+ >
+ {name}
</span>
- );
- }
+ {isActionable && (
+ <ClearButton
+ aria-label={translateWithParameters('project_activity.graphs.custom.remove_metric', name)}
+ className="button-tiny sw-align-middle sw-ml-2"
+ iconProps={{ size: 12 }}
+ onClick={() => removeMetric(metric)}
+ />
+ )}
+ </span>
+ );
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { NewCodeLegend } from 'design-system';
import * as React from 'react';
+import { translate } from '../../helpers/l10n';
import { Serie } from '../../types/project-activity';
-import GraphsLegendItem from './GraphsLegendItem';
+import { GraphsLegendItem } from './GraphsLegendItem';
export interface GraphsLegendStaticProps {
series: Array<Pick<Serie, 'name' | 'translatedName'>>;
{series.map((serie, idx) => (
<li key={serie.name}>
<GraphsLegendItem
- className="big-spacer-left big-spacer-right"
+ className="sw-ml-3"
index={idx}
metric={serie.name}
name={serie.translatedName}
/>
</li>
))}
+ <li key={translate('hotspot.filters.period.since_leak_period')}>
+ <NewCodeLegend
+ className="sw-ml-3 big-spacer-right"
+ text={translate('hotspot.filters.period.since_leak_period')}
+ />
+ </li>
</ul>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { ThemeProp, themeColor, withTheme } from 'design-system';
import * as React from 'react';
import { Popup, PopupPlacement } from '../../components/ui/popups';
import { isDefined } from '../../helpers/types';
import GraphsTooltipsContentIssues from './GraphsTooltipsContentIssues';
import { DEFAULT_GRAPH } from './utils';
-interface Props {
+interface PropsWithoutTheme {
events: AnalysisEvent[];
formatValue: (tick: number | string) => string;
graph: string;
tooltipPos: number;
}
-const TOOLTIP_WIDTH = 250;
+export type Props = PropsWithoutTheme & ThemeProp;
+
+const TOOLTIP_WIDTH = 280;
const TOOLTIP_LEFT_MARGIN = 60;
const TOOLTIP_LEFT_FLIP_THRESHOLD = 50;
-export default class GraphsTooltips extends React.PureComponent<Props> {
+export class GraphsTooltipsClass extends React.PureComponent<Props> {
renderContent() {
const { tooltipIdx, series, graph, measuresHistory } = this.props;
return series.map((serie, idx) => {
const point = serie.data[tooltipIdx];
+
if (!point || (!point.y && point.y !== 0)) {
return null;
}
}
render() {
- const { events, measuresHistory, tooltipIdx, tooltipPos, graph, graphWidth, selectedDate } =
- this.props;
+ const {
+ events,
+ measuresHistory,
+ tooltipIdx,
+ tooltipPos,
+ graph,
+ graphWidth,
+ selectedDate,
+ theme,
+ } = this.props;
const top = 30;
let left = tooltipPos + TOOLTIP_LEFT_MARGIN;
let placement = PopupPlacement.RightTop;
+
if (left > graphWidth - TOOLTIP_WIDTH - TOOLTIP_LEFT_FLIP_THRESHOLD) {
left -= TOOLTIP_WIDTH;
placement = PopupPlacement.LeftTop;
style={{ top, left, width: TOOLTIP_WIDTH }}
>
<div className="activity-graph-tooltip">
- <div className="activity-graph-tooltip-title spacer-bottom">
+ <div
+ className="sw-body-md-highlight sw-whitespace-nowrap"
+ style={{ color: themeColor('selectionCardHeader')({ theme }) }}
+ >
<DateTimeFormatter date={selectedDate} />
</div>
- <table className="width-100">
+ <table
+ className="width-100"
+ style={{ color: themeColor('dropdownMenuSubTitle')({ theme }) }}
+ >
+ {addSeparator && (
+ <tr>
+ <td className="activity-graph-tooltip-separator" colSpan={3}>
+ <hr />
+ </td>
+ </tr>
+ )}
{events?.length > 0 && (
<GraphsTooltipsContentEvents addSeparator={addSeparator} events={events} />
)}
);
}
}
+
+export const GraphsTooltips = withTheme<PropsWithoutTheme>(GraphsTooltipsClass);
* 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 '../../components/icons/ChartLegendIcon';
+import { ChartLegendIcon } from '../../components/icons/ChartLegendIcon';
interface Props {
name: string;
export default function GraphsTooltipsContentEvents({ addSeparator, events }: Props) {
return (
<tbody>
- {addSeparator && (
- <tr>
- <td className="activity-graph-tooltip-separator" colSpan={3}>
- <hr />
- </td>
- </tr>
- )}
<tr className="activity-graph-tooltip-line">
<td colSpan={3}>
{events.map((event) => (
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { MetricsEnum, MetricsRatingBadge } from 'design-system';
import * as React from 'react';
-import ChartLegendIcon from '../../components/icons/ChartLegendIcon';
-import Rating from '../../components/ui/Rating';
+import { ChartLegendIcon } from '../../components/icons/ChartLegendIcon';
import { MetricKey } from '../../types/metrics';
import { MeasureHistory } from '../../types/project-activity';
import { Dict } from '../../types/types';
export default function GraphsTooltipsContentIssues(props: GraphsTooltipsContentIssuesProps) {
const { index, measuresHistory, name, tooltipIdx, translatedName, value } = props;
const rating = measuresHistory.find((measure) => measure.metric === METRIC_RATING[name]);
+
if (!rating || !rating.history[tooltipIdx]) {
return null;
}
+
const ratingValue = rating.history[tooltipIdx].value;
+
+ const ratingEnumValue =
+ (ratingValue &&
+ {
+ '1.0': MetricsEnum.A,
+ '2.0': MetricsEnum.B,
+ '3.0': MetricsEnum.C,
+ '4.0': MetricsEnum.D,
+ '5.0': MetricsEnum.E,
+ }[ratingValue]) ||
+ undefined;
+
return (
- <tr className="activity-graph-tooltip-issues-line" key={name}>
- <td className="thin">
- <ChartLegendIcon className="spacer-right" index={index} />
+ <tr className="sw-h-8" key={name}>
+ <td className="sw-w-5">
+ <ChartLegendIcon className="sw-mr-0" index={index} />
+ </td>
+ <td>
+ <span className="sw-body-sm-highlight sw-ml-2">{value}</span>
</td>
- <td className="text-right spacer-right">
- <span className="activity-graph-tooltip-value">{value}</span>
- {ratingValue && <Rating className="spacer-left" value={ratingValue} />}
+ <td>
+ <span className="sw-body-sm">{translatedName}</span>
</td>
- <td>{translatedName}</td>
+ {ratingValue && (
+ <td>
+ <MetricsRatingBadge label={ratingValue} rating={ratingEnumValue} size="xs" />
+ </td>
+ )}
</tr>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import * as React from 'react';
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
-import ZoomTimeLine from '../../components/charts/ZoomTimeLine';
+import { ZoomTimeLine } from '../../components/charts/ZoomTimeLine';
import { Serie } from '../../types/project-activity';
import { hasHistoryData } from './utils';
* 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 * as React from 'react';
import { parseDate } from '../../../helpers/dates';
import { MetricKey } from '../../../types/metrics';
import { GraphType, MeasureHistory } from '../../../types/project-activity';
import { Metric } from '../../../types/types';
-import GraphsTooltips from '../GraphsTooltips';
+import { GraphsTooltips, Props } from '../GraphsTooltips';
import { generateSeries, getDisplayedHistoryMetrics } from '../utils';
it.each([
[
GraphType.issues,
[
- [MetricKey.bugs, 1, 'C'],
- [MetricKey.code_smells, 0, 'A'],
- [MetricKey.vulnerabilities, 2, 'E'],
+ [MetricKey.bugs, 1, 3],
+ [MetricKey.code_smells, 0, 1],
+ [MetricKey.vulnerabilities, 2, 5],
],
],
[
expect(
screen.getByRole('row', {
// eslint-disable-next-line jest/no-conditional-in-test
- name: rating ? `${n} metric.has_rating_X.${rating} ${key}` : `${n} ${key}`,
+ name: rating ? `${n} ${key} ${rating}` : `${n} ${key}`,
})
).toBeInTheDocument();
});
}
);
-function renderGraphsTooltips(props: Partial<GraphsTooltips['props']> = {}) {
+function renderGraphsTooltips(props: Partial<Props> = {}) {
const graph = (props.graph as GraphType) || GraphType.coverage;
const measuresHistory: MeasureHistory[] = [];
const date = props.selectedDate || parseDate('2016-01-01T00:00:00+0200');
history: [mockHistoryItem({ date, value })],
})
);
+
metrics.push(
mockMetric({
key: metric,
* 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;
}
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));
margin-bottom: var(--gridSize);
}
-.activity-graph-tooltip-title,
.activity-graph-tooltip-value {
font-weight: bold;
}
.activity-graph-legends {
+ align-items: center;
display: flex;
justify-content: center;
padding-bottom: calc(2 * var(--gridSize));
border-style: solid;
border-radius: calc(1.5 * var(--gridSize));
}
-
-.activity-graph-new-code-legend {
- margin-right: 10px; /* padding of activity graph */
-}
-
-.activity-graph-new-code-legend::after {
- content: '';
- display: inline-block;
- margin-left: calc(var(--gridSize) / 2);
- width: var(--gridSize);
- height: var(--gridSize);
- background-color: var(--leakPrimaryColor);
- border: 2px solid var(--leakSecondaryColor);
-}
* 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 '../../helpers/l10n';
import { localizeMetric } from '../../helpers/measures';
export function getGraphTypes(ignoreCustom = false) {
const graphs = [GraphType.issues, GraphType.coverage, GraphType.duplications];
+
return ignoreCustom ? graphs : [...graphs, GraphType.custom];
}
const linesToCover = measuresHistory.find(
(measure) => measure.metric === MetricKey.lines_to_cover
);
+
return {
data: linesToCover
? uncoveredLines.history.map((analysis, idx) => ({
if (displayedMetrics.length <= 0 || measuresHistory === undefined) {
return [];
}
+
return sortBy(
measuresHistory
.filter((measure) => displayedMetrics.indexOf(measure.metric) >= 0)
};
}),
(serie) =>
- displayedMetrics.indexOf(serie.name === 'covered_lines' ? 'uncovered_lines' : serie.name)
+ displayedMetrics.indexOf(
+ serie.name === 'covered_lines' ? MetricKey.uncovered_lines : serie.name
+ )
);
}
metrics: string[] = []
) {
save(namespace, graph, project);
+
if (isCustomGraph(graph)) {
save(`${namespace}.custom`, metrics.join(','), project);
}
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(',') : [],
return analysis.events;
}
}
+
return [];
}
* 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 { bisector, extent, max } from 'd3-array';
import {
NumberValue,
ScaleLinear,
- scaleLinear,
ScalePoint,
+ ScaleTime,
+ scaleLinear,
scalePoint,
scaleTime,
- ScaleTime,
} from 'd3-scale';
import { area, curveBasis, line as d3Line } from 'd3-shape';
+import { ThemeProp, themeColor, withTheme } from 'design-system';
import { flatten, isEqual, sortBy, throttle, uniq } from 'lodash';
import * as React from 'react';
-import { colors, rawSizes } from '../../app/theme';
import { isDefined } from '../../helpers/types';
+import { MetricType } from '../../types/metrics';
import { Chart } from '../../types/types';
import './AdvancedTimeline.css';
import './LineChart.css';
-export interface Props {
+export interface PropsWithoutTheme {
graphDescription?: string;
basisCurve?: boolean;
endDate?: Date;
disableZoom?: boolean;
- displayNewCodeLegend?: boolean;
formatYTick?: (tick: number | string) => string;
hideGrid?: boolean;
hideXAxis?: boolean;
width: number;
leakPeriodDate?: Date;
// used to avoid same y ticks labels
- maxYTicksCount: number;
+ maxYTicksCount?: number;
metricType: string;
- padding: number[];
+ padding?: number[];
selectedDate?: Date;
series: Chart.Serie[];
showAreas?: boolean;
updateSelectedDate?: (selectedDate?: Date) => void;
updateTooltip?: (selectedDate?: Date, tooltipXPos?: number, tooltipIdx?: number) => void;
updateZoom?: (start?: Date, endDate?: Date) => void;
- zoomSpeed: number;
+ zoomSpeed?: number;
}
+export type Props = PropsWithoutTheme & ThemeProp;
+
+type PropsWithDefaults = Props & typeof AdvancedTimelineClass.defaultProps;
+
type XScale = ScaleTime<number, number>;
type YScale = ScaleLinear<number, number> | ScalePoint<number | string>;
type YPoint = (number | string) & NumberValue;
-const LEGEND_LINE_HEIGHT = 16;
const X_LABEL_OFFSET = 15;
interface State {
- leakLegendTextWidth?: number;
maxXRange: number[];
mouseOver?: boolean;
selectedDate?: Date;
xScale: XScale;
}
-export default class AdvancedTimeline extends React.PureComponent<Props, State> {
+export class AdvancedTimelineClass extends React.PureComponent<Props, State> {
static defaultProps = {
- eventSize: 8,
- maxYTicksCount: 4,
- padding: [26, 10, 50, 60],
- zoomSpeed: 1,
+ padding: [26, 10, 50, 50],
};
- constructor(props: Props) {
+ constructor(props: PropsWithDefaults) {
super(props);
+
const scales = this.getScales(props);
const selectedDatePos = this.getSelectedDatePos(scales.xScale, props.selectedDate);
this.state = { ...scales, ...selectedDatePos };
this.handleZoomUpdate = throttle(this.handleZoomUpdate, 40);
}
- componentDidUpdate(prevProps: Props) {
+ componentDidUpdate(prevProps: PropsWithDefaults) {
let scales;
let selectedDatePos;
+
if (
this.props.metricType !== prevProps.metricType ||
this.props.startDate !== prevProps.startDate ||
this.props.height !== prevProps.height ||
this.props.series !== prevProps.series
) {
- scales = this.getScales(this.props);
+ scales = this.getScales(this.props as PropsWithDefaults);
+ this.setState({ ...scales });
+
if (this.state.selectedDate != null) {
selectedDatePos = this.getSelectedDatePos(scales.xScale, this.state.selectedDate);
}
if (!isEqual(this.props.selectedDate, prevProps.selectedDate)) {
const xScale = scales ? scales.xScale : this.state.xScale;
+
selectedDatePos = this.getSelectedDatePos(xScale, this.props.selectedDate);
}
- if (scales || selectedDatePos) {
- if (scales) {
- this.setState({ ...scales });
- }
- if (selectedDatePos) {
- this.setState({ ...selectedDatePos });
- }
+ if (selectedDatePos) {
+ this.setState({ ...selectedDatePos });
- if (selectedDatePos && this.props.updateTooltip) {
+ if (this.props.updateTooltip) {
this.props.updateTooltip(
selectedDatePos.selectedDate,
selectedDatePos.selectedDateXPos,
return scalePoint().domain(['ERROR', 'WARN', 'OK']).range([availableHeight, 0]);
};
- getYScale = (props: Props, availableHeight: number, flatData: Chart.Point[]): YScale => {
- if (props.metricType === 'RATING') {
+ getYScale = (
+ props: PropsWithDefaults,
+ availableHeight: number,
+ flatData: Chart.Point[]
+ ): YScale => {
+ if (props.metricType === MetricType.Rating) {
return this.getRatingScale(availableHeight);
- } else if (props.metricType === 'LEVEL') {
+ } else if (props.metricType === MetricType.Level) {
return this.getLevelScale(availableHeight);
}
+
return scaleLinear()
.range([availableHeight, 0])
.domain([0, max(flatData, (d) => Number(d.y || 0)) || 1])
return 'ticks' in yScale;
}
- getXScale = ({ startDate, endDate }: Props, availableWidth: number, flatData: Chart.Point[]) => {
+ getXScale = (
+ { startDate, endDate }: PropsWithDefaults,
+ availableWidth: number,
+ flatData: Chart.Point[]
+ ) => {
const dateRange = extent(flatData, (d) => d.x) as [Date, Date];
const start = startDate && startDate > dateRange[0] ? startDate : dateRange[0];
const end = endDate && endDate < dateRange[1] ? endDate : dateRange[1];
+
const xScale: ScaleTime<number, number> = scaleTime()
.domain(sortBy([start, end]))
.range([0, availableWidth])
.clamp(false);
+
return {
xScale,
maxXRange: dateRange.map(xScale),
};
};
- getScales = (props: Props) => {
+ getScales = (props: PropsWithDefaults) => {
const availableWidth = props.width - props.padding[1] - props.padding[3];
const availableHeight = props.height - props.padding[0] - props.padding[2];
const flatData = flatten(props.series.map((serie) => serie.data));
+
return {
...this.getXScale(props, availableWidth, flatData),
yScale: this.getYScale(props, availableHeight, flatData),
getSelectedDatePos = (xScale: XScale, selectedDate?: Date) => {
const firstSerie = this.props.series[0];
+
if (selectedDate && firstSerie) {
const idx = firstSerie.data.findIndex((p) => p.x.valueOf() === selectedDate.valueOf());
const xRange = sortBy(xScale.range());
};
}
}
- return { selectedDate: undefined, selectedDateXPos: undefined, selectedDateIdx: undefined };
- };
- getEventMarker = (size: number) => {
- const half = size / 2;
- return `M${half} 0 L${size} ${half} L ${half} ${size} L0 ${half} L${half} 0 L${size} ${half}`;
+ return { selectedDate: undefined, selectedDateXPos: undefined, selectedDateIdx: undefined };
};
handleWheel = (event: React.WheelEvent<SVGElement>) => {
- event.preventDefault();
+ const { zoomSpeed = 1 } = this.props;
const { maxXRange, xScale } = this.state;
+
+ event.preventDefault();
const parentBbox = event.currentTarget.getBoundingClientRect();
const mouseXPos = (event.pageX - parentBbox.left) / parentBbox.width;
const xRange = xScale.range();
- const speed = event.deltaMode
- ? (25 / event.deltaMode) * this.props.zoomSpeed
- : this.props.zoomSpeed;
+ const speed = (event.deltaMode as number | undefined)
+ ? (25 / event.deltaMode) * zoomSpeed
+ : zoomSpeed;
+
const leftPos = xRange[0] - Math.round(speed * event.deltaY * mouseXPos);
const rightPos = xRange[1] + Math.round(speed * event.deltaY * (1 - mouseXPos));
const startDate = leftPos > maxXRange[0] ? xScale.invert(leftPos) : undefined;
handleMouseOut = () => {
const { updateTooltip } = this.props;
+
if (updateTooltip) {
this.setState({
mouseOver: false,
selectedDateXPos: undefined,
selectedDateIdx: undefined,
});
+
updateTooltip(undefined, undefined, undefined);
}
};
handleClick = () => {
const { updateSelectedDate } = this.props;
+
if (updateSelectedDate) {
updateSelectedDate(this.state.selectedDate || undefined);
}
};
- setLeakLegendTextWidth = (node: SVGTextElement | null) => {
- if (node) {
- this.setState({ leakLegendTextWidth: node.getBoundingClientRect().width });
- }
- };
-
updateTooltipPos = (xPos: number) => {
this.setState((state) => {
const firstSerie = this.props.series[0];
+
if (state.mouseOver && firstSerie) {
const { updateTooltip } = this.props;
const date = state.xScale.invert(xPos);
const bisectX = bisector<Chart.Point, Date>((d) => d.x).right;
let idx = bisectX(firstSerie.data, date);
+
if (idx >= 0) {
const previousPoint = firstSerie.data[idx - 1];
const nextPoint = firstSerie.data[idx];
+
if (
!nextPoint ||
(previousPoint &&
) {
idx--;
}
+
const selectedDate = firstSerie.data[idx].x;
const xPos = state.xScale(selectedDate);
+
if (updateTooltip) {
updateTooltip(selectedDate, xPos, idx);
}
+
return { selectedDate, selectedDateXPos: xPos, selectedDateIdx: idx };
}
}
+
return null;
});
};
renderHorizontalGrid = () => {
- const { formatYTick } = this.props;
+ const { formatYTick, maxYTicksCount = 4 } = this.props;
const { xScale, yScale } = this.state;
const hasTicks = this.isYScaleLinear(yScale);
- let ticks: Array<string | number> = hasTicks
- ? yScale.ticks(this.props.maxYTicksCount)
- : yScale.domain();
+ let ticks: Array<string | number> = hasTicks ? yScale.ticks(maxYTicksCount) : yScale.domain();
if (!ticks.length) {
ticks.push(yScale.domain()[1]);
<g>
{ticks.map((tick) => {
const y = yScale(tick as YPoint);
+
return (
<g key={tick}>
{formatYTick != null && (
<text
- className="line-chart-tick line-chart-tick-x"
+ className="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
textAnchor="end"
const format = xScale.tickFormat(7);
const ticks = xScale.ticks(7);
const y = yScale.range()[0];
+
return (
<g transform="translate(0, 20)">
{ticks.slice(0, -1).map((tick, index) => {
const x = xScale(tick);
+
return (
<text
- className="line-chart-tick"
+ className="line-chart-tick sw-body-sm"
// eslint-disable-next-line react/no-array-index-key
key={index}
textAnchor="end"
);
};
- renderNewCodeLegend = (params: { leakStart: number; leakWidth: number }) => {
- const { leakStart, leakWidth } = params;
- const { leakLegendTextWidth, xScale, yScale } = this.state;
- const yRange = yScale.range();
- const xRange = xScale.range();
-
- const legendMinWidth = (leakLegendTextWidth ?? 0) + rawSizes.grid;
- const legendPadding = rawSizes.grid / 2;
-
- let legendBackgroundPosition;
- let legendBackgroundWidth;
- let legendMargin;
- let legendPosition;
- let legendTextAnchor;
-
- if (leakWidth >= legendMinWidth) {
- legendBackgroundWidth = leakWidth;
- legendBackgroundPosition = leakStart;
- legendMargin = 0;
- legendPosition = legendBackgroundPosition + legendPadding;
- legendTextAnchor = 'start';
- } else {
- legendBackgroundWidth = legendMinWidth;
- legendBackgroundPosition = xRange[xRange.length - 1] - legendBackgroundWidth;
- legendMargin = rawSizes.grid / 2;
- legendPosition = xRange[xRange.length - 1] - legendPadding;
- legendTextAnchor = 'end';
- }
-
- return (
- <>
- <rect
- fill={colors.leakPrimaryColor}
- height={LEGEND_LINE_HEIGHT}
- width={legendBackgroundWidth}
- x={legendBackgroundPosition}
- y={yRange[yRange.length - 1] - LEGEND_LINE_HEIGHT - legendMargin}
- />
- <text
- className="new-code-legend"
- ref={this.setLeakLegendTextWidth}
- x={legendPosition}
- y={yRange[yRange.length - 1] - legendPadding - legendMargin}
- textAnchor={legendTextAnchor}
- >
- new code
- </text>
- </>
- );
- };
-
renderLeak = () => {
- const { displayNewCodeLegend, leakPeriodDate } = this.props;
+ const { leakPeriodDate, theme } = this.props;
+
if (!leakPeriodDate) {
return null;
}
+
const { xScale, yScale } = this.state;
const yRange = yScale.range();
const xRange = xScale.range();
const leakStart = Math.max(xScale(leakPeriodDate), xRange[0]);
const leakWidth = xRange[xRange.length - 1] - leakStart;
+
if (leakWidth < 1) {
return null;
}
return (
- <>
- {displayNewCodeLegend && this.renderNewCodeLegend({ leakStart, leakWidth })}
- <rect
- className="leak-chart-rect"
- fill={colors.leakPrimaryColor}
- height={yRange[0] - yRange[yRange.length - 1]}
- width={leakWidth}
- x={leakStart}
- y={yRange[yRange.length - 1]}
- />
- </>
+ <rect
+ className="leak-chart-rect"
+ fill={themeColor('newCodeLegend')({ theme })}
+ height={yRange[0] - yRange[yRange.length - 1]}
+ width={leakWidth}
+ x={leakStart}
+ y={yRange[yRange.length - 1]}
+ />
);
};
renderLines = () => {
+ const { series, theme } = this.props;
const { xScale, yScale } = this.state;
const lineGenerator = d3Line<Chart.Point>()
if (this.props.basisCurve) {
lineGenerator.curve(curveBasis);
}
+
return (
<g>
- {this.props.series.map((serie, idx) => (
+ {series.map((serie, idx) => (
<path
className={classNames('line-chart-path', `line-chart-path-${idx}`)}
d={lineGenerator(serie.data) ?? undefined}
key={serie.name}
+ stroke={themeColor(`graphLineColor.${idx}` as Parameters<typeof themeColor>[0])({
+ theme,
+ })}
/>
))}
</g>
renderDots = () => {
const { series } = this.props;
const { xScale, yScale } = this.state;
+
return (
<g>
{series
serie.data
.map((point, idx) => {
const pointNotDefined = !point.y && point.y !== 0;
+
const hasPointBefore =
serie.data[idx - 1] && (serie.data[idx - 1].y || serie.data[idx - 1].y === 0);
+
const hasPointAfter =
serie.data[idx + 1] && (serie.data[idx + 1].y || serie.data[idx + 1].y === 0);
+
if (pointNotDefined || hasPointBefore || hasPointAfter) {
return undefined;
}
+
return (
<circle
className={classNames('line-chart-dot', `line-chart-dot-${serieIdx}`)}
if (basisCurve) {
areaGenerator.curve(curveBasis);
}
+
return (
<g>
{series.map((serie, idx) => (
renderSelectedDate = () => {
const { selectedDateIdx, selectedDateXPos, yScale } = this.state;
const firstSerie = this.props.series[0];
+
if (selectedDateIdx == null || selectedDateXPos == null || !firstSerie) {
return null;
}
/>
{this.props.series.map((serie, idx) => {
const point = serie.data[selectedDateIdx];
+
if (!point || (!point.y && point.y !== 0)) {
return null;
}
+
return (
<circle
className={classNames('line-chart-dot', `line-chart-dot-${idx}`)}
const { yScale, xScale } = this.state;
const mouseEvents: Partial<React.SVGProps<SVGRectElement>> = {};
+
if (zoomEnabled) {
mouseEvents.onWheel = this.handleWheel;
}
+
if (this.props.updateTooltip) {
mouseEvents.onMouseEnter = this.handleMouseEnter;
mouseEvents.onMouseMove = this.handleMouseMove;
mouseEvents.onMouseOut = this.handleMouseOut;
}
+
if (this.props.updateSelectedDate) {
mouseEvents.onClick = this.handleClick;
}
+
return (
<rect
className="chart-mouse-events-overlay"
hideXAxis,
showAreas,
graphDescription,
- } = this.props;
+ } = this.props as PropsWithDefaults;
+
if (!width || !height) {
return <div />;
}
+
const zoomEnabled = !disableZoom && this.props.updateZoom != null;
const isZoomed = Boolean(startDate ?? endDate);
+
return (
<svg
aria-label={graphDescription}
);
}
}
+
+export const AdvancedTimeline = withTheme<PropsWithoutTheme>(AdvancedTimelineClass);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
.line-chart-path {
fill: none;
- stroke: var(--blue);
stroke-width: 3px;
}
.line-chart-path.line-chart-path-1 {
- stroke: var(--darkBlue);
stroke-dasharray: 3;
}
.line-chart-path.line-chart-path-2 {
- stroke: #24c6e0;
stroke-dasharray: 10;
}
.line-chart-tick {
fill: var(--secondFontColor);
- font-size: var(--smallFontSize);
}
.line-chart-tick-x {
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { extent, max } from 'd3-array';
-import { scaleLinear, ScaleLinear } from 'd3-scale';
-import { area as d3Area, curveBasis, line as d3Line } from 'd3-shape';
+import { ScaleLinear, scaleLinear } from 'd3-scale';
+import { curveBasis, area as d3Area, line as d3Line } from 'd3-shape';
import * as React from 'react';
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer';
import './LineChart.css';
.curve(curveBasis);
let { data } = this.props;
+
if (this.props.backdropConstraints) {
const c = this.props.backdropConstraints;
data = data.filter((d) => c[0] <= d.x && d.x <= c[1]);
.map((point, index) => {
const x = xScale(point.x);
const y = yScale(point.y || 0);
+
// eslint-disable-next-line react/no-array-index-key
return <circle className="line-chart-point" cx={x} cy={y} key={index} r="3" />;
});
+
return <g>{points}</g>;
}
const x = xScale(point.x);
const y1 = yScale.range()[0];
const y2 = yScale(point.y || 0);
+
// eslint-disable-next-line react/no-array-index-key
return <line className="line-chart-grid" key={index} x1={x} x2={x} y1={y1} y2={y2} />;
});
+
return <g>{lines}</g>;
}
const point = this.props.data[index];
const x = xScale(point.x);
const y = yScale.range()[0];
+
return (
// eslint-disable-next-line react/no-array-index-key
- <text className="line-chart-tick" dy="1.5em" key={index} x={x} y={y}>
+ <text className="line-chart-tick sw-body-sm" dy="1.5em" key={index} x={x} y={y}>
{tick}
</text>
);
});
+
return <g>{ticks}</g>;
}
const point = this.props.data[index];
const x = xScale(point.x);
const y = yScale(point.y || 0);
+
return (
// eslint-disable-next-line react/no-array-index-key
- <text className="line-chart-tick" dy="-1em" key={index} x={x} y={y}>
+ <text className="line-chart-tick sw-body-sm" dy="-1em" key={index} x={x} y={y}>
{value}
</text>
);
});
+
return <g>{ticks}</g>;
}
.y((d) => yScale(d.y || 0))
.defined((d) => d.y != null)
.curve(curveBasis);
+
return <path className="line-chart-path" d={p(this.props.data) as string} />;
}
const xScale = scaleLinear()
.domain(extent(this.props.data, (d) => d.x) as [number, number])
.range([0, availableWidth]);
+
const yScale = scaleLinear().range([availableHeight, 0]);
if (this.props.domain) {
* 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 { extent, max } from 'd3-array';
-import { scaleLinear, scalePoint, scaleTime, ScaleTime } from 'd3-scale';
+import { ScaleTime, scaleLinear, scalePoint, scaleTime } from 'd3-scale';
import { area, curveBasis, line as d3Line } from 'd3-shape';
+import { ThemeProp, themeColor, withTheme } from 'design-system';
import { flatten, sortBy, throttle } from 'lodash';
import * as React from 'react';
import Draggable, { DraggableBounds, DraggableCore, DraggableData } from 'react-draggable';
-import { colors } from '../../app/theme';
+import { MetricType } from '../../types/metrics';
import { Chart } from '../../types/types';
import './LineChart.css';
import './ZoomTimeLine.css';
-export interface Props {
+export interface PropsWithoutTheme {
basisCurve?: boolean;
endDate?: Date;
height: number;
leakPeriodDate?: Date;
metricType: string;
- padding: number[];
+ padding?: number[];
series: Chart.Serie[];
showAreas?: boolean;
- showXTicks: boolean;
+ showXTicks?: boolean;
startDate?: Date;
updateZoom: (start?: Date, endDate?: Date) => void;
width: number;
}
+export type Props = PropsWithoutTheme & ThemeProp;
+
+export type PropsWithDefaults = Props & typeof ZoomTimeLineClass.defaultProps;
+
interface State {
overlayLeftPos?: number;
newZoomStart?: number;
}
type XScale = ScaleTime<number, number>;
-// It should be `ScaleLinear<number, number> | ScalePoint<number> | ScalePoint<string>`, but in order
-// to make it work, we need to write a lot of type guards :-(. This introduces a lot of unnecessary code,
-// not to mention overhead at runtime. The simplest is just to cast to any, and rely on D3's internals
-// to make it work.
-type YScale = any;
-export default class ZoomTimeLine extends React.PureComponent<Props, State> {
+export class ZoomTimeLineClass extends React.PureComponent<Props, State> {
static defaultProps = {
padding: [0, 0, 18, 0],
- showXTicks: true,
};
- constructor(props: Props) {
+ constructor(props: PropsWithDefaults) {
super(props);
+
this.state = {};
this.handleZoomUpdate = throttle(this.handleZoomUpdate, 40);
}
return scalePoint().domain(['ERROR', 'WARN', 'OK']).range([availableHeight, 0]);
};
- getYScale = (availableHeight: number, flatData: Chart.Point[]): YScale => {
- if (this.props.metricType === 'RATING') {
+ getYScale = (availableHeight: number, flatData: Chart.Point[]) => {
+ if (this.props.metricType === MetricType.Rating) {
return this.getRatingScale(availableHeight);
- } else if (this.props.metricType === 'LEVEL') {
+ } else if (this.props.metricType === MetricType.Level) {
return this.getLevelScale(availableHeight);
}
+
return scaleLinear()
.range([availableHeight, 0])
.domain([0, max(flatData, (d) => Number(d.y || 0)) as number])
};
getScales = () => {
- const availableWidth = this.props.width - this.props.padding[1] - this.props.padding[3];
- const availableHeight = this.props.height - this.props.padding[0] - this.props.padding[2];
+ const { padding } = this.props as PropsWithDefaults;
+
+ const availableWidth = this.props.width - padding[1] - padding[3];
+ const availableHeight = this.props.height - padding[0] - padding[2];
const flatData = flatten(this.props.series.map((serie) => serie.data));
+
return {
xScale: this.getXScale(availableWidth, flatData),
yScale: this.getYScale(availableHeight, flatData),
};
};
- getEventMarker = (size: number) => {
- const half = size / 2;
- return `M${half} 0 L${size} ${half} L ${half} ${size} L0 ${half} L${half} 0 L${size} ${half}`;
- };
-
handleDoubleClick = (xScale: XScale, xDim: number[]) => () => {
this.handleZoomUpdate(xScale, xDim);
};
handleNewZoomDragStart = (xDim: number[]) => (_: MouseEvent, data: DraggableData) => {
const overlayLeftPos = data.node.getBoundingClientRect().left;
+
this.setState({
overlayLeftPos,
newZoomStart: Math.round(Math.max(xDim[0], Math.min(data.x - overlayLeftPos, xDim[1]))),
handleNewZoomDrag = (xScale: XScale, xDim: number[]) => (_: MouseEvent, data: DraggableData) => {
const { newZoomStart, overlayLeftPos } = this.state;
+
if (newZoomStart != null && overlayLeftPos != null && data.deltaX) {
this.handleZoomUpdate(
xScale,
handleNewZoomDragEnd =
(xScale: XScale, xDim: number[]) => (_: MouseEvent, data: DraggableData) => {
const { newZoomStart, overlayLeftPos } = this.state;
+
if (newZoomStart !== undefined && overlayLeftPos !== undefined) {
const x = Math.round(Math.max(xDim[0], Math.min(data.x - overlayLeftPos, xDim[1])));
this.handleZoomUpdate(xScale, newZoomStart === x ? xDim : sortBy([newZoomStart, x]));
handleZoomUpdate = (xScale: XScale, xArray: number[]) => {
const xRange = xScale.range();
+
const startDate =
xArray[0] > xRange[0] && xArray[0] < xRange[xRange.length - 1]
? xScale.invert(xArray[0])
: undefined;
+
const endDate =
xArray[1] > xRange[0] && xArray[1] < xRange[xRange.length - 1]
? xScale.invert(xArray[1])
: undefined;
+
this.props.updateZoom(startDate, endDate);
};
- renderBaseLine = (xScale: XScale, yScale: YScale) => {
+ renderBaseLine = (xScale: XScale, yScale: { range: () => number[] }) => {
return (
<line
className="line-chart-grid"
);
};
- renderTicks = (xScale: XScale, yScale: YScale) => {
+ renderTicks = (xScale: XScale, yScale: { range: () => number[] }) => {
const format = xScale.tickFormat(7);
const ticks = xScale.ticks(7);
const y = yScale.range()[0];
+
return (
<g>
{ticks.slice(0, -1).map((tick, index) => {
const nextTick = index + 1 < ticks.length ? ticks[index + 1] : xScale.domain()[1];
const x = (xScale(tick) + xScale(nextTick)) / 2;
+
return (
// eslint-disable-next-line react/no-array-index-key
<text className="chart-zoom-tick" dy="1.3em" key={index} x={x} y={y}>
);
};
- renderLeak = (xScale: XScale, yScale: YScale) => {
- const { leakPeriodDate } = this.props;
+ renderNewCode = (xScale: XScale, yScale: { range: () => number[] }) => {
+ const { leakPeriodDate, theme } = this.props;
+
if (!leakPeriodDate) {
return null;
}
+
const yRange = yScale.range();
+
return (
<rect
- fill={colors.leakPrimaryColor}
+ fill={themeColor('newCodeLegend')({ theme })}
height={yRange[0] - yRange[yRange.length - 1]}
width={xScale.range()[1] - xScale(leakPeriodDate)}
x={xScale(leakPeriodDate)}
);
};
- renderLines = (xScale: XScale, yScale: YScale) => {
+ renderLines = (xScale: XScale, yScale: (y: string | number | undefined) => number) => {
+ const { series, theme } = this.props;
+
const lineGenerator = d3Line<Chart.Point>()
.defined((d) => Boolean(d.y || d.y === 0))
.x((d) => xScale(d.x))
.y((d) => yScale(d.y));
+
if (this.props.basisCurve) {
lineGenerator.curve(curveBasis);
}
+
return (
<g>
- {this.props.series.map((serie, idx) => (
+ {series.map((serie, idx) => (
<path
- className={classNames('line-chart-path', 'line-chart-path-' + idx)}
- d={lineGenerator(serie.data) || undefined}
+ className={classNames('line-chart-path', `line-chart-path-${idx}`)}
+ d={lineGenerator(serie.data) ?? undefined}
key={serie.name}
+ stroke={themeColor(`graphLineColor.${idx}` as Parameters<typeof themeColor>[0])({
+ theme,
+ })}
/>
))}
</g>
);
};
- renderAreas = (xScale: XScale, yScale: YScale) => {
+ renderAreas = (xScale: XScale, yScale: (y: string | number | undefined) => number) => {
const areaGenerator = area<Chart.Point>()
.defined((d) => Boolean(d.y || d.y === 0))
.x((d) => xScale(d.x))
.y1((d) => yScale(d.y))
.y0(yScale(0));
+
if (this.props.basisCurve) {
areaGenerator.curve(curveBasis);
}
+
return (
<g>
{this.props.series.map((serie, idx) => (
</Draggable>
);
- renderZoom = (xScale: XScale, yScale: YScale) => {
+ renderZoom = (xScale: XScale, yScale: { range: () => number[] }) => {
const xRange = xScale.range();
const yRange = yScale.range();
const xDim = [xRange[0], xRange[xRange.length - 1]];
const endX = Math.round(this.props.endDate ? xScale(this.props.endDate) : xDim[1]);
const xArray = sortBy([startX, endX]);
const zoomBoxWidth = xArray[1] - xArray[0];
+
const showZoomArea =
this.state.newZoomStart == null ||
this.state.newZoomStart === startX ||
};
render() {
+ const { padding, showXTicks = true } = this.props as PropsWithDefaults;
+
if (!this.props.width || !this.props.height) {
return <div />;
}
return (
<svg className="line-chart " height={this.props.height} width={this.props.width}>
- <g transform={`translate(${this.props.padding[3]}, ${this.props.padding[0] + 2})`}>
- {this.renderLeak(xScale, yScale)}
- {this.renderBaseLine(xScale, yScale)}
- {this.props.showXTicks && this.renderTicks(xScale, yScale)}
- {this.props.showAreas && this.renderAreas(xScale, yScale)}
- {this.renderLines(xScale, yScale)}
- {this.renderZoom(xScale, yScale)}
+ <g transform={`translate(${padding[3]}, ${padding[0] + 2})`}>
+ {this.renderNewCode(xScale, yScale as Parameters<typeof this.renderNewCode>[1])}
+ {this.renderBaseLine(xScale, yScale as Parameters<typeof this.renderBaseLine>[1])}
+ {showXTicks && this.renderTicks(xScale, yScale as Parameters<typeof this.renderTicks>[1])}
+ {this.props.showAreas &&
+ this.renderAreas(xScale, yScale as Parameters<typeof this.renderAreas>[1])}
+ {this.renderLines(xScale, yScale as Parameters<typeof this.renderLines>[1])}
+ {this.renderZoom(xScale, yScale as Parameters<typeof this.renderZoom>[1])}
</g>
</svg>
);
}
}
+
+export const ZoomTimeLine = withTheme<PropsWithoutTheme>(ZoomTimeLineClass);
* 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 { Chart } from '../../../types/types';
-import AdvancedTimeline from '../AdvancedTimeline';
-const newCodeLegendClass = '.new-code-legend';
+import { render } from '@testing-library/react';
+import * as React from 'react';
+import { MetricType } from '../../../types/metrics';
+import { AdvancedTimeline, PropsWithoutTheme } from '../AdvancedTimeline';
// Replace scaleTime with scaleUtc to avoid timezone-dependent snapshots
jest.mock('d3-scale', () => {
jest.mock('lodash', () => {
const lodash = jest.requireActual('lodash');
- return { ...lodash, throttle: (f: any) => f };
-});
-
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
- expect(shallowRender({ disableZoom: false, updateZoom: () => {} })).toMatchSnapshot(
- 'Zoom enabled'
- );
- expect(shallowRender({ formatYTick: (t) => `Nicer tick ${t}` })).toMatchSnapshot('format y tick');
- expect(shallowRender({ width: undefined })).toMatchSnapshot('no width');
- expect(shallowRender({ height: undefined })).toMatchSnapshot('no height');
- expect(shallowRender({ showAreas: undefined })).toMatchSnapshot('no areas');
-});
-
-it('should render leak correctly', () => {
- const wrapper = shallowRender({ leakPeriodDate: new Date('2019-10-02') });
-
- const leakNode = wrapper.find('.leak-chart-rect');
- expect(leakNode.exists()).toBe(true);
- expect(leakNode.getElement().props.width).toBe(15);
-});
-
-it('should render leak legend correctly', () => {
- const wrapper = shallowRender({
- displayNewCodeLegend: true,
- leakPeriodDate: new Date('2019-10-02'),
- });
-
- const leakNode = wrapper;
- expect(leakNode.find(newCodeLegendClass).exists()).toBe(true);
- expect(leakNode.find(newCodeLegendClass).props().textAnchor).toBe('start');
- expect(leakNode).toMatchSnapshot();
-});
-
-it('should render leak legend correctly for small leak', () => {
- const wrapper = shallowRender({
- displayNewCodeLegend: true,
- leakPeriodDate: new Date('2020-02-06'),
- series: [
- mockData(1, '2020-02-01'),
- mockData(2, '2020-02-02'),
- mockData(3, '2020-02-03'),
- mockData(4, '2020-02-04'),
- mockData(5, '2020-02-05'),
- mockData(6, '2020-02-06'),
- mockData(7, '2020-02-07'),
- ],
- });
-
- const leakNode = wrapper;
- expect(leakNode.find(newCodeLegendClass).exists()).toBe(true);
- expect(leakNode.find(newCodeLegendClass).props().textAnchor).toBe('end');
-});
-
-it('should set leakLegendTextWidth correctly', () => {
- const wrapper = shallowRender();
-
- wrapper.instance().setLeakLegendTextWidth({
- getBoundingClientRect: () => ({ width: 12 } as DOMRect),
- } as SVGTextElement);
-
- expect(wrapper.state().leakLegendTextWidth).toBe(12);
-
- wrapper.instance().setLeakLegendTextWidth(null);
-
- expect(wrapper.state().leakLegendTextWidth).toBe(12);
-});
-
-it('should render old leak correctly', () => {
- const wrapper = shallowRender({ leakPeriodDate: new Date('2014-10-02') });
- const leakNode = wrapper.find('.leak-chart-rect');
- expect(leakNode.exists()).toBe(true);
- expect(leakNode.getElement().props.width).toBe(30);
+ return { ...lodash, throttle: (f: unknown) => f };
});
-it('should find date to display based on mouse location', () => {
- const wrapper = shallowRender();
-
- wrapper.instance().updateTooltipPos(0);
- expect(wrapper.state().selectedDateIdx).toBeUndefined();
-
- wrapper.instance().handleMouseEnter();
- wrapper.instance().updateTooltipPos(10);
- expect(wrapper.state().selectedDateIdx).toBe(1);
-});
-
-it('should update timeline when width changes', () => {
- const updateTooltip = jest.fn();
- const wrapper = shallowRender({ selectedDate: new Date('2019-10-02'), updateTooltip });
- const { xScale, selectedDateXPos } = wrapper.state();
-
- wrapper.setProps({ width: 200 });
- expect(wrapper.state().xScale).not.toBe(xScale);
- expect(wrapper.state().xScale).toEqual(expect.any(Function));
- expect(wrapper.state().selectedDateXPos).not.toBe(selectedDateXPos);
- expect(wrapper.state().selectedDateXPos).toEqual(expect.any(Number));
- expect(updateTooltip).toHaveBeenCalled();
-});
-
-it('should update tootlips when selected date changes', () => {
- const updateTooltip = jest.fn();
-
- const wrapper = shallowRender({ selectedDate: new Date('2019-10-01'), updateTooltip });
- const { xScale, selectedDateXPos } = wrapper.state();
- const selectedDate = new Date('2019-10-02');
-
- wrapper.setProps({ selectedDate });
- expect(wrapper.state().xScale).toBe(xScale);
- expect(wrapper.state().selectedDate).toBe(selectedDate);
- expect(wrapper.state().selectedDateXPos).not.toBe(selectedDateXPos);
- expect(wrapper.state().selectedDateXPos).toEqual(expect.any(Number));
- expect(updateTooltip).toHaveBeenCalled();
-});
-
-it('should handle scroll correcly', () => {
- let updateZoom = jest.fn();
- let preventDefault = jest.fn();
- let wrapper = shallowRender({ updateZoom });
- wrapper.instance().handleWheel({
- preventDefault,
- deltaX: 1,
- deltaY: -2,
- deltaZ: 0,
- pageX: 100,
- pageY: 1,
- currentTarget: {
- getBoundingClientRect: () => ({
- bottom: 0,
- height: 100,
- width: 50,
- left: 0,
- right: 0,
- top: 10,
- x: 12,
- y: 23,
- toJSON: () => '',
- }),
- } as any as SVGElement,
- } as any as React.WheelEvent<SVGElement>);
- expect(preventDefault).toHaveBeenCalled();
- expect(updateZoom).toHaveBeenCalledWith(new Date('2019-10-01T06:24:00.000Z'), undefined);
-
- updateZoom = jest.fn();
- preventDefault = jest.fn();
- wrapper = shallowRender({ updateZoom });
- wrapper.instance().handleWheel({
- preventDefault,
- deltaX: 1,
- deltaY: 2,
- deltaZ: 0,
- pageX: 100,
- pageY: 1,
- deltaMode: 25,
- currentTarget: {
- getBoundingClientRect: () => ({
- bottom: 0,
- height: 100,
- width: 50,
- left: 0,
- right: 0,
- top: 10,
- x: 12,
- y: 23,
- toJSON: () => '',
- }),
- } as any as SVGElement,
- } as any as React.WheelEvent<SVGElement>);
- expect(preventDefault).toHaveBeenCalled();
- expect(updateZoom).toHaveBeenCalledWith(undefined, new Date('2019-10-02T20:48:00.000Z'));
-});
-
-it('should handle mouse out correcly', () => {
- const updateTooltip = jest.fn();
- const wrapper = shallowRender({ updateTooltip: undefined });
- wrapper.setState({
- mouseOver: true,
- selectedDate: new Date(),
- selectedDateXPos: 1,
- selectedDateIdx: 1,
- });
- wrapper.instance().handleMouseOut();
- expect(wrapper.state().mouseOver).toBe(true);
-
- wrapper.setProps({ updateTooltip });
- wrapper.instance().handleMouseOut();
- expect(wrapper.state().mouseOver).toBe(false);
- expect(wrapper.state().selectedDate).toBeUndefined();
- expect(wrapper.state().selectedDateXPos).toBeUndefined();
- expect(wrapper.state().selectedDateIdx).toBeUndefined();
- wrapper.instance().handleMouseOut();
-});
+it('should render correctly', () => {
+ const checkSnapShot = (props: Partial<PropsWithoutTheme> = {}, snapshotName = 'default') => {
+ const renderedComponent = renderComponent(props);
-it('should handle click correcly', () => {
- const updateSelectedDate = jest.fn();
- const wrapper = shallowRender({ updateSelectedDate });
- wrapper.setState({ selectedDate: new Date() });
+ // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
+ const svg = renderedComponent.container.querySelector("[class='line-chart']");
- wrapper.instance().handleClick();
- expect(updateSelectedDate).toHaveBeenCalledWith(wrapper.state().selectedDate);
+ expect(svg).toMatchSnapshot(snapshotName);
+ };
- wrapper.setProps({ updateSelectedDate: undefined });
- updateSelectedDate.mockClear();
- wrapper.instance().handleClick();
- expect(updateSelectedDate).not.toHaveBeenCalled();
+ checkSnapShot();
+ checkSnapShot({ disableZoom: false, updateZoom: () => {} }, 'Zoom enabled');
+ checkSnapShot({ formatYTick: (t) => `Nicer tick ${t}` }, 'format y tick');
+ checkSnapShot({ width: undefined }, 'no width');
+ checkSnapShot({ height: undefined }, 'no height');
+ checkSnapShot({ showAreas: undefined }, 'no areas');
+ checkSnapShot({ selectedDate: new Date('2019-10-01') }, 'selected date');
+ checkSnapShot({ metricType: MetricType.Rating }, 'rating metric');
+ checkSnapShot({ metricType: MetricType.Level }, 'level metric');
+ checkSnapShot({ zoomSpeed: 2 }, 'zoomSpeed');
+ checkSnapShot({ leakPeriodDate: new Date('2019-10-02T00:00:00.000Z') }, 'leakPeriodDate');
+ checkSnapShot({ basisCurve: true }, 'basisCurve');
});
-function shallowRender(props?: Partial<AdvancedTimeline['props']>) {
- return shallow<AdvancedTimeline>(
+function renderComponent(props?: Partial<PropsWithoutTheme>) {
+ return render(
<AdvancedTimeline
height={100}
maxYTicksCount={10}
translatedName: '',
data: [
{
- x: new Date('2019-10-01'),
+ x: new Date('2019-10-01T00:00:00.000Z'),
y: 1,
},
{
- x: new Date('2019-10-02'),
+ x: new Date('2019-10-02T00:00:00.000Z'),
y: 2,
},
],
translatedName: '',
data: [
{
- x: new Date('2019-10-03'),
+ x: new Date('2019-10-03T00:00:00.000Z'),
y: 3,
},
],
/>
);
}
-
-function mockData(i: number, date: string): Chart.Serie {
- return {
- name: `t${i}`,
- type: 'type',
- translatedName: '',
- data: [{ x: new Date(date), y: i }],
- };
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { scaleTime } from 'd3-scale';
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import { colors } from '../../../app/theme';
-import ZoomTimeLine from '../ZoomTimeLine';
-
-const series = [
- {
- data: [
- {
- x: new Date('2020-01-01'),
- y: 'beginning',
- },
- {
- x: new Date('2020-02-01'),
- y: 'end',
- },
- ],
- name: 'foo',
- translatedName: 'foo-translated',
- type: 'bar',
- },
-];
-
-it('should render correctly', () => {
- expect(shallowRender({ width: undefined })).toMatchSnapshot('no width');
- expect(shallowRender({ height: undefined })).toMatchSnapshot('no height');
-});
-
-it('should draw a graph with lines', () => {
- const wrapper = shallowRender();
- expect(wrapper.find('.line-chart-grid').exists()).toBe(true);
- expect(wrapper.find('.line-chart-path').exists()).toBe(true);
- expect(wrapper.find('.chart-zoom-tick').exists()).toBe(true);
- expect(wrapper.find('.line-chart-area').exists()).toBe(false);
-});
-
-it('should be zoomable', () => {
- expect(shallowRender().find('.chart-zoom').exists()).toBe(true);
-});
-
-it('should render a leak period', () => {
- expect(
- shallowRender({ leakPeriodDate: new Date('2020-01-01') })
- .find(`rect[fill="${colors.leakPrimaryColor}"]`)
- .exists()
- ).toBe(true);
-});
-
-it('should render areas under the graph lines', () => {
- expect(shallowRender({ showAreas: true }).find('.line-chart-area').exists()).toBe(true);
-});
-
-it('should handle zoom update correctly', () => {
- const updateZoom = jest.fn();
- const startDate = new Date('1970-01-01T00:00:00.001Z');
- const endDate = new Date('2000-01-01T00:00:00.001Z');
- let wrapper = shallowRender({ updateZoom, startDate, endDate });
- wrapper
- .instance()
- .handleZoomUpdate(scaleTime().domain([startDate, endDate]).range([0, 150]), [3, 50]);
- expect(updateZoom).toHaveBeenCalledWith(
- new Date('1970-08-08T03:21:36.001Z'),
- new Date('1980-01-01T08:00:00.001Z')
- );
-
- updateZoom.mockClear();
-
- // We throttle the handleZoomUpdate so re-render to avoid issue
- wrapper = shallowRender({ updateZoom, startDate, endDate });
- wrapper
- .instance()
- .handleZoomUpdate(scaleTime().domain([startDate, endDate]).range([0, 150]), [-1, 151]);
- expect(updateZoom).toHaveBeenCalledWith(undefined, undefined);
-});
-
-function shallowRender(props: Partial<ZoomTimeLine['props']> = {}) {
- return shallow<ZoomTimeLine>(
- <ZoomTimeLine
- width={300}
- series={series}
- updateZoom={jest.fn()}
- metricType="RATING"
- height={300}
- {...props}
- />
- );
-}
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render correctly 1`] = `
+exports[`should render correctly: Zoom enabled 1`] = `
<svg
- className="line-chart"
- height={100}
- width={100}
+ class="line-chart"
+ height="100"
+ width="100"
>
+ <defs>
+ <clippath
+ id="chart-clip"
+ >
+ <rect
+ height="34"
+ transform="translate(0,-5)"
+ width="40"
+ />
+ </clippath>
+ </defs>
<g
- transform="translate(60, 26)"
+ transform="translate(50, 26)"
>
<g>
- <g
- key="0"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={24}
- y2={24}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="24"
+ y2="24"
/>
</g>
- <g
- key="0.2"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={22.4}
- y2={22.4}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="22.4"
+ y2="22.4"
/>
</g>
- <g
- key="0.4"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={20.8}
- y2={20.8}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="20.8"
+ y2="20.8"
/>
</g>
- <g
- key="0.6"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={19.200000000000003}
- y2={19.200000000000003}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="19.200000000000003"
+ y2="19.200000000000003"
/>
</g>
- <g
- key="0.8"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={17.6}
- y2={17.6}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="17.6"
+ y2="17.6"
/>
</g>
- <g
- key="1"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={16}
- y2={16}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="16"
+ y2="16"
/>
</g>
- <g
- key="1.2"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={14.400000000000002}
- y2={14.400000000000002}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="14.400000000000002"
+ y2="14.400000000000002"
/>
</g>
- <g
- key="1.4"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={12.800000000000002}
- y2={12.800000000000002}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="12.800000000000002"
+ y2="12.800000000000002"
/>
</g>
- <g
- key="1.6"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={11.2}
- y2={11.2}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="11.2"
+ y2="11.2"
/>
</g>
- <g
- key="1.8"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={9.600000000000001}
- y2={9.600000000000001}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="9.600000000000001"
+ y2="9.600000000000001"
/>
</g>
- <g
- key="2"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={8}
- y2={8}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="8"
+ y2="8"
/>
</g>
- <g
- key="2.2"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={6.399999999999999}
- y2={6.399999999999999}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="6.399999999999999"
+ y2="6.399999999999999"
/>
</g>
- <g
- key="2.4"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={4.800000000000002}
- y2={4.800000000000002}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="4.800000000000002"
+ y2="4.800000000000002"
/>
</g>
- <g
- key="2.6"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={3.1999999999999993}
- y2={3.1999999999999993}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="3.1999999999999993"
+ y2="3.1999999999999993"
/>
</g>
- <g
- key="2.8"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={1.6000000000000023}
- y2={1.6000000000000023}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="1.6000000000000023"
+ y2="1.6000000000000023"
/>
</g>
- <g
- key="3"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={0}
- y2={0}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="0"
+ y2="0"
/>
</g>
</g>
transform="translate(0, 20)"
>
<text
- className="line-chart-tick"
- key="0"
- textAnchor="end"
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
transform="rotate(-35, 15, 24)"
- x={15}
- y={24}
+ x="15"
+ y="24"
>
October
</text>
<text
- className="line-chart-tick"
- key="1"
- textAnchor="end"
- transform="rotate(-35, 18.75, 24)"
- x={18.75}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 20, 24)"
+ x="20"
+ y="24"
>
06 AM
</text>
<text
- className="line-chart-tick"
- key="2"
- textAnchor="end"
- transform="rotate(-35, 22.5, 24)"
- x={22.5}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 25, 24)"
+ x="25"
+ y="24"
>
12 PM
</text>
<text
- className="line-chart-tick"
- key="3"
- textAnchor="end"
- transform="rotate(-35, 26.25, 24)"
- x={26.25}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 30, 24)"
+ x="30"
+ y="24"
>
06 PM
</text>
<text
- className="line-chart-tick"
- key="4"
- textAnchor="end"
- transform="rotate(-35, 30, 24)"
- x={30}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 35, 24)"
+ x="35"
+ y="24"
>
Wed 02
</text>
<text
- className="line-chart-tick"
- key="5"
- textAnchor="end"
- transform="rotate(-35, 33.75, 24)"
- x={33.75}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 40, 24)"
+ x="40"
+ y="24"
>
06 AM
</text>
<text
- className="line-chart-tick"
- key="6"
- textAnchor="end"
- transform="rotate(-35, 37.5, 24)"
- x={37.5}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 45, 24)"
+ x="45"
+ y="24"
>
12 PM
</text>
<text
- className="line-chart-tick"
- key="7"
- textAnchor="end"
- transform="rotate(-35, 41.25, 24)"
- x={41.25}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 50, 24)"
+ x="50"
+ y="24"
>
06 PM
</text>
</g>
<g>
<path
- className="line-chart-path line-chart-path-0"
- d="M0,16L15,8"
- key="test-1"
+ class="line-chart-path line-chart-path-0"
+ d="M0,16L20,8"
+ stroke="rgb(58,127,173)"
/>
<path
- className="line-chart-path line-chart-path-1"
- d="M30,0Z"
- key="test-2"
+ class="line-chart-path line-chart-path-1"
+ d="M40,0Z"
+ stroke="rgb(85,170,223)"
/>
</g>
<g>
<circle
- className="line-chart-dot line-chart-dot-1"
- cx={30}
- cy={0}
- key="test-20"
+ class="line-chart-dot line-chart-dot-1"
+ cx="40"
+ cy="0"
r="2"
/>
</g>
<rect
- className="chart-mouse-events-overlay"
- height={24}
- width={30}
+ class="chart-mouse-events-overlay"
+ height="24"
+ width="40"
/>
</g>
</svg>
`;
-exports[`should render correctly: Zoom enabled 1`] = `
+exports[`should render correctly: basisCurve 1`] = `
<svg
- className="line-chart"
- height={100}
- width={100}
+ class="line-chart"
+ height="100"
+ width="100"
>
- <defs>
- <clipPath
- id="chart-clip"
+ <g
+ transform="translate(50, 26)"
+ >
+ <g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="24"
+ y2="24"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="22.4"
+ y2="22.4"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="20.8"
+ y2="20.8"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="19.200000000000003"
+ y2="19.200000000000003"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="17.6"
+ y2="17.6"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="16"
+ y2="16"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="14.400000000000002"
+ y2="14.400000000000002"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="12.800000000000002"
+ y2="12.800000000000002"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="11.2"
+ y2="11.2"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="9.600000000000001"
+ y2="9.600000000000001"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="8"
+ y2="8"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="6.399999999999999"
+ y2="6.399999999999999"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="4.800000000000002"
+ y2="4.800000000000002"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="3.1999999999999993"
+ y2="3.1999999999999993"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="1.6000000000000023"
+ y2="1.6000000000000023"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="0"
+ y2="0"
+ />
+ </g>
+ </g>
+ <g
+ transform="translate(0, 20)"
>
- <rect
- height={34}
- transform="translate(0,-5)"
- width={30}
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 15, 24)"
+ x="15"
+ y="24"
+ >
+ October
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 20, 24)"
+ x="20"
+ y="24"
+ >
+ 06 AM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 25, 24)"
+ x="25"
+ y="24"
+ >
+ 12 PM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 30, 24)"
+ x="30"
+ y="24"
+ >
+ 06 PM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 35, 24)"
+ x="35"
+ y="24"
+ >
+ Wed 02
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 40, 24)"
+ x="40"
+ y="24"
+ >
+ 06 AM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 45, 24)"
+ x="45"
+ y="24"
+ >
+ 12 PM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 50, 24)"
+ x="50"
+ y="24"
+ >
+ 06 PM
+ </text>
+ </g>
+ <g>
+ <path
+ class="line-chart-path line-chart-path-0"
+ d="M0,16L20,8"
+ stroke="rgb(58,127,173)"
/>
- </clipPath>
- </defs>
+ <path
+ class="line-chart-path line-chart-path-1"
+ d="M40,0Z"
+ stroke="rgb(85,170,223)"
+ />
+ </g>
+ <g>
+ <circle
+ class="line-chart-dot line-chart-dot-1"
+ cx="40"
+ cy="0"
+ r="2"
+ />
+ </g>
+ <rect
+ class="chart-mouse-events-overlay"
+ height="24"
+ width="40"
+ />
+ </g>
+</svg>
+`;
+
+exports[`should render correctly: default 1`] = `
+<svg
+ class="line-chart"
+ height="100"
+ width="100"
+>
<g
- transform="translate(60, 26)"
+ transform="translate(50, 26)"
>
<g>
- <g
- key="0"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={24}
- y2={24}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="24"
+ y2="24"
/>
</g>
- <g
- key="0.2"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={22.4}
- y2={22.4}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="22.4"
+ y2="22.4"
/>
</g>
- <g
- key="0.4"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={20.8}
- y2={20.8}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="20.8"
+ y2="20.8"
/>
</g>
- <g
- key="0.6"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={19.200000000000003}
- y2={19.200000000000003}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="19.200000000000003"
+ y2="19.200000000000003"
/>
</g>
- <g
- key="0.8"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={17.6}
- y2={17.6}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="17.6"
+ y2="17.6"
/>
</g>
- <g
- key="1"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={16}
- y2={16}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="16"
+ y2="16"
/>
</g>
- <g
- key="1.2"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={14.400000000000002}
- y2={14.400000000000002}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="14.400000000000002"
+ y2="14.400000000000002"
/>
</g>
- <g
- key="1.4"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={12.800000000000002}
- y2={12.800000000000002}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="12.800000000000002"
+ y2="12.800000000000002"
/>
</g>
- <g
- key="1.6"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={11.2}
- y2={11.2}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="11.2"
+ y2="11.2"
/>
</g>
- <g
- key="1.8"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={9.600000000000001}
- y2={9.600000000000001}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="9.600000000000001"
+ y2="9.600000000000001"
/>
</g>
- <g
- key="2"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={8}
- y2={8}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="8"
+ y2="8"
/>
</g>
- <g
- key="2.2"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={6.399999999999999}
- y2={6.399999999999999}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="6.399999999999999"
+ y2="6.399999999999999"
/>
</g>
- <g
- key="2.4"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={4.800000000000002}
- y2={4.800000000000002}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="4.800000000000002"
+ y2="4.800000000000002"
/>
</g>
- <g
- key="2.6"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={3.1999999999999993}
- y2={3.1999999999999993}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="3.1999999999999993"
+ y2="3.1999999999999993"
/>
</g>
- <g
- key="2.8"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={1.6000000000000023}
- y2={1.6000000000000023}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="1.6000000000000023"
+ y2="1.6000000000000023"
/>
</g>
- <g
- key="3"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={0}
- y2={0}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="0"
+ y2="0"
/>
</g>
</g>
transform="translate(0, 20)"
>
<text
- className="line-chart-tick"
- key="0"
- textAnchor="end"
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
transform="rotate(-35, 15, 24)"
- x={15}
- y={24}
+ x="15"
+ y="24"
>
October
</text>
<text
- className="line-chart-tick"
- key="1"
- textAnchor="end"
- transform="rotate(-35, 18.75, 24)"
- x={18.75}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 20, 24)"
+ x="20"
+ y="24"
>
06 AM
</text>
<text
- className="line-chart-tick"
- key="2"
- textAnchor="end"
- transform="rotate(-35, 22.5, 24)"
- x={22.5}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 25, 24)"
+ x="25"
+ y="24"
>
12 PM
</text>
<text
- className="line-chart-tick"
- key="3"
- textAnchor="end"
- transform="rotate(-35, 26.25, 24)"
- x={26.25}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 30, 24)"
+ x="30"
+ y="24"
>
06 PM
</text>
<text
- className="line-chart-tick"
- key="4"
- textAnchor="end"
- transform="rotate(-35, 30, 24)"
- x={30}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 35, 24)"
+ x="35"
+ y="24"
>
Wed 02
</text>
<text
- className="line-chart-tick"
- key="5"
- textAnchor="end"
- transform="rotate(-35, 33.75, 24)"
- x={33.75}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 40, 24)"
+ x="40"
+ y="24"
>
06 AM
</text>
<text
- className="line-chart-tick"
- key="6"
- textAnchor="end"
- transform="rotate(-35, 37.5, 24)"
- x={37.5}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 45, 24)"
+ x="45"
+ y="24"
>
12 PM
</text>
<text
- className="line-chart-tick"
- key="7"
- textAnchor="end"
- transform="rotate(-35, 41.25, 24)"
- x={41.25}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 50, 24)"
+ x="50"
+ y="24"
>
06 PM
</text>
</g>
<g>
<path
- className="line-chart-path line-chart-path-0"
- d="M0,16L15,8"
- key="test-1"
+ class="line-chart-path line-chart-path-0"
+ d="M0,16L20,8"
+ stroke="rgb(58,127,173)"
/>
<path
- className="line-chart-path line-chart-path-1"
- d="M30,0Z"
- key="test-2"
+ class="line-chart-path line-chart-path-1"
+ d="M40,0Z"
+ stroke="rgb(85,170,223)"
/>
</g>
<g>
<circle
- className="line-chart-dot line-chart-dot-1"
- cx={30}
- cy={0}
- key="test-20"
+ class="line-chart-dot line-chart-dot-1"
+ cx="40"
+ cy="0"
r="2"
/>
</g>
<rect
- className="chart-mouse-events-overlay"
- height={24}
- onWheel={[Function]}
- width={30}
+ class="chart-mouse-events-overlay"
+ height="24"
+ width="40"
/>
</g>
</svg>
exports[`should render correctly: format y tick 1`] = `
<svg
- className="line-chart"
- height={100}
- width={100}
+ class="line-chart"
+ height="100"
+ width="100"
>
<g
- transform="translate(60, 26)"
+ transform="translate(50, 26)"
>
<g>
- <g
- key="0"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={24}
+ text-anchor="end"
+ x="0"
+ y="24"
>
Nicer tick 0
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={24}
- y2={24}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="24"
+ y2="24"
/>
</g>
- <g
- key="0.2"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={22.4}
+ text-anchor="end"
+ x="0"
+ y="22.4"
>
Nicer tick 0.2
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={22.4}
- y2={22.4}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="22.4"
+ y2="22.4"
/>
</g>
- <g
- key="0.4"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={20.8}
+ text-anchor="end"
+ x="0"
+ y="20.8"
>
Nicer tick 0.4
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={20.8}
- y2={20.8}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="20.8"
+ y2="20.8"
/>
</g>
- <g
- key="0.6"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={19.200000000000003}
+ text-anchor="end"
+ x="0"
+ y="19.200000000000003"
>
Nicer tick 0.6
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={19.200000000000003}
- y2={19.200000000000003}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="19.200000000000003"
+ y2="19.200000000000003"
/>
</g>
- <g
- key="0.8"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={17.6}
+ text-anchor="end"
+ x="0"
+ y="17.6"
>
Nicer tick 0.8
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={17.6}
- y2={17.6}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="17.6"
+ y2="17.6"
/>
</g>
- <g
- key="1"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={16}
+ text-anchor="end"
+ x="0"
+ y="16"
>
Nicer tick 1
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={16}
- y2={16}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="16"
+ y2="16"
/>
</g>
- <g
- key="1.2"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={14.400000000000002}
+ text-anchor="end"
+ x="0"
+ y="14.400000000000002"
>
Nicer tick 1.2
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={14.400000000000002}
- y2={14.400000000000002}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="14.400000000000002"
+ y2="14.400000000000002"
/>
</g>
- <g
- key="1.4"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={12.800000000000002}
+ text-anchor="end"
+ x="0"
+ y="12.800000000000002"
>
Nicer tick 1.4
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={12.800000000000002}
- y2={12.800000000000002}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="12.800000000000002"
+ y2="12.800000000000002"
/>
</g>
- <g
- key="1.6"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={11.2}
+ text-anchor="end"
+ x="0"
+ y="11.2"
>
Nicer tick 1.6
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={11.2}
- y2={11.2}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="11.2"
+ y2="11.2"
/>
</g>
- <g
- key="1.8"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={9.600000000000001}
+ text-anchor="end"
+ x="0"
+ y="9.600000000000001"
>
Nicer tick 1.8
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={9.600000000000001}
- y2={9.600000000000001}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="9.600000000000001"
+ y2="9.600000000000001"
/>
</g>
- <g
- key="2"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={8}
+ text-anchor="end"
+ x="0"
+ y="8"
>
Nicer tick 2
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={8}
- y2={8}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="8"
+ y2="8"
/>
</g>
- <g
- key="2.2"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={6.399999999999999}
+ text-anchor="end"
+ x="0"
+ y="6.399999999999999"
>
Nicer tick 2.2
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={6.399999999999999}
- y2={6.399999999999999}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="6.399999999999999"
+ y2="6.399999999999999"
/>
</g>
- <g
- key="2.4"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={4.800000000000002}
+ text-anchor="end"
+ x="0"
+ y="4.800000000000002"
>
Nicer tick 2.4
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={4.800000000000002}
- y2={4.800000000000002}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="4.800000000000002"
+ y2="4.800000000000002"
/>
</g>
- <g
- key="2.6"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={3.1999999999999993}
+ text-anchor="end"
+ x="0"
+ y="3.1999999999999993"
>
Nicer tick 2.6
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={3.1999999999999993}
- y2={3.1999999999999993}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="3.1999999999999993"
+ y2="3.1999999999999993"
/>
</g>
- <g
- key="2.8"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={1.6000000000000023}
+ text-anchor="end"
+ x="0"
+ y="1.6000000000000023"
>
Nicer tick 2.8
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={1.6000000000000023}
- y2={1.6000000000000023}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="1.6000000000000023"
+ y2="1.6000000000000023"
/>
</g>
- <g
- key="3"
- >
+ <g>
<text
- className="line-chart-tick line-chart-tick-x"
+ class="line-chart-tick line-chart-tick-x sw-body-sm"
dx="-1em"
dy="0.3em"
- textAnchor="end"
- x={0}
- y={0}
+ text-anchor="end"
+ x="0"
+ y="0"
>
Nicer tick 3
</text>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={0}
- y2={0}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="0"
+ y2="0"
/>
</g>
</g>
transform="translate(0, 20)"
>
<text
- className="line-chart-tick"
- key="0"
- textAnchor="end"
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
transform="rotate(-35, 15, 24)"
- x={15}
- y={24}
+ x="15"
+ y="24"
>
October
</text>
<text
- className="line-chart-tick"
- key="1"
- textAnchor="end"
- transform="rotate(-35, 18.75, 24)"
- x={18.75}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 20, 24)"
+ x="20"
+ y="24"
>
06 AM
</text>
<text
- className="line-chart-tick"
- key="2"
- textAnchor="end"
- transform="rotate(-35, 22.5, 24)"
- x={22.5}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 25, 24)"
+ x="25"
+ y="24"
>
12 PM
</text>
<text
- className="line-chart-tick"
- key="3"
- textAnchor="end"
- transform="rotate(-35, 26.25, 24)"
- x={26.25}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 30, 24)"
+ x="30"
+ y="24"
>
06 PM
</text>
<text
- className="line-chart-tick"
- key="4"
- textAnchor="end"
- transform="rotate(-35, 30, 24)"
- x={30}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 35, 24)"
+ x="35"
+ y="24"
>
Wed 02
</text>
<text
- className="line-chart-tick"
- key="5"
- textAnchor="end"
- transform="rotate(-35, 33.75, 24)"
- x={33.75}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 40, 24)"
+ x="40"
+ y="24"
>
06 AM
</text>
<text
- className="line-chart-tick"
- key="6"
- textAnchor="end"
- transform="rotate(-35, 37.5, 24)"
- x={37.5}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 45, 24)"
+ x="45"
+ y="24"
>
12 PM
</text>
<text
- className="line-chart-tick"
- key="7"
- textAnchor="end"
- transform="rotate(-35, 41.25, 24)"
- x={41.25}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 50, 24)"
+ x="50"
+ y="24"
>
06 PM
</text>
</g>
<g>
<path
- className="line-chart-path line-chart-path-0"
- d="M0,16L15,8"
- key="test-1"
+ class="line-chart-path line-chart-path-0"
+ d="M0,16L20,8"
+ stroke="rgb(58,127,173)"
/>
<path
- className="line-chart-path line-chart-path-1"
- d="M30,0Z"
- key="test-2"
+ class="line-chart-path line-chart-path-1"
+ d="M40,0Z"
+ stroke="rgb(85,170,223)"
/>
</g>
<g>
<circle
- className="line-chart-dot line-chart-dot-1"
- cx={30}
- cy={0}
- key="test-20"
+ class="line-chart-dot line-chart-dot-1"
+ cx="40"
+ cy="0"
r="2"
/>
</g>
<rect
- className="chart-mouse-events-overlay"
- height={24}
- width={30}
+ class="chart-mouse-events-overlay"
+ height="24"
+ width="40"
/>
</g>
</svg>
`;
-exports[`should render correctly: no areas 1`] = `
+exports[`should render correctly: leakPeriodDate 1`] = `
<svg
- className="line-chart"
- height={100}
- width={100}
+ class="line-chart"
+ height="100"
+ width="100"
>
<g
- transform="translate(60, 26)"
+ transform="translate(50, 26)"
>
+ <rect
+ class="leak-chart-rect"
+ fill="rgba(159,169,237,0.15)"
+ height="24"
+ width="20"
+ x="20"
+ y="0"
+ />
<g>
- <g
- key="0"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={24}
- y2={24}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="24"
+ y2="24"
/>
</g>
- <g
- key="0.2"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={22.4}
- y2={22.4}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="22.4"
+ y2="22.4"
/>
</g>
- <g
- key="0.4"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={20.8}
- y2={20.8}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="20.8"
+ y2="20.8"
/>
</g>
- <g
- key="0.6"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={19.200000000000003}
- y2={19.200000000000003}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="19.200000000000003"
+ y2="19.200000000000003"
/>
</g>
- <g
- key="0.8"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={17.6}
- y2={17.6}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="17.6"
+ y2="17.6"
/>
</g>
- <g
- key="1"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={16}
- y2={16}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="16"
+ y2="16"
/>
</g>
- <g
- key="1.2"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={14.400000000000002}
- y2={14.400000000000002}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="14.400000000000002"
+ y2="14.400000000000002"
/>
</g>
- <g
- key="1.4"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={12.800000000000002}
- y2={12.800000000000002}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="12.800000000000002"
+ y2="12.800000000000002"
/>
</g>
- <g
- key="1.6"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={11.2}
- y2={11.2}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="11.2"
+ y2="11.2"
/>
</g>
- <g
- key="1.8"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={9.600000000000001}
- y2={9.600000000000001}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="9.600000000000001"
+ y2="9.600000000000001"
/>
</g>
- <g
- key="2"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={8}
- y2={8}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="8"
+ y2="8"
/>
</g>
- <g
- key="2.2"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={6.399999999999999}
- y2={6.399999999999999}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="6.399999999999999"
+ y2="6.399999999999999"
/>
</g>
- <g
- key="2.4"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={4.800000000000002}
- y2={4.800000000000002}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="4.800000000000002"
+ y2="4.800000000000002"
/>
</g>
- <g
- key="2.6"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={3.1999999999999993}
- y2={3.1999999999999993}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="3.1999999999999993"
+ y2="3.1999999999999993"
/>
</g>
- <g
- key="2.8"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={1.6000000000000023}
- y2={1.6000000000000023}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="1.6000000000000023"
+ y2="1.6000000000000023"
/>
</g>
- <g
- key="3"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={0}
- y2={0}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="0"
+ y2="0"
/>
</g>
</g>
transform="translate(0, 20)"
>
<text
- className="line-chart-tick"
- key="0"
- textAnchor="end"
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
transform="rotate(-35, 15, 24)"
- x={15}
- y={24}
+ x="15"
+ y="24"
>
October
</text>
<text
- className="line-chart-tick"
- key="1"
- textAnchor="end"
- transform="rotate(-35, 18.75, 24)"
- x={18.75}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 20, 24)"
+ x="20"
+ y="24"
>
06 AM
</text>
<text
- className="line-chart-tick"
- key="2"
- textAnchor="end"
- transform="rotate(-35, 22.5, 24)"
- x={22.5}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 25, 24)"
+ x="25"
+ y="24"
>
12 PM
</text>
<text
- className="line-chart-tick"
- key="3"
- textAnchor="end"
- transform="rotate(-35, 26.25, 24)"
- x={26.25}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 30, 24)"
+ x="30"
+ y="24"
>
06 PM
</text>
<text
- className="line-chart-tick"
- key="4"
- textAnchor="end"
- transform="rotate(-35, 30, 24)"
- x={30}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 35, 24)"
+ x="35"
+ y="24"
>
Wed 02
</text>
<text
- className="line-chart-tick"
- key="5"
- textAnchor="end"
- transform="rotate(-35, 33.75, 24)"
- x={33.75}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 40, 24)"
+ x="40"
+ y="24"
>
06 AM
</text>
<text
- className="line-chart-tick"
- key="6"
- textAnchor="end"
- transform="rotate(-35, 37.5, 24)"
- x={37.5}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 45, 24)"
+ x="45"
+ y="24"
>
12 PM
</text>
<text
- className="line-chart-tick"
- key="7"
- textAnchor="end"
- transform="rotate(-35, 41.25, 24)"
- x={41.25}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 50, 24)"
+ x="50"
+ y="24"
>
06 PM
</text>
</g>
<g>
<path
- className="line-chart-path line-chart-path-0"
- d="M0,16L15,8"
- key="test-1"
+ class="line-chart-path line-chart-path-0"
+ d="M0,16L20,8"
+ stroke="rgb(58,127,173)"
/>
<path
- className="line-chart-path line-chart-path-1"
- d="M30,0Z"
- key="test-2"
+ class="line-chart-path line-chart-path-1"
+ d="M40,0Z"
+ stroke="rgb(85,170,223)"
/>
</g>
<g>
<circle
- className="line-chart-dot line-chart-dot-1"
- cx={30}
- cy={0}
- key="test-20"
+ class="line-chart-dot line-chart-dot-1"
+ cx="40"
+ cy="0"
r="2"
/>
</g>
<rect
- className="chart-mouse-events-overlay"
- height={24}
- width={30}
+ class="chart-mouse-events-overlay"
+ height="24"
+ width="40"
/>
</g>
</svg>
`;
-exports[`should render correctly: no height 1`] = `<div />`;
-
-exports[`should render correctly: no width 1`] = `<div />`;
-
-exports[`should render leak legend correctly 1`] = `
+exports[`should render correctly: level metric 1`] = `
<svg
- className="line-chart"
- height={100}
- width={100}
+ class="line-chart"
+ height="100"
+ width="100"
>
<g
- transform="translate(60, 26)"
+ transform="translate(50, 26)"
>
- <rect
- fill="#fbf3d5"
- height={16}
- width={15}
- x={15}
- y={-16}
- />
- <text
- className="new-code-legend"
- textAnchor="start"
- x={19}
- y={-4}
- >
- new code
- </text>
- <rect
- className="leak-chart-rect"
- fill="#fbf3d5"
- height={24}
- width={15}
- x={15}
- y={0}
- />
<g>
- <g
- key="0"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={24}
- y2={24}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="24"
+ y2="24"
/>
</g>
- <g
- key="0.2"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={22.4}
- y2={22.4}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="12"
+ y2="12"
/>
</g>
- <g
- key="0.4"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={20.8}
- y2={20.8}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="0"
+ y2="0"
/>
</g>
- <g
- key="0.6"
+ </g>
+ <g
+ transform="translate(0, 20)"
+ >
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 15, 24)"
+ x="15"
+ y="24"
>
- <line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={19.200000000000003}
- y2={19.200000000000003}
- />
- </g>
- <g
- key="0.8"
+ October
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 20, 24)"
+ x="20"
+ y="24"
>
- <line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={17.6}
- y2={17.6}
- />
- </g>
- <g
- key="1"
+ 06 AM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 25, 24)"
+ x="25"
+ y="24"
>
- <line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={16}
- y2={16}
- />
- </g>
- <g
- key="1.2"
+ 12 PM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 30, 24)"
+ x="30"
+ y="24"
>
- <line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={14.400000000000002}
- y2={14.400000000000002}
- />
- </g>
- <g
- key="1.4"
+ 06 PM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 35, 24)"
+ x="35"
+ y="24"
>
- <line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={12.800000000000002}
- y2={12.800000000000002}
- />
- </g>
- <g
- key="1.6"
+ Wed 02
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 40, 24)"
+ x="40"
+ y="24"
>
- <line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={11.2}
- y2={11.2}
- />
- </g>
- <g
- key="1.8"
+ 06 AM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 45, 24)"
+ x="45"
+ y="24"
>
- <line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={9.600000000000001}
- y2={9.600000000000001}
- />
- </g>
- <g
- key="2"
+ 12 PM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 50, 24)"
+ x="50"
+ y="24"
>
- <line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={8}
- y2={8}
+ 06 PM
+ </text>
+ </g>
+ <g>
+ <path
+ class="line-chart-path line-chart-path-0"
+ d="M0,NaNL20,NaN"
+ stroke="rgb(58,127,173)"
+ />
+ <path
+ class="line-chart-path line-chart-path-1"
+ d="M40,NaNZ"
+ stroke="rgb(85,170,223)"
+ />
+ </g>
+ <g>
+ <circle
+ class="line-chart-dot line-chart-dot-1"
+ cx="40"
+ r="2"
+ />
+ </g>
+ <rect
+ class="chart-mouse-events-overlay"
+ height="24"
+ width="40"
+ />
+ </g>
+</svg>
+`;
+
+exports[`should render correctly: no areas 1`] = `
+<svg
+ class="line-chart"
+ height="100"
+ width="100"
+>
+ <g
+ transform="translate(50, 26)"
+ >
+ <g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="24"
+ y2="24"
/>
</g>
- <g
- key="2.2"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={6.399999999999999}
- y2={6.399999999999999}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="22.4"
+ y2="22.4"
/>
</g>
- <g
- key="2.4"
- >
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={4.800000000000002}
- y2={4.800000000000002}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="20.8"
+ y2="20.8"
/>
</g>
- <g
- key="2.6"
- >
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="19.200000000000003"
+ y2="19.200000000000003"
+ />
+ </g>
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={3.1999999999999993}
- y2={3.1999999999999993}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="17.6"
+ y2="17.6"
/>
</g>
- <g
- key="2.8"
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="16"
+ y2="16"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="14.400000000000002"
+ y2="14.400000000000002"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="12.800000000000002"
+ y2="12.800000000000002"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="11.2"
+ y2="11.2"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="9.600000000000001"
+ y2="9.600000000000001"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="8"
+ y2="8"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="6.399999999999999"
+ y2="6.399999999999999"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="4.800000000000002"
+ y2="4.800000000000002"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="3.1999999999999993"
+ y2="3.1999999999999993"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="1.6000000000000023"
+ y2="1.6000000000000023"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="0"
+ y2="0"
+ />
+ </g>
+ </g>
+ <g
+ transform="translate(0, 20)"
+ >
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 15, 24)"
+ x="15"
+ y="24"
+ >
+ October
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 20, 24)"
+ x="20"
+ y="24"
+ >
+ 06 AM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 25, 24)"
+ x="25"
+ y="24"
>
+ 12 PM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 30, 24)"
+ x="30"
+ y="24"
+ >
+ 06 PM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 35, 24)"
+ x="35"
+ y="24"
+ >
+ Wed 02
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 40, 24)"
+ x="40"
+ y="24"
+ >
+ 06 AM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 45, 24)"
+ x="45"
+ y="24"
+ >
+ 12 PM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 50, 24)"
+ x="50"
+ y="24"
+ >
+ 06 PM
+ </text>
+ </g>
+ <g>
+ <path
+ class="line-chart-path line-chart-path-0"
+ d="M0,16L20,8"
+ stroke="rgb(58,127,173)"
+ />
+ <path
+ class="line-chart-path line-chart-path-1"
+ d="M40,0Z"
+ stroke="rgb(85,170,223)"
+ />
+ </g>
+ <g>
+ <circle
+ class="line-chart-dot line-chart-dot-1"
+ cx="40"
+ cy="0"
+ r="2"
+ />
+ </g>
+ <rect
+ class="chart-mouse-events-overlay"
+ height="24"
+ width="40"
+ />
+ </g>
+</svg>
+`;
+
+exports[`should render correctly: no height 1`] = `null`;
+
+exports[`should render correctly: no width 1`] = `null`;
+
+exports[`should render correctly: rating metric 1`] = `
+<svg
+ class="line-chart"
+ height="100"
+ width="100"
+>
+ <g
+ transform="translate(50, 26)"
+ >
+ <g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="24"
+ y2="24"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="18"
+ y2="18"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="12"
+ y2="12"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="6"
+ y2="6"
+ />
+ </g>
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={1.6000000000000023}
- y2={1.6000000000000023}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="0"
+ y2="0"
/>
</g>
- <g
- key="3"
+ </g>
+ <g
+ transform="translate(0, 20)"
+ >
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 15, 24)"
+ x="15"
+ y="24"
+ >
+ October
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 20, 24)"
+ x="20"
+ y="24"
+ >
+ 06 AM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 25, 24)"
+ x="25"
+ y="24"
+ >
+ 12 PM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 30, 24)"
+ x="30"
+ y="24"
>
+ 06 PM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 35, 24)"
+ x="35"
+ y="24"
+ >
+ Wed 02
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 40, 24)"
+ x="40"
+ y="24"
+ >
+ 06 AM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 45, 24)"
+ x="45"
+ y="24"
+ >
+ 12 PM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 50, 24)"
+ x="50"
+ y="24"
+ >
+ 06 PM
+ </text>
+ </g>
+ <g>
+ <path
+ class="line-chart-path line-chart-path-0"
+ d="M0,0L20,6"
+ stroke="rgb(58,127,173)"
+ />
+ <path
+ class="line-chart-path line-chart-path-1"
+ d="M40,12Z"
+ stroke="rgb(85,170,223)"
+ />
+ </g>
+ <g>
+ <circle
+ class="line-chart-dot line-chart-dot-1"
+ cx="40"
+ cy="12"
+ r="2"
+ />
+ </g>
+ <rect
+ class="chart-mouse-events-overlay"
+ height="24"
+ width="40"
+ />
+ </g>
+</svg>
+`;
+
+exports[`should render correctly: selected date 1`] = `
+<svg
+ class="line-chart"
+ height="100"
+ width="100"
+>
+ <g
+ transform="translate(50, 26)"
+ >
+ <g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="24"
+ y2="24"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="22.4"
+ y2="22.4"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="20.8"
+ y2="20.8"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="19.200000000000003"
+ y2="19.200000000000003"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="17.6"
+ y2="17.6"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="16"
+ y2="16"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="14.400000000000002"
+ y2="14.400000000000002"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="12.800000000000002"
+ y2="12.800000000000002"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="11.2"
+ y2="11.2"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="9.600000000000001"
+ y2="9.600000000000001"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="8"
+ y2="8"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="6.399999999999999"
+ y2="6.399999999999999"
+ />
+ </g>
+ <g>
<line
- className="line-chart-grid"
- x1={0}
- x2={30}
- y1={0}
- y2={0}
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="4.800000000000002"
+ y2="4.800000000000002"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="3.1999999999999993"
+ y2="3.1999999999999993"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="1.6000000000000023"
+ y2="1.6000000000000023"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="0"
+ y2="0"
/>
</g>
</g>
transform="translate(0, 20)"
>
<text
- className="line-chart-tick"
- key="0"
- textAnchor="end"
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
transform="rotate(-35, 15, 24)"
- x={15}
- y={24}
+ x="15"
+ y="24"
>
October
</text>
<text
- className="line-chart-tick"
- key="1"
- textAnchor="end"
- transform="rotate(-35, 18.75, 24)"
- x={18.75}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 20, 24)"
+ x="20"
+ y="24"
>
06 AM
</text>
<text
- className="line-chart-tick"
- key="2"
- textAnchor="end"
- transform="rotate(-35, 22.5, 24)"
- x={22.5}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 25, 24)"
+ x="25"
+ y="24"
>
12 PM
</text>
<text
- className="line-chart-tick"
- key="3"
- textAnchor="end"
- transform="rotate(-35, 26.25, 24)"
- x={26.25}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 30, 24)"
+ x="30"
+ y="24"
>
06 PM
</text>
<text
- className="line-chart-tick"
- key="4"
- textAnchor="end"
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 35, 24)"
+ x="35"
+ y="24"
+ >
+ Wed 02
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 40, 24)"
+ x="40"
+ y="24"
+ >
+ 06 AM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 45, 24)"
+ x="45"
+ y="24"
+ >
+ 12 PM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 50, 24)"
+ x="50"
+ y="24"
+ >
+ 06 PM
+ </text>
+ </g>
+ <g>
+ <path
+ class="line-chart-path line-chart-path-0"
+ d="M0,16L20,8"
+ stroke="rgb(58,127,173)"
+ />
+ <path
+ class="line-chart-path line-chart-path-1"
+ d="M40,0Z"
+ stroke="rgb(85,170,223)"
+ />
+ </g>
+ <g>
+ <circle
+ class="line-chart-dot line-chart-dot-1"
+ cx="40"
+ cy="0"
+ r="2"
+ />
+ </g>
+ <g>
+ <line
+ class="line-tooltip"
+ x1="0"
+ x2="0"
+ y1="24"
+ y2="0"
+ />
+ <circle
+ class="line-chart-dot line-chart-dot-0"
+ cx="0"
+ cy="16"
+ r="4"
+ />
+ <circle
+ class="line-chart-dot line-chart-dot-1"
+ cx="0"
+ cy="0"
+ r="4"
+ />
+ </g>
+ <rect
+ class="chart-mouse-events-overlay"
+ height="24"
+ width="40"
+ />
+ </g>
+</svg>
+`;
+
+exports[`should render correctly: zoomSpeed 1`] = `
+<svg
+ class="line-chart"
+ height="100"
+ width="100"
+>
+ <g
+ transform="translate(50, 26)"
+ >
+ <g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="24"
+ y2="24"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="22.4"
+ y2="22.4"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="20.8"
+ y2="20.8"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="19.200000000000003"
+ y2="19.200000000000003"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="17.6"
+ y2="17.6"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="16"
+ y2="16"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="14.400000000000002"
+ y2="14.400000000000002"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="12.800000000000002"
+ y2="12.800000000000002"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="11.2"
+ y2="11.2"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="9.600000000000001"
+ y2="9.600000000000001"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="8"
+ y2="8"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="6.399999999999999"
+ y2="6.399999999999999"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="4.800000000000002"
+ y2="4.800000000000002"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="3.1999999999999993"
+ y2="3.1999999999999993"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="1.6000000000000023"
+ y2="1.6000000000000023"
+ />
+ </g>
+ <g>
+ <line
+ class="line-chart-grid"
+ x1="0"
+ x2="40"
+ y1="0"
+ y2="0"
+ />
+ </g>
+ </g>
+ <g
+ transform="translate(0, 20)"
+ >
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 15, 24)"
+ x="15"
+ y="24"
+ >
+ October
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 20, 24)"
+ x="20"
+ y="24"
+ >
+ 06 AM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 25, 24)"
+ x="25"
+ y="24"
+ >
+ 12 PM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
transform="rotate(-35, 30, 24)"
- x={30}
- y={24}
+ x="30"
+ y="24"
+ >
+ 06 PM
+ </text>
+ <text
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 35, 24)"
+ x="35"
+ y="24"
>
Wed 02
</text>
<text
- className="line-chart-tick"
- key="5"
- textAnchor="end"
- transform="rotate(-35, 33.75, 24)"
- x={33.75}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 40, 24)"
+ x="40"
+ y="24"
>
06 AM
</text>
<text
- className="line-chart-tick"
- key="6"
- textAnchor="end"
- transform="rotate(-35, 37.5, 24)"
- x={37.5}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 45, 24)"
+ x="45"
+ y="24"
>
12 PM
</text>
<text
- className="line-chart-tick"
- key="7"
- textAnchor="end"
- transform="rotate(-35, 41.25, 24)"
- x={41.25}
- y={24}
+ class="line-chart-tick sw-body-sm"
+ text-anchor="end"
+ transform="rotate(-35, 50, 24)"
+ x="50"
+ y="24"
>
06 PM
</text>
</g>
<g>
<path
- className="line-chart-path line-chart-path-0"
- d="M0,16L15,8"
- key="test-1"
+ class="line-chart-path line-chart-path-0"
+ d="M0,16L20,8"
+ stroke="rgb(58,127,173)"
/>
<path
- className="line-chart-path line-chart-path-1"
- d="M30,0Z"
- key="test-2"
+ class="line-chart-path line-chart-path-1"
+ d="M40,0Z"
+ stroke="rgb(85,170,223)"
/>
</g>
<g>
<circle
- className="line-chart-dot line-chart-dot-1"
- cx={30}
- cy={0}
- key="test-20"
+ class="line-chart-dot line-chart-dot-1"
+ cx="40"
+ cy="0"
r="2"
/>
</g>
<rect
- className="chart-mouse-events-overlay"
- height={24}
- width={30}
+ class="chart-mouse-events-overlay"
+ height="24"
+ width="40"
/>
</g>
</svg>
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly: no height 1`] = `<div />`;
-
-exports[`should render correctly: no width 1`] = `<div />`;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { useTheme } from '@emotion/react';
import classNames from 'classnames';
+import { Theme, themeColor } from 'design-system';
import * as React from 'react';
import Icon from './Icon';
index: number;
}
-export default function ChartLegendIcon({ index, className }: Props) {
+export function ChartLegendIcon({ index, className }: Props) {
+ const theme = useTheme() as Theme;
+
return (
- <Icon className={className} aria-hidden={true} width={21}>
+ <Icon className={className} aria-hidden={true} width={20}>
<path
- className={classNames('line-chart-path line-chart-path-legend', 'line-chart-path-' + index)}
- d="M0 8 L 21 8"
+ className={classNames('line-chart-path line-chart-path-legend', `line-chart-path-${index}`)}
+ d="M0 8 L 20 8"
+ stroke={themeColor(`graphLineColor.${index}` as Parameters<typeof themeColor>[0])({
+ theme,
+ })}
/>
</Icon>
);
* 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 { translate, translateWithParameters } from '../../helpers/l10n';
import { formatMeasure } from '../../helpers/measures';
+import { MetricType } from '../../types/metrics';
import './Rating.css';
interface Props extends React.AriaAttributes {
</span>
);
}
- const formatted = formatMeasure(value, 'RATING');
+
+ const formatted = formatMeasure(value, MetricType.Rating);
+
return (
<span
aria-label={translateWithParameters('metric.has_rating_X', formatted)}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
const path = require('path');
const { fontFamily } = require('tailwindcss/defaultTheme');
const utilities = require('./tailwind-utilities');
12: '3rem', // 48px
16: '4rem', // 64px
24: '6rem', // 96px
+ 32: '8rem', // 128px
40: '10rem', // 160px
64: '16rem', // 256px
hotspot.filters.status.acknowledged=Acknowledged
hotspot.filters.status.fixed=Fixed
hotspot.filters.period=Period filter
-hotspot.filters.period.since_leak_period=New code
+hotspot.filters.period.since_leak_period=New Code
hotspot.filters.period.overall=Overall code
hotspot.filters.status.safe=Safe
hotspot.filters.show_all=Show all hotspots
project_activity.new_code_period_start=New Code Period starts here
project_activity.new_code_period_start.help=The analysis before this mark is the baseline for New Code comparison
-project_activity.graphs.choose_type=Choose graph type
project_activity.graphs.explanation_x=This interactive graph shows data for the following project measures over time: {0}
project_activity.graphs.issues=Issues
project_activity.graphs.coverage=Coverage