]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19026 Adapt the Graph component to MIUI
authorDavid Cho-Lerat <david.cho-lerat@sonarsource.com>
Mon, 24 Apr 2023 14:59:24 +0000 (16:59 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 25 Apr 2023 20:03:00 +0000 (20:03 +0000)
38 files changed:
server/sonar-web/design-system/src/components/NewCodeLegend.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/__tests__/NewCodeLegend-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/index.ts
server/sonar-web/design-system/src/helpers/index.ts
server/sonar-web/design-system/src/index.ts
server/sonar-web/design-system/src/theme/index.ts
server/sonar-web/design-system/src/theme/light.ts
server/sonar-web/design-system/src/theme/withTheme.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/styles/components/page.css
server/sonar-web/src/main/js/apps/overview/branches/ActivityPanel.tsx
server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx
server/sonar-web/src/main/js/apps/overview/styles.css
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-it.tsx
server/sonar-web/src/main/js/components/activity-graph/GraphHistory.tsx
server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx
server/sonar-web/src/main/js/components/activity-graph/GraphsLegendCustom.tsx
server/sonar-web/src/main/js/components/activity-graph/GraphsLegendItem.tsx
server/sonar-web/src/main/js/components/activity-graph/GraphsLegendStatic.tsx
server/sonar-web/src/main/js/components/activity-graph/GraphsTooltips.tsx
server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContent.tsx
server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentEvents.tsx
server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentIssues.tsx
server/sonar-web/src/main/js/components/activity-graph/GraphsZoom.tsx
server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltips-it.tsx
server/sonar-web/src/main/js/components/activity-graph/styles.css
server/sonar-web/src/main/js/components/activity-graph/utils.ts
server/sonar-web/src/main/js/components/charts/AdvancedTimeline.tsx
server/sonar-web/src/main/js/components/charts/LineChart.css
server/sonar-web/src/main/js/components/charts/LineChart.tsx
server/sonar-web/src/main/js/components/charts/ZoomTimeLine.tsx
server/sonar-web/src/main/js/components/charts/__tests__/AdvancedTimeline-test.tsx
server/sonar-web/src/main/js/components/charts/__tests__/ZoomTimeLine-test.tsx [deleted file]
server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/AdvancedTimeline-test.tsx.snap
server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/ZoomTimeLine-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/icons/ChartLegendIcon.tsx
server/sonar-web/src/main/js/components/ui/Rating.tsx
server/sonar-web/tailwind.base.config.js
sonar-core/src/main/resources/org/sonar/l10n/core.properties

diff --git a/server/sonar-web/design-system/src/components/NewCodeLegend.tsx b/server/sonar-web/design-system/src/components/NewCodeLegend.tsx
new file mode 100644 (file)
index 0000000..a6342d2
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * 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>
+  );
+}
diff --git a/server/sonar-web/design-system/src/components/__tests__/NewCodeLegend-test.tsx b/server/sonar-web/design-system/src/components/__tests__/NewCodeLegend-test.tsx
new file mode 100644 (file)
index 0000000..a11dcac
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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],
+  });
+});
index 93058100cf37e58680f20485d27c6f3a9c80d4b7..41748cd06e9e99a1dc2d6ecd233f34c13a611403 100644 (file)
@@ -40,6 +40,7 @@ export * from './MainMenu';
 export * from './MainMenuItem';
 export * from './MetricsRatingBadge';
 export * from './NavBarTabs';
+export * from './NewCodeLegend';
 export { QualityGateIndicator } from './QualityGateIndicator';
 export * from './SizeIndicator';
 export * from './SonarQubeLogo';
index 427c828d24fae5ce88a2218856239c3c494ca3aa..5e62e8b766f105fc5851997f8d800f7aece1d6de 100644 (file)
@@ -21,3 +21,4 @@
 export * from './colors';
 export * from './constants';
 export * from './positioning';
+export * from './theme';
index c8e853ca7a18903999e359c522df8d0bf8635c3b..9c4c8f50bfb22729049c9c3e70bc8dc60efda5b1 100644 (file)
@@ -21,4 +21,5 @@
 export * from './components';
 export * from './helpers';
 export * from './theme';
+export * from './types/measures';
 export * from './types/theme';
