Browse Source

fix(ddm): focus area overflow (#62919)

Ogi 1 year ago
parent
commit
44cdb43ec8

+ 1 - 1
static/app/components/organizations/pageFilters/parse.tsx

@@ -30,7 +30,7 @@ export function parseStatsPeriod(input: string | IntervalPeriod) {
   const period = result[1];
 
   // default to seconds. this behaviour is based on src/sentry/utils/dates.py
-  const periodLength = result[2] || 's';
+  const periodLength = (result[2] || 's') as StatsPeriodType;
 
   return {period, periodLength};
 }

+ 37 - 0
static/app/utils/metrics/index.spec.tsx

@@ -3,6 +3,7 @@ import {
   formatMetricsUsingUnitAndOp,
   formatMetricUsingFixedUnit,
   formattingSupportedMetricUnits,
+  getAbsoluteDateTimeRange,
   getDateTimeParams,
   getDDMInterval,
   getMetricsApiRequestQuery,
@@ -220,3 +221,39 @@ describe('stringifyMetricWidget', () => {
     expect(result).toEqual('');
   });
 });
+
+describe('getAbsoluteDateTimeRange', () => {
+  beforeAll(() => {
+    jest.useFakeTimers();
+    jest.setSystemTime(new Date('2024-01-01T00:00:00Z'));
+  });
+
+  it('should return the correct object with "start" and "end" when period is not provided', () => {
+    const datetime = {
+      start: '2023-01-01T00:00:00.000Z',
+      end: '2023-01-01T00:00:00.000Z',
+      period: null,
+      utc: true,
+    };
+    const result = getAbsoluteDateTimeRange(datetime);
+
+    expect(result).toEqual({
+      start: '2023-01-01T00:00:00.000Z',
+      end: '2023-01-01T00:00:00.000Z',
+    });
+  });
+
+  it('should return the correct object with "start" and "end" when period is provided', () => {
+    const datetime = {start: null, end: null, period: '7d', utc: true};
+    const result = getAbsoluteDateTimeRange(datetime);
+
+    expect(result).toEqual({
+      start: '2023-12-25T00:00:00.000Z',
+      end: '2024-01-01T00:00:00.000Z',
+    });
+  });
+
+  afterAll(() => {
+    jest.useRealTimers();
+  });
+});

+ 31 - 0
static/app/utils/metrics/index.tsx

@@ -50,6 +50,10 @@ import {
 } from 'sentry/utils/metrics/mri';
 import useRouter from 'sentry/utils/useRouter';
 
+import {
+  normalizeDateTimeParams,
+  parseStatsPeriod,
+} from '../../components/organizations/pageFilters/parse';
 import {DateString, PageFilters} from '../../types/core';
 
 export const METRICS_DOCS_URL =
@@ -642,3 +646,30 @@ export function stringifyMetricWidget(metricWidget: MetricsQuerySubject): string
 
   return result;
 }
+
+// TODO: consider moving this to utils/dates.tsx
+export function getAbsoluteDateTimeRange(params: PageFilters['datetime']) {
+  const {start, end, statsPeriod, utc} = normalizeDateTimeParams(params, {
+    allowAbsoluteDatetime: true,
+  });
+
+  if (start && end) {
+    return {start: moment(start).toISOString(), end: moment(end).toISOString()};
+  }
+
+  const parsedStatusPeriod = parseStatsPeriod(statsPeriod || '24h');
+
+  const now = utc ? moment().utc() : moment();
+
+  if (!parsedStatusPeriod) {
+    // Default to 24h
+    return {start: moment(now).subtract(1, 'day').toISOString(), end: now.toISOString()};
+  }
+
+  const startObj = moment(now).subtract(
+    parsedStatusPeriod.period,
+    parsedStatusPeriod.periodLength
+  );
+
+  return {start: startObj.toISOString(), end: now.toISOString()};
+}

+ 6 - 2
static/app/views/ddm/chartBrush.tsx

@@ -209,10 +209,14 @@ function BrushRectOverlay({
       ? `${CHART_HEIGHT}px`
       : `${heightPx.toPrecision(5)}px`;
 
+    // Ensure the focus area rect is always within the chart bounds
+    const left = Math.max(topLeft[0], 0);
+    const width = Math.min(widthPx, chartInstance.getWidth() - left);
+
     setPosition({
-      left: `${topLeft[0].toPrecision(5)}px`,
+      left: `${left.toPrecision(5)}px`,
       top: resultTop,
-      width: `${widthPx.toPrecision(5)}px`,
+      width: `${width.toPrecision(5)}px`,
       height: resultHeight,
     });
   }, [rect, chartInstance, useFullYAxis]);

+ 13 - 1
static/app/views/ddm/context.tsx

@@ -11,6 +11,7 @@ import * as Sentry from '@sentry/react';
 import {MRI} from 'sentry/types';
 import {
   defaultMetricDisplayType,
+  getAbsoluteDateTimeRange,
   MetricDisplayType,
   MetricWidgetQueryParams,
   useInstantRef,
@@ -184,12 +185,23 @@ export function DDMContextProvider({children}: {children: React.ReactNode}) {
 
   const handleAddFocusArea = useCallback(
     (area: FocusArea) => {
+      const dateRange = getAbsoluteDateTimeRange(pageFilters.datetime);
+      if (!area.range.start || !area.range.end) {
+        Sentry.metrics.increment('ddm.enhance.range-undefined');
+        return;
+      }
+
+      if (area.range.start < dateRange.start || area.range.end > dateRange.end) {
+        Sentry.metrics.increment('ddm.enhance.range-overflow');
+        return;
+      }
+
       Sentry.metrics.increment('ddm.enhance.add');
       setFocusArea(area);
       setSelectedWidgetIndex(area.widgetIndex);
       updateQuery({focusArea: JSON.stringify(area)});
     },
-    [updateQuery]
+    [updateQuery, pageFilters.datetime]
   );
 
   const handleRemoveFocusArea = useCallback(() => {