Browse Source

feat(chartcuterie): Refactor Endpoint Chart to separate data (#66551)

I'm adding Chartcuterie support for Endpoint Regression chart. The first
step is to refactor it so we can generate the chart props in the
backend.

Example of chart that is being generate here:
![Screenshot 2024-03-07 at 2 33 18
PM](https://github.com/getsentry/sentry/assets/132939361/b443bf71-af20-4e1b-9f76-1aafc65d4e08)
Athena Moghaddam 1 year ago
parent
commit
66f24c1c8c

+ 25 - 0
static/app/chartcuterie/performance.tsx

@@ -0,0 +1,25 @@
+import getBreakpointChartOptionsFromData, {
+  type EventBreakpointChartData,
+} from 'sentry/components/events/eventStatisticalDetector/breakpointChartOptions';
+import {lightTheme as theme} from 'sentry/utils/theme';
+
+import {slackChartDefaults, slackChartSize} from './slack';
+import type {RenderDescriptor} from './types';
+import {ChartType} from './types';
+
+export const performanceCharts: RenderDescriptor<ChartType>[] = [];
+
+performanceCharts.push({
+  key: ChartType.SLACK_PERFORMANCE_ENDPOINT_REGRESSION,
+  getOption: (data: EventBreakpointChartData) => {
+    const {chartOptions, series} = getBreakpointChartOptionsFromData(data, theme);
+
+    return {
+      ...chartOptions,
+      backgroundColor: theme.background,
+      series: series,
+      grid: slackChartDefaults.grid,
+    };
+  },
+  ...slackChartSize,
+});

+ 1 - 0
static/app/chartcuterie/types.tsx

@@ -16,6 +16,7 @@ export enum ChartType {
   SLACK_DISCOVER_PREVIOUS_PERIOD = 'slack:discover.previousPeriod',
   SLACK_METRIC_ALERT_EVENTS = 'slack:metricAlert.events',
   SLACK_METRIC_ALERT_SESSIONS = 'slack:metricAlert.sessions',
+  SLACK_PERFORMANCE_ENDPOINT_REGRESSION = 'slack:performance.endpointRegression',
 }
 
 /**

+ 99 - 0
static/app/components/events/eventStatisticalDetector/breakpointChartOptions.tsx

@@ -0,0 +1,99 @@
+import type {Theme} from '@emotion/react';
+
+import VisualMap from 'sentry/components/charts/components/visualMap';
+import type {LineChart as EChartsLineChart} from 'sentry/components/charts/lineChart';
+import type {Series} from 'sentry/types/echarts';
+import {
+  axisLabelFormatter,
+  getDurationUnit,
+  tooltipFormatter,
+} from 'sentry/utils/discover/charts';
+import {aggregateOutputType} from 'sentry/utils/discover/fields';
+import type {NormalizedTrendsTransaction} from 'sentry/views/performance/trends/types';
+import {getIntervalLine} from 'sentry/views/performance/utils';
+
+export type EventBreakpointChartData = {
+  evidenceData: NormalizedTrendsTransaction;
+  percentileSeries: Series[];
+};
+
+function getBreakpointChartOptionsFromData(
+  {percentileSeries, evidenceData}: EventBreakpointChartData,
+  theme: Theme
+) {
+  const intervalSeries = getIntervalLine(
+    theme,
+    percentileSeries,
+    0.5,
+    true,
+    evidenceData,
+    true
+  );
+
+  const series = [...percentileSeries, ...intervalSeries];
+
+  const legend = {
+    right: 16,
+    top: 12,
+    data: percentileSeries.map(s => s.seriesName),
+  };
+
+  const durationUnit = getDurationUnit(series);
+
+  const chartOptions: Omit<React.ComponentProps<typeof EChartsLineChart>, 'series'> = {
+    axisPointer: {
+      link: [
+        {
+          xAxisIndex: [0, 1],
+          yAxisIndex: [0, 1],
+        },
+      ],
+    },
+    colors: [theme.gray200, theme.gray500],
+    grid: {
+      top: '40px',
+      bottom: '0px',
+    },
+    legend,
+    toolBox: {show: false},
+    tooltip: {
+      valueFormatter: (value, seriesName) => {
+        return tooltipFormatter(value, aggregateOutputType(seriesName));
+      },
+    },
+    xAxis: {type: 'time'},
+    yAxis: {
+      minInterval: durationUnit,
+      axisLabel: {
+        color: theme.chartLabel,
+        formatter: (value: number) =>
+          axisLabelFormatter(value, 'duration', undefined, durationUnit),
+      },
+    },
+    options: {
+      visualMap: VisualMap({
+        show: false,
+        type: 'piecewise',
+        selectedMode: false,
+        dimension: 0,
+        pieces: [
+          {
+            gte: 0,
+            lt: evidenceData?.breakpoint ? evidenceData.breakpoint * 1000 : 0,
+            color: theme.gray500,
+          },
+          {
+            gte: evidenceData?.breakpoint ? evidenceData.breakpoint * 1000 : 0,
+            color: theme.red300,
+          },
+        ],
+      }),
+    },
+  };
+  return {
+    series,
+    chartOptions,
+  };
+}
+
+export default getBreakpointChartOptionsFromData;

+ 3 - 82
static/app/components/events/eventStatisticalDetector/lineChart.tsx

@@ -2,19 +2,12 @@ import {useMemo} from 'react';
 import {useTheme} from '@emotion/react';
 
 import ChartZoom from 'sentry/components/charts/chartZoom';
-import VisualMap from 'sentry/components/charts/components/visualMap';
 import {LineChart as EChartsLineChart} from 'sentry/components/charts/lineChart';
+import getBreakpointChartOptionsFromData from 'sentry/components/events/eventStatisticalDetector/breakpointChartOptions';
 import type {PageFilters} from 'sentry/types';
 import type {Series} from 'sentry/types/echarts';
-import {
-  axisLabelFormatter,
-  getDurationUnit,
-  tooltipFormatter,
-} from 'sentry/utils/discover/charts';
-import {aggregateOutputType} from 'sentry/utils/discover/fields';
 import useRouter from 'sentry/utils/useRouter';
 import type {NormalizedTrendsTransaction} from 'sentry/views/performance/trends/types';
-import {getIntervalLine} from 'sentry/views/performance/utils';
 
 interface ChartProps {
   datetime: PageFilters['datetime'];
@@ -26,82 +19,10 @@ function LineChart({datetime, percentileSeries, evidenceData}: ChartProps) {
   const theme = useTheme();
   const router = useRouter();
 
-  const series = useMemo(() => {
-    const intervalSeries = getIntervalLine(
-      theme,
-      percentileSeries,
-      0.5,
-      true,
-      evidenceData,
-      true
-    );
-    return [...percentileSeries, ...intervalSeries];
+  const {series, chartOptions} = useMemo(() => {
+    return getBreakpointChartOptionsFromData({percentileSeries, evidenceData}, theme);
   }, [percentileSeries, evidenceData, theme]);
 
-  const chartOptions: Omit<
-    React.ComponentProps<typeof EChartsLineChart>,
-    'series'
-  > = useMemo(() => {
-    const legend = {
-      right: 16,
-      top: 12,
-      data: percentileSeries.map(s => s.seriesName),
-    };
-
-    const durationUnit = getDurationUnit(series);
-
-    return {
-      axisPointer: {
-        link: [
-          {
-            xAxisIndex: [0, 1],
-            yAxisIndex: [0, 1],
-          },
-        ],
-      },
-      colors: [theme.gray200, theme.gray500],
-      grid: {
-        top: '40px',
-        bottom: '0px',
-      },
-      legend,
-      toolBox: {show: false},
-      tooltip: {
-        valueFormatter: (value, seriesName) => {
-          return tooltipFormatter(value, aggregateOutputType(seriesName));
-        },
-      },
-      xAxis: {type: 'time'},
-      yAxis: {
-        minInterval: durationUnit,
-        axisLabel: {
-          color: theme.chartLabel,
-          formatter: (value: number) =>
-            axisLabelFormatter(value, 'duration', undefined, durationUnit),
-        },
-      },
-      options: {
-        visualMap: VisualMap({
-          show: false,
-          type: 'piecewise',
-          selectedMode: false,
-          dimension: 0,
-          pieces: [
-            {
-              gte: 0,
-              lt: evidenceData?.breakpoint ? evidenceData.breakpoint * 1000 : 0,
-              color: theme.gray500,
-            },
-            {
-              gte: evidenceData?.breakpoint ? evidenceData.breakpoint * 1000 : 0,
-              color: theme.red300,
-            },
-          ],
-        }),
-      },
-    };
-  }, [series, theme, percentileSeries, evidenceData.breakpoint]);
-
   return (
     <ChartZoom router={router} {...datetime}>
       {zoomRenderProps => (