Browse Source

feat(ddm): seamless zoom (#57830)

Ogi 1 year ago
parent
commit
2fce28252e
3 changed files with 66 additions and 7 deletions
  1. 49 2
      static/app/utils/metrics.tsx
  2. 14 3
      static/app/views/ddm/chart.tsx
  3. 3 2
      static/app/views/ddm/widget.tsx

+ 49 - 2
static/app/utils/metrics.tsx

@@ -1,4 +1,4 @@
-import {useMemo} from 'react';
+import {useEffect, useMemo, useState} from 'react';
 import {InjectedRouter} from 'react-router';
 import moment from 'moment';
 
@@ -9,7 +9,7 @@ import {formatPercentage, getDuration} from 'sentry/utils/formatters';
 import {ApiQueryKey, useApiQuery} from 'sentry/utils/queryClient';
 import useOrganization from 'sentry/utils/useOrganization';
 
-import {PageFilters} from '../types/core';
+import {DateString, PageFilters} from '../types/core';
 
 // TODO(ddm): reuse from types/metrics.tsx
 type MetricMeta = {
@@ -170,6 +170,53 @@ export function useMetricsData({
   );
 }
 
+// Wraps useMetricsData and provides two additional features:
+// 1. return data is undefined only during the initial load
+// 2. provides a callback to trim the data to a specific time range when chart zoom is used
+export function useMetricsDataZoom(props: MetricsQuery) {
+  const [metricsData, setMetricsData] = useState<MetricsData | undefined>();
+  const {data: rawData, isLoading, isError, error} = useMetricsData(props);
+
+  useEffect(() => {
+    if (rawData) {
+      setMetricsData(rawData);
+    }
+  }, [rawData]);
+
+  const trimData = (start, end): MetricsData | undefined => {
+    if (!metricsData) {
+      return metricsData;
+    }
+    // find the index of the first interval that is greater than the start time
+    const startIndex = metricsData.intervals.findIndex(interval => interval >= start) - 1;
+    const endIndex = metricsData.intervals.findIndex(interval => interval >= end);
+
+    return {
+      ...metricsData,
+      intervals: metricsData.intervals.slice(startIndex, endIndex),
+      groups: metricsData.groups.map(group => ({
+        ...group,
+        series: Object.fromEntries(
+          Object.entries(group.series).map(([seriesName, series]) => [
+            seriesName,
+            series.slice(startIndex, endIndex),
+          ])
+        ),
+      })),
+    };
+  };
+
+  return {
+    data: metricsData,
+    isLoading,
+    isError,
+    error,
+    onZoom: (start: DateString, end: DateString) => {
+      setMetricsData(trimData(start, end));
+    },
+  };
+}
+
 function getDateTimeParams({start, end, period}: PageFilters['datetime']) {
   return period
     ? {statsPeriod: period}

+ 14 - 3
static/app/views/ddm/chart.tsx

@@ -10,7 +10,7 @@ import {LineChart} from 'sentry/components/charts/lineChart';
 import ReleaseSeries from 'sentry/components/charts/releaseSeries';
 import {RELEASE_LINES_THRESHOLD} from 'sentry/components/charts/utils';
 import {t} from 'sentry/locale';
-import {PageFilters} from 'sentry/types';
+import {DateString, PageFilters} from 'sentry/types';
 import {ReactEchartsRef} from 'sentry/types/echarts';
 import {
   formatMetricsUsingUnitAndOp,
@@ -31,6 +31,7 @@ type ChartProps = {
   projects: PageFilters['projects'];
   series: Series[];
   end?: string;
+  onZoom?: (start: DateString, end: DateString) => void;
   operation?: string;
   period?: string;
   start?: string;
@@ -47,6 +48,7 @@ export function MetricChart({
   operation,
   projects,
   environments,
+  onZoom,
 }: ChartProps) {
   const chartRef = useRef<ReactEchartsRef>(null);
   const router = useRouter();
@@ -111,7 +113,16 @@ export function MetricChart({
 
   return (
     <ChartWrapper>
-      <ChartZoom router={router} period={period} start={start} end={end} utc={utc}>
+      <ChartZoom
+        router={router}
+        period={period}
+        start={start}
+        end={end}
+        utc={utc}
+        onZoom={zoomPeriod => {
+          onZoom?.(zoomPeriod.start, zoomPeriod.end);
+        }}
+      >
         {zoomRenderProps => (
           <ReleaseSeries
             utc={utc}
@@ -153,7 +164,7 @@ export function MetricChart({
               ) : displayType === MetricDisplayType.AREA ? (
                 <AreaChart {...allProps} />
               ) : (
-                <BarChart stacked {...allProps} />
+                <BarChart stacked animation={false} {...allProps} />
               );
             }}
           </ReleaseSeries>

+ 3 - 2
static/app/views/ddm/widget.tsx

@@ -22,7 +22,7 @@ import {
   MetricsData,
   MetricsQuery,
   updateQuery,
-  useMetricsData,
+  useMetricsDataZoom,
 } from 'sentry/utils/metrics';
 import {decodeList} from 'sentry/utils/queryString';
 import theme from 'sentry/utils/theme';
@@ -170,7 +170,7 @@ function MetricWidgetBody({
 }: MetricWidgetProps & PageFilters) {
   const {mri, op, query, groupBy, projects, environments, datetime} = metricsQuery;
 
-  const {data, isLoading, isError, error} = useMetricsData({
+  const {data, isLoading, isError, error, onZoom} = useMetricsDataZoom({
     mri,
     op,
     query,
@@ -231,6 +231,7 @@ function MetricWidgetBody({
         projects={metricsQuery.projects}
         environments={metricsQuery.environments}
         {...normalizeChartTimeParams(dataToBeRendered)}
+        onZoom={onZoom}
       />
       {metricsQuery.showSummaryTable && (
         <SummaryTable