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 {