index 6b8c84a5721b7c4e9e9893ca4041e78c879b8965..6b5303116b802ce9e0c1ff818bf3fb85d0def8e8 100644 (file)
@@ -17,4 +17,6 @@
  * 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';
index df8bb271834b60dcba3db47ed487592d1cbbda16..8ef9b7d1d081dbafe4f5bec531ba2c6efd77cb43 100644 (file)
@@ -43,7 +43,7 @@ const danger = {
   darker: COLORS.red[800],
 };
 
-const lightTheme = {
+export const lightTheme = {
   id: 'light-theme',
   highlightTheme: 'atom-one-light.css',
   logo: 'sonarcloud-logo-black.svg',
@@ -381,10 +381,10 @@ const lightTheme = {
 
     // 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],
@@ -441,9 +441,9 @@ const lightTheme = {
     '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],
@@ -744,5 +744,3 @@ const lightTheme = {
     GitLabPipeline: '/images/alms/gitlab.svg',
   },
 };
-
-export default lightTheme;
diff --git a/server/sonar-web/design-system/src/theme/withTheme.tsx b/server/sonar-web/design-system/src/theme/withTheme.tsx
new file mode 100644 (file)
index 0000000..64e657f
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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} />;
+  };
+}
index 64ecdd667bc6868e0abf8a49336f686ad70ba88d..966a7cc80c51ec31dfd4a50223f5e87b8f203094 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 .white-page {
   background-color: #fff !important;
 }
@@ -29,7 +30,6 @@
 }
 
 .page {
-  position: relative;
   z-index: var(--normalZIndex);
   padding: 10px 20px;
 }
index 41edf6d1698e21cf6d6958ced23c37c0c177f95f..8b1060beeba3e662bbb77d0a733fafe4a97fc1db 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import * as React from 'react';
 import GraphsHeader from '../../../components/activity-graph/GraphsHeader';
 import GraphsHistory from '../../../components/activity-graph/GraphsHistory';
@@ -72,15 +73,18 @@ export function ActivityPanel(props: ActivityPanelProps) {
   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;
   }
@@ -94,7 +98,7 @@ export function ActivityPanel(props: ActivityPanelProps) {
       <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={[]}
index 428ef513c29948722d584009303ec9c93fa47828..a96481a7a597ba345fd358d26f768f9672977715 100644 (file)
  * 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 {
@@ -40,7 +40,7 @@ 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';
@@ -58,11 +58,11 @@ jest.mock('../../../../api/measures', () => {
 
         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(
@@ -350,10 +350,20 @@ it.each([
 
 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,
index 19dfba11686c3ec730072c9005f5003a0bc7e05b..11cefef0cf95637abb26aa1092a52d72c79b06ba 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 .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 {
index d18daecdff6c366fb83139fa7f3351dd8ba6752f..87132b7d8bf63167878fe9cb1744f3e1fae61861 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import { screen, waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import { keyBy, times } from 'lodash';
@@ -66,6 +67,7 @@ beforeEach(() => {
   jest.clearAllMocks();
   projectActivityHandler.reset();
   timeMachineHandler.reset();
+
   timeMachineHandler.setMeasureHistory(
     [
       MetricKey.bugs,
@@ -97,6 +99,7 @@ describe('rendering', () => {
 
   it('should correctly show the baseline marker', async () => {
     const { ui } = getPageObject();
+
     renderProjectActivityAppContainer(
       mockComponent({
         leakPeriodDate: parseDate('2017-03-01T22:00:00.000Z').toDateString(),
@@ -105,6 +108,7 @@ describe('rendering', () => {
         ],
       })
     );
+
     await ui.appLoaded();
 
     expect(ui.baseline.get()).toBeInTheDocument();
@@ -112,6 +116,7 @@ describe('rendering', () => {
 
   it('should only show certain security hotspot-related metrics for a project', async () => {
     const { ui } = getPageObject();
+
     renderProjectActivityAppContainer(
       mockComponent({
         breadcrumbs: [
@@ -130,6 +135,7 @@ describe('rendering', () => {
     'should only show certain security hotspot-related metrics for a %s',
     async (qualifier) => {
       const { ui } = getPageObject();
+
       renderProjectActivityAppContainer(
         mockComponent({
           qualifier,
@@ -140,6 +146,7 @@ describe('rendering', () => {
       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();
@@ -152,6 +159,7 @@ describe('CRUD', () => {
     const { ui } = getPageObject();
     const initialValue = '1.1-SNAPSHOT';
     const updatedValue = '1.1--SNAPSHOT';
+
     renderProjectActivityAppContainer(
       mockComponent({
         breadcrumbs: [
@@ -160,6 +168,7 @@ describe('CRUD', () => {
         configuration: { showHistory: true },
       })
     );
+
     await ui.appLoaded();
 
     await ui.addVersionEvent('1.1.0.1', initialValue);
@@ -178,6 +187,7 @@ describe('CRUD', () => {
     const { ui } = getPageObject();
     const initialValue = 'Custom event name';
     const updatedValue = 'Custom event updated name';
+
     renderProjectActivityAppContainer(
       mockComponent({
         breadcrumbs: [
@@ -186,6 +196,7 @@ describe('CRUD', () => {
         configuration: { showHistory: true },
       })
     );
+
     await ui.appLoaded();
 
     await act(async () => {
@@ -204,6 +215,7 @@ describe('CRUD', () => {
 
   it('should correctly allow deletion of specific analyses', async () => {
     const { ui } = getPageObject();
+
     renderProjectActivityAppContainer(
       mockComponent({
         breadcrumbs: [
@@ -212,6 +224,7 @@ describe('CRUD', () => {
         configuration: { showHistory: true },
       })
     );
+
     await ui.appLoaded();
 
     // Most recent analysis is not deletable.
@@ -231,6 +244,7 @@ describe('data loading', () => {
 
   it('should load all analyses', async () => {
     const count = 1000;
+
     projectActivityHandler.setAnalysesList(
       times(count, (i) => {
         return mockAnalysis({
@@ -239,6 +253,7 @@ describe('data loading', () => {
         });
       })
     );
+
     const { ui } = getPageObject();
     renderProjectActivityAppContainer();
     await ui.appLoaded();
@@ -257,6 +272,7 @@ describe('data loading', () => {
 
   it('should correctly fetch the top level component when dealing with sub portfolios', async () => {
     const { ui } = getPageObject();
+
     renderProjectActivityAppContainer(
       mockComponent({
         key: 'unknown',
@@ -267,6 +283,7 @@ describe('data loading', () => {
         ],
       })
     );
+
     await ui.appLoaded();
 
     // If it didn't fail, it means we correctly queried for project "foo".
@@ -319,6 +336,7 @@ describe('filtering', () => {
         });
       })
     );
+
     const { ui } = getPageObject();
     renderProjectActivityAppContainer();
     await ui.appLoaded();
@@ -347,9 +365,11 @@ describe('graph interactions', () => {
     await ui.appLoaded();
 
     expect(ui.bugsPopupCell.query()).not.toBeInTheDocument();
+
     await act(async () => {
       await ui.showDetails('1.1.0.1');
     });
+
     expect(ui.bugsPopupCell.get()).toBeInTheDocument();
   });
 
@@ -386,6 +406,7 @@ describe('graph interactions', () => {
 
 function getPageObject() {
   const user = userEvent.setup();
+
   const ui = {
     // Graph types.
     graphTypeSelect: byLabelText('project_activity.graphs.choose_type'),
@@ -426,7 +447,7 @@ function getPageObject() {
     // Misc.
     loading: byLabelText('loading'),
     baseline: byText('project_activity.new_code_period_start'),
-    bugsPopupCell: byRole('cell', { name: 'bugs' }),
+    bugsPopupCell: byRole('cell', { name: MetricKey.bugs }),
   };
 
   return {
@@ -438,65 +459,80 @@ function getPageObject() {
           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());
       },
index f724d84c71b344c0cd3c2b6fbc81a8266c20ff93..eee04c2270d8790d7ab1b31e6b9d89878bde35f1 100644 (file)
  * 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 {
@@ -88,11 +89,27 @@ export default class GraphHistory extends React.PureComponent<Props, State> {
       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} />
         ) : (
@@ -104,7 +121,6 @@ export default class GraphHistory extends React.PureComponent<Props, State> {
             {({ height, width }) => (
               <div>
                 <AdvancedTimeline
-                  displayNewCodeLegend={true}
                   endDate={graphEndDate}
                   formatYTick={this.formatValue}
                   height={height}
@@ -140,17 +156,7 @@ export default class GraphHistory extends React.PureComponent<Props, State> {
           </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')}
index 7065b9c17b04e190e5ce2986e50dd2c4656666e9..971dbd53157daa721e3d9d2233fae1b514829dfe 100644 (file)
  * 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';
@@ -48,30 +57,66 @@ export default class GraphsHeader extends React.PureComponent<Props> {
   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 &&
index 1558d997ad4884e80306325bd702b007615760d4..be10615dd13bdc0a494baec66beeee84faa1a210 100644 (file)
  * 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 {
@@ -31,10 +33,12 @@ 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}
@@ -44,6 +48,7 @@ export default function GraphsLegendCustom(props: GraphsLegendCustomProps) {
             showWarning={!hasData}
           />
         );
+
         if (!hasData) {
           return (
             <Tooltip
@@ -59,12 +64,19 @@ export default function GraphsLegendCustom(props: GraphsLegendCustomProps) {
             </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>
   );
 }
index c6a5509118bc15c85543996b433e836d89ddf18d..e216dbf51ea356680251350b62071ffe6fd2c2e2 100644 (file)
  * 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 {
@@ -33,38 +36,41 @@ 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>
+  );
 }
index bf1547cd1425491407157680ca68200eb5cdee20..9f62fbcb55baa866ea90027e05057c2df4dddb93 100644 (file)
  * 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'>>;
@@ -31,13 +34,19 @@ export default function GraphsLegendStatic({ series }: GraphsLegendStaticProps)
       {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>
   );
 }
index 732faf5f765d381cf609d62d22c38bfbfaf37a12..fc1b9a81ef1b2a60a5c829f2fbf21a5ac0c1d2bd 100644 (file)
@@ -17,6 +17,8 @@
  * 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';
@@ -29,7 +31,7 @@ import GraphsTooltipsContentEvents from './GraphsTooltipsContentEvents';
 import GraphsTooltipsContentIssues from './GraphsTooltipsContentIssues';
 import { DEFAULT_GRAPH } from './utils';
 
-interface Props {
+interface PropsWithoutTheme {
   events: AnalysisEvent[];
   formatValue: (tick: number | string) => string;
   graph: string;
@@ -41,16 +43,19 @@ interface Props {
   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;
       }
@@ -82,12 +87,21 @@ export default class GraphsTooltips extends React.PureComponent<Props> {
   }
 
   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;
@@ -103,10 +117,23 @@ export default class GraphsTooltips extends React.PureComponent<Props> {
         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} />
             )}
@@ -131,3 +158,5 @@ export default class GraphsTooltips extends React.PureComponent<Props> {
     );
   }
 }
+
+export const GraphsTooltips = withTheme<PropsWithoutTheme>(GraphsTooltipsClass);
index 56d224e4a555653aaa08e37572daadd53eafbee9..345c16d7bf6035ceb48e38522885b81360821e24 100644 (file)
@@ -17,8 +17,9 @@
  * 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;
index 3ed717f5cf3f542c68ae99b8ff1848265eda1c86..69def6015131f6c15e873672493a2eaa83cad6aa 100644 (file)
@@ -29,13 +29,6 @@ interface Props {
 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) => (
index 30f4c700f6ffcf0738fb905806334f29688c6dd4..094f49d4954572769cbf7770e1d5272cdd7a04ad 100644 (file)
  * 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';
@@ -42,20 +43,40 @@ const METRIC_RATING: Dict<string> = {
 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>
   );
 }
index 72213529e3b5f68acbe312b2fbfc065d9347d2b8..8f2bdfee1eb28181fb9f5c0663411db1fee677e4 100644 (file)
  * 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';
 
index fe3818fa1b1c0f86d3e1fb0b973d539473355314..6ac2c03d8c53e18161a7b58eac7a81803afba9af 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import { screen } from '@testing-library/react';
 import * as React from 'react';
 import { parseDate } from '../../../helpers/dates';
@@ -30,16 +31,16 @@ import { renderComponent } from '../../../helpers/testReactTestingUtils';
 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],
     ],
   ],
   [
@@ -64,14 +65,14 @@ it.each([
       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');
@@ -95,6 +96,7 @@ function renderGraphsTooltips(props: Partial<GraphsTooltips['props']> = {}) {
         history: [mockHistoryItem({ date, value })],
       })
     );
+
     metrics.push(
       mockMetric({
         key: metric,
index 8d17469eae02c6fbe1225a8182a06edd40e7d727..2ce69827b133b60ae97261562c3cc2be358af1bc 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 .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);
-}
index 2938a44f927dab22489d4abfc7e2e935fde560ee..a24dfad62f55dbd6beed378abd34bbec2c7f1df9 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import { chunk, flatMap, groupBy, sortBy } from 'lodash';
 import { getLocalizedMetricName, translate } from '../../helpers/l10n';
 import { localizeMetric } from '../../helpers/measures';
@@ -52,6 +53,7 @@ export function isCustomGraph(graph: GraphType) {
 
 export function getGraphTypes(ignoreCustom = false) {
   const graphs = [GraphType.issues, GraphType.coverage, GraphType.duplications];
+
   return ignoreCustom ? graphs : [...graphs, GraphType.custom];
 }
 
@@ -93,6 +95,7 @@ export function generateCoveredLinesMetric(
   const linesToCover = measuresHistory.find(
     (measure) => measure.metric === MetricKey.lines_to_cover
   );
+
   return {
     data: linesToCover
       ? uncoveredLines.history.map((analysis, idx) => ({
@@ -115,6 +118,7 @@ export function generateSeries(
   if (displayedMetrics.length <= 0 || measuresHistory === undefined) {
     return [];
   }
+
   return sortBy(
     measuresHistory
       .filter((measure) => displayedMetrics.indexOf(measure.metric) >= 0)
@@ -134,7 +138,9 @@ export function generateSeries(
         };
       }),
     (serie) =>
-      displayedMetrics.indexOf(serie.name === 'covered_lines' ? 'uncovered_lines' : serie.name)
+      displayedMetrics.indexOf(
+        serie.name === 'covered_lines' ? MetricKey.uncovered_lines : serie.name
+      )
   );
 }
 
@@ -145,6 +151,7 @@ export function saveActivityGraph(
   metrics: string[] = []
 ) {
   save(namespace, graph, project);
+
   if (isCustomGraph(graph)) {
     save(`${namespace}.custom`, metrics.join(','), project);
   }
@@ -155,6 +162,7 @@ export function getActivityGraph(
   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(',') : [],
@@ -168,6 +176,7 @@ export function getAnalysisEventsForDate(analyses: ParsedAnalysis[], date?: Date
       return analysis.events;
     }
   }
+
   return [];
 }
 
index 57b478198147da392c19aa288d71e7c39d6db030..0de903bd5b06223f3b3e6a2657f72d9702f7aec9 100644 (file)
  * 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;
@@ -50,9 +51,9 @@ export interface Props {
   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;
@@ -60,18 +61,20 @@ export interface Props {
   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;
@@ -81,16 +84,14 @@ interface State {
   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 };
@@ -98,9 +99,10 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
     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 ||
@@ -110,7 +112,9 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
       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);
       }
@@ -118,18 +122,14 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
 
     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,
@@ -147,12 +147,17 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
     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])
@@ -163,24 +168,31 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
     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),
@@ -189,6 +201,7 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
 
   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());
@@ -201,24 +214,23 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
         };
       }
     }
-    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;
@@ -243,6 +255,7 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
 
   handleMouseOut = () => {
     const { updateTooltip } = this.props;
+
     if (updateTooltip) {
       this.setState({
         mouseOver: false,
@@ -250,34 +263,33 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
         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 &&
@@ -285,26 +297,28 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
           ) {
             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]);
@@ -323,11 +337,12 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
       <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"
@@ -356,13 +371,15 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
     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"
@@ -378,62 +395,13 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
     );
   };
 
-  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();
@@ -443,26 +411,25 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
     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>()
@@ -473,13 +440,17 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
     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>
@@ -489,6 +460,7 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
   renderDots = () => {
     const { series } = this.props;
     const { xScale, yScale } = this.state;
+
     return (
       <g>
         {series
@@ -496,13 +468,17 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
             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}`)}
@@ -533,6 +509,7 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
     if (basisCurve) {
       areaGenerator.curve(curveBasis);
     }
+
     return (
       <g>
         {series.map((serie, idx) => (
@@ -549,6 +526,7 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
   renderSelectedDate = () => {
     const { selectedDateIdx, selectedDateXPos, yScale } = this.state;
     const firstSerie = this.props.series[0];
+
     if (selectedDateIdx == null || selectedDateXPos == null || !firstSerie) {
       return null;
     }
@@ -564,9 +542,11 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
         />
         {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}`)}
@@ -601,17 +581,21 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
     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"
@@ -635,12 +619,15 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
       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}
@@ -663,3 +650,5 @@ export default class AdvancedTimeline extends React.PureComponent<Props, State>
     );
   }
 }
+
+export const AdvancedTimeline = withTheme<PropsWithoutTheme>(AdvancedTimelineClass);
index 4555d1215588acf74a6285b4827302acbafa0e94..7536054b3d0cc237475e96c90cdff85c95cfb04c 100644 (file)
  * 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;
 }
 
@@ -58,7 +56,6 @@
 
 .line-chart-tick {
   fill: var(--secondFontColor);
-  font-size: var(--smallFontSize);
 }
 
 .line-chart-tick-x {
index 645793f5f911582611c8a615e81eb12f3463a4bf..f15adfdaa8491797c345b4a95125817b7e81939f 100644 (file)
  * 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';
@@ -59,6 +60,7 @@ export default class LineChart extends React.PureComponent<Props> {
       .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]);
@@ -79,9 +81,11 @@ export default class LineChart extends React.PureComponent<Props> {
       .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>;
   }
 
@@ -96,9 +100,11 @@ export default class LineChart extends React.PureComponent<Props> {
       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>;
   }
 
@@ -113,13 +119,15 @@ export default class LineChart extends React.PureComponent<Props> {
       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>;
   }
 
@@ -134,13 +142,15 @@ export default class LineChart extends React.PureComponent<Props> {
       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>;
   }
 
@@ -150,6 +160,7 @@ export default class LineChart extends React.PureComponent<Props> {
       .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} />;
   }
 
@@ -166,6 +177,7 @@ export default class LineChart extends React.PureComponent<Props> {
     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) {
index 0a9f013a129270da000b0c29c3a4886748a3a507..4adbf91952b438a4e1345e71825305cefca9cd32 100644 (file)
  * 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);
   }
@@ -76,12 +77,13 @@ export default class ZoomTimeLine extends React.PureComponent<Props, State> {
     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])
@@ -96,20 +98,18 @@ export default class ZoomTimeLine extends React.PureComponent<Props, State> {
   };
 
   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);
   };
@@ -134,6 +134,7 @@ export default class ZoomTimeLine extends React.PureComponent<Props, State> {
 
   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]))),
@@ -142,6 +143,7 @@ export default class ZoomTimeLine extends React.PureComponent<Props, State> {
 
   handleNewZoomDrag = (xScale: XScale, xDim: number[]) => (_: MouseEvent, data: DraggableData) => {
     const { newZoomStart, overlayLeftPos } = this.state;
+
     if (newZoomStart != null && overlayLeftPos != null && data.deltaX) {
       this.handleZoomUpdate(
         xScale,
@@ -153,6 +155,7 @@ export default class ZoomTimeLine extends React.PureComponent<Props, State> {
   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]));
@@ -162,18 +165,21 @@ export default class ZoomTimeLine extends React.PureComponent<Props, State> {
 
   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"
@@ -185,15 +191,17 @@ export default class ZoomTimeLine extends React.PureComponent<Props, State> {
     );
   };
 
-  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}>
@@ -205,15 +213,18 @@ export default class ZoomTimeLine extends React.PureComponent<Props, State> {
     );
   };
 
-  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)}
@@ -222,36 +233,45 @@ export default class ZoomTimeLine extends React.PureComponent<Props, State> {
     );
   };
 
-  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) => (
@@ -301,7 +321,7 @@ export default class ZoomTimeLine extends React.PureComponent<Props, State> {
     </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]];
@@ -310,6 +330,7 @@ export default class ZoomTimeLine extends React.PureComponent<Props, State> {
     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 ||
@@ -371,6 +392,8 @@ export default class ZoomTimeLine extends React.PureComponent<Props, State> {
   };
 
   render() {
+    const { padding, showXTicks = true } = this.props as PropsWithDefaults;
+
     if (!this.props.width || !this.props.height) {
       return <div />;
     }
@@ -379,15 +402,18 @@ export default class ZoomTimeLine extends React.PureComponent<Props, State> {
 
     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);
index ddebf5c02c82d61951c2d27ad6d6e37c008e164f..4a2be3cf36fd9bd366601fd3c035ad36a6588c80 100644 (file)
  * 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', () => {
@@ -36,215 +35,36 @@ 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}
@@ -256,11 +76,11 @@ function shallowRender(props?: Partial<AdvancedTimeline['props']>) {
           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,
             },
           ],
@@ -271,7 +91,7 @@ function shallowRender(props?: Partial<AdvancedTimeline['props']>) {
           translatedName: '',
           data: [
             {
-              x: new Date('2019-10-03'),
+              x: new Date('2019-10-03T00:00:00.000Z'),
               y: 3,
             },
           ],
@@ -283,12 +103,3 @@ function shallowRender(props?: Partial<AdvancedTimeline['props']>) {
     />
   );
 }
-
-function mockData(i: number, date: string): Chart.Serie {
-  return {
-    name: `t${i}`,
-    type: 'type',
-    translatedName: '',
-    data: [{ x: new Date(date), y: i }],
-  };
-}
diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/ZoomTimeLine-test.tsx b/server/sonar-web/src/main/js/components/charts/__tests__/ZoomTimeLine-test.tsx
deleted file mode 100644 (file)
index a8da5a0..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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}
-    />
-  );
-}
index a9cbb85434271f6ea6da7e1bbfbf5dfd5bc875ea..86b31ae17be41603aefa6005737c364e55af1856 100644 (file)
 // 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>
@@ -191,311 +170,519 @@ exports[`should render correctly 1`] = `
       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>
@@ -503,112 +690,102 @@ exports[`should render correctly: Zoom enabled 1`] = `
       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>
@@ -616,348 +793,316 @@ exports[`should render correctly: Zoom enabled 1`] = `
 
 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>
@@ -965,300 +1110,267 @@ exports[`should render correctly: format y tick 1`] = `
       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>
@@ -1266,327 +1378,826 @@ exports[`should render correctly: no areas 1`] = `
       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>
@@ -1594,111 +2205,383 @@ exports[`should render leak legend correctly 1`] = `
       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>
diff --git a/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/ZoomTimeLine-test.tsx.snap b/server/sonar-web/src/main/js/components/charts/__tests__/__snapshots__/ZoomTimeLine-test.tsx.snap
deleted file mode 100644 (file)
index adb3c28..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly: no height 1`] = `<div />`;
-
-exports[`should render correctly: no width 1`] = `<div />`;
index 89a196809f0d9cb98db06efeddfd3c5c56b30c69..edc407a8ce80375d0115aec357d3ae548dc588d7 100644 (file)
  * 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';
 
@@ -26,12 +29,17 @@ interface Props {
   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>
   );
index 51b770458348747b707f1928ae1c616a0e3b7e76..cc3e44fa9213f7f67948d8358ab4664f3090d79a 100644 (file)
  * 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 {
@@ -42,7 +44,9 @@ export default function Rating({ className, muted = false, value, ...ariaAttrs }
       </span>
     );
   }
-  const formatted = formatMeasure(value, 'RATING');
+
+  const formatted = formatMeasure(value, MetricType.Rating);
+
   return (
     <span
       aria-label={translateWithParameters('metric.has_rating_X', formatted)}
index 4c2246745d3fe20ab9be79429b21def192cadf96..2670b330921d77b08a2e5de9577e1155fd5c328d 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 const path = require('path');
 const { fontFamily } = require('tailwindcss/defaultTheme');
 const utilities = require('./tailwind-utilities');
@@ -77,6 +78,7 @@ module.exports = {
       12: '3rem', // 48px
       16: '4rem', // 64px
       24: '6rem', // 96px
+      32: '8rem', // 128px
       40: '10rem', // 160px
       64: '16rem', // 256px
 
index 93fb50eb06eeebbe3f671652791cc7d811af179f..203a43cd8a8d664921d238c071ee2b42384c0019 100644 (file)
@@ -821,7 +821,7 @@ hotspot.filters.status.to_review=To review
 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
@@ -1609,7 +1609,6 @@ project_activity.events.tooltip.delete=Delete this event
 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