From 93ab6b0709b11efcd3911cac93106e481cac86a9 Mon Sep 17 00:00:00 2001 From: "sentry[bot]" <39604003+sentry[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 22:43:33 +0000 Subject: [PATCH 1/4] fix(widget-builder): Prevent crash when threshold max_values is undefined --- .../thresholdsStep/thresholds.spec.tsx | 21 +++++++++++++++++++ .../buildSteps/thresholdsStep/thresholds.tsx | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.spec.tsx b/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.spec.tsx index d6361aaf24a9..558e8718d486 100644 --- a/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.spec.tsx +++ b/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.spec.tsx @@ -11,6 +11,27 @@ const exampleThresholdsConfig: ThresholdsConfig = { }; describe('Widget Builder > ThresholdsStep', () => { + it('renders without crashing when max_values is undefined', async () => { + const onChange = jest.fn(); + const configWithoutMaxValues = { + max_values: undefined, + unit: null, + } as unknown as ThresholdsConfig; + render( + + ); + + // Component should render with empty max value inputs instead of throwing + expect(await screen.findByLabelText('First Minimum')).toBeInTheDocument(); + expect(screen.getByLabelText('First Maximum', {selector: 'input'})).toHaveValue(null); + expect(screen.getByLabelText('Second Maximum', {selector: 'input'})).toHaveValue(null); + }); + it('renders thresholds step', async () => { const onChange = jest.fn(); render( diff --git a/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.tsx b/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.tsx index e86071db52a6..3514ec3d7d7a 100644 --- a/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.tsx +++ b/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.tsx @@ -102,8 +102,8 @@ export function Thresholds({ onPolarityChange, }: ThresholdsStepProps) { const theme = useTheme(); - const maxOneValue = thresholdsConfig?.max_values[ThresholdMaxKeys.MAX_1] ?? ''; - const maxTwoValue = thresholdsConfig?.max_values[ThresholdMaxKeys.MAX_2] ?? ''; + const maxOneValue = thresholdsConfig?.max_values?.[ThresholdMaxKeys.MAX_1] ?? ''; + const maxTwoValue = thresholdsConfig?.max_values?.[ThresholdMaxKeys.MAX_2] ?? ''; const unit = thresholdsConfig?.unit ?? dataUnit; const unitOptions = getThresholdUnitSelectOptions(dataType); From 61ac9b7bae5c9e2b95135d9cedb385391f523e86 Mon Sep 17 00:00:00 2001 From: "getsantry[bot]" <66042841+getsantry[bot]@users.noreply.github.com> Date: Thu, 17 Sep 2026 22:44:19 +0000 Subject: [PATCH 2/4] :hammer_and_wrench: apply pre-commit fixes --- .../buildSteps/thresholdsStep/thresholds.spec.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.spec.tsx b/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.spec.tsx index 558e8718d486..9407b2865eff 100644 --- a/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.spec.tsx +++ b/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.spec.tsx @@ -29,7 +29,9 @@ describe('Widget Builder > ThresholdsStep', () => { // Component should render with empty max value inputs instead of throwing expect(await screen.findByLabelText('First Minimum')).toBeInTheDocument(); expect(screen.getByLabelText('First Maximum', {selector: 'input'})).toHaveValue(null); - expect(screen.getByLabelText('Second Maximum', {selector: 'input'})).toHaveValue(null); + expect(screen.getByLabelText('Second Maximum', {selector: 'input'})).toHaveValue( + null + ); }); it('renders thresholds step', async () => { From 5681dcf01de607f2a0759e752b30440f1441f3dd Mon Sep 17 00:00:00 2001 From: TkDodo Date: Fri, 18 Sep 2026 16:05:05 +0200 Subject: [PATCH 3/4] fix: widen type of thresholdsConfig to have values be potentially undefined to force checks everywhere --- static/app/chartcuterie/dashboardsWidget.tsx | 2 +- .../buildSteps/thresholdsStep/thresholds.tsx | 2 +- .../thresholdsStep/thresholdsHoverWrapper.tsx | 5 +---- .../widgetBuilder/components/thresholds.tsx | 4 ++-- .../dashboards/widgetCard/visualizationWidget.tsx | 4 ++-- .../bigNumberWidgetVisualization.tsx | 4 ++-- .../timeSeriesWidget/plottables/thresholds.tsx | 14 +++++++------- 7 files changed, 16 insertions(+), 19 deletions(-) diff --git a/static/app/chartcuterie/dashboardsWidget.tsx b/static/app/chartcuterie/dashboardsWidget.tsx index d1d6e73643ae..960919882726 100644 --- a/static/app/chartcuterie/dashboardsWidget.tsx +++ b/static/app/chartcuterie/dashboardsWidget.tsx @@ -38,7 +38,7 @@ export const makeDashboardsWidgetCharts = ( const {thresholds} = data.widget; if ( thresholds && - (defined(thresholds.max_values.max1) || defined(thresholds.max_values.max2)) + (defined(thresholds.max_values?.max1) || defined(thresholds.max_values?.max2)) ) { extraPlottables.push( new Thresholds({ diff --git a/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.tsx b/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.tsx index 3514ec3d7d7a..9b720fd42826 100644 --- a/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.tsx +++ b/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds.tsx @@ -50,7 +50,7 @@ enum ThresholdMaxKeys { type ThresholdMaxValues = Partial>; export type ThresholdsConfig = { - max_values: ThresholdMaxValues; + max_values: ThresholdMaxValues | undefined; unit: string | null; preferredPolarity?: Polarity; }; diff --git a/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholdsHoverWrapper.tsx b/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholdsHoverWrapper.tsx index d18682d81a27..7b1e904b405c 100644 --- a/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholdsHoverWrapper.tsx +++ b/static/app/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholdsHoverWrapper.tsx @@ -21,10 +21,7 @@ type Props = { }; export function ThresholdsHoverWrapper({children, thresholds, type}: Props) { - const { - unit, - max_values: {max1, max2}, - } = thresholds; + const {unit, max_values: {max1, max2} = {}} = thresholds; const theme = useTheme(); const formattedUnit = unit && (type === 'duration' ? `${unit}s` : `/${unit.split('/')[1]}`); diff --git a/static/app/views/dashboards/widgetBuilder/components/thresholds.tsx b/static/app/views/dashboards/widgetBuilder/components/thresholds.tsx index 9b7ad166b3a5..8780a19e4220 100644 --- a/static/app/views/dashboards/widgetBuilder/components/thresholds.tsx +++ b/static/app/views/dashboards/widgetBuilder/components/thresholds.tsx @@ -82,7 +82,7 @@ export function ThresholdsSection({ newThresholds = {max_values: {}, unit: null}; } - if (newThresholds) { + if (newThresholds?.max_values) { if (value) { newThresholds.max_values[maxKey] = Number(value); } else { @@ -92,7 +92,7 @@ export function ThresholdsSection({ // Check if the value cleared all of the max values if ( - newThresholds && + newThresholds?.max_values && Object.values(newThresholds.max_values).every( nextMaxValue => !defined(nextMaxValue) ) diff --git a/static/app/views/dashboards/widgetCard/visualizationWidget.tsx b/static/app/views/dashboards/widgetCard/visualizationWidget.tsx index 3719997e8431..23ed21780293 100644 --- a/static/app/views/dashboards/widgetCard/visualizationWidget.tsx +++ b/static/app/views/dashboards/widgetCard/visualizationWidget.tsx @@ -404,8 +404,8 @@ function VisualizationWidgetContent({ ); if ( - defined(widget.thresholds?.max_values.max1) || - defined(widget.thresholds?.max_values.max2) + defined(widget.thresholds?.max_values?.max1) || + defined(widget.thresholds?.max_values?.max2) ) { plottables.push( new Thresholds({ diff --git a/static/app/views/dashboards/widgets/bigNumberWidget/bigNumberWidgetVisualization.tsx b/static/app/views/dashboards/widgets/bigNumberWidget/bigNumberWidgetVisualization.tsx index 3352172a18dc..bd567eaf11fc 100644 --- a/static/app/views/dashboards/widgets/bigNumberWidget/bigNumberWidgetVisualization.tsx +++ b/static/app/views/dashboards/widgets/bigNumberWidget/bigNumberWidgetVisualization.tsx @@ -105,14 +105,14 @@ function BigNumberWidgetVisualizationInner(props: BigNumberWidgetVisualizationPr return ( - {defined(props.thresholds?.max_values.max1) && + {defined(props.thresholds?.max_values?.max1) && defined(props.thresholds?.max_values.max2) && ( Date: Fri, 18 Sep 2026 16:22:46 +0200 Subject: [PATCH 4/4] fix: optional chaining check --- .../views/dashboards/widgetBuilder/components/thresholds.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/app/views/dashboards/widgetBuilder/components/thresholds.tsx b/static/app/views/dashboards/widgetBuilder/components/thresholds.tsx index 8780a19e4220..b61baf0297fd 100644 --- a/static/app/views/dashboards/widgetBuilder/components/thresholds.tsx +++ b/static/app/views/dashboards/widgetBuilder/components/thresholds.tsx @@ -82,7 +82,8 @@ export function ThresholdsSection({ newThresholds = {max_values: {}, unit: null}; } - if (newThresholds?.max_values) { + if (newThresholds) { + newThresholds.max_values ??= {}; if (value) { newThresholds.max_values[maxKey] = Number(value); } else {