fix: allow any numeric value for alert thresholds (#1869)

## Summary
- Removes restrictive numeric constraints from alert threshold Zod schemas across all packages (common-utils, api, app), allowing any number (negative, zero, float) as a valid threshold
- Removes `min` constraints from threshold `NumberInput` components in the saved search alert form and dashboard tile alert form
- Updates the enhanced errors test to reflect that negative thresholds are no longer a validation error

Closes HDX-3186

## Test plan
- [x] `yarn ci:unit` passes in `packages/app` (1031 tests passing)
- [x] `yarn ci:unit` passes in `packages/common-utils` (624 passing, 8 pre-existing failures in queryParser unrelated to this change)
- [ ] Verify saved search alerts accept threshold values of 0, negative numbers, and floats
- [ ] Verify dashboard tile alerts accept threshold values of 0, negative numbers, and floats
- [ ] Verify external API v2 alerts accept threshold values of 0, negative numbers, and floats


Made with [Cursor](https://cursor.com)
This commit is contained in:
Dan Hable 2026-03-09 18:28:16 -05:00 committed by GitHub
parent 84b0315f73
commit 1bae972e95
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 13 additions and 10 deletions

View file

@ -0,0 +1,7 @@
---
"@hyperdx/common-utils": patch
"@hyperdx/api": patch
"@hyperdx/app": patch
---
fix: allow any numeric value for alert thresholds

View file

@ -155,7 +155,7 @@ describe('enhancedErrors', () => {
source: 'tile',
tileId: '507f1f77bcf86cd799439011',
dashboardId: '507f1f77bcf86cd799439011',
threshold: -5, // Invalid: negative threshold
threshold: -5,
interval: '99m', // Invalid: not a valid interval
thresholdType: 'above',
channel: {
@ -166,7 +166,7 @@ describe('enhancedErrors', () => {
expect(response.status).toBe(400);
expect(response.body.message).toEqual(
"Body validation failed: interval: Invalid enum value. Expected '1m' | '5m' | '15m' | '30m' | '1h' | '6h' | '12h' | '1d', received '99m'; threshold: Number must be greater than or equal to 0; Params validation failed: id: Invalid input",
"Body validation failed: interval: Invalid enum value. Expected '1m' | '5m' | '15m' | '30m' | '1h' | '6h' | '12h' | '1d', received '99m'; Params validation failed: id: Invalid input",
);
});
});

View file

@ -412,7 +412,7 @@ export const alertSchema = z
interval: z.enum(['1m', '5m', '15m', '30m', '1h', '6h', '12h', '1d']),
scheduleOffsetMinutes: z.number().int().min(0).max(1439).optional(),
scheduleStartAt: scheduleStartAtSchema,
threshold: z.number().min(0),
threshold: z.number(),
thresholdType: z.nativeEnum(AlertThresholdType),
source: z.nativeEnum(AlertSource).default(AlertSource.SAVED_SEARCH),
name: z.string().min(1).max(512).nullish(),

View file

@ -62,7 +62,7 @@ import { optionsToSelectData } from './utils';
const SavedSearchAlertFormSchema = z
.object({
interval: AlertIntervalSchema,
threshold: z.number().int().min(1),
threshold: z.number(),
scheduleOffsetMinutes: z.number().int().min(0).default(0),
scheduleStartAt: scheduleStartAtSchema,
thresholdType: z.nativeEnum(AlertThresholdType),
@ -170,7 +170,6 @@ const AlertForm = ({
control={control}
/>
<NumberInput
min={1}
size="xs"
w={80}
control={control}

View file

@ -163,8 +163,6 @@ const isQueryReady = (queriedConfig: ChartConfigWithDateRange | undefined) => {
);
};
const MINIMUM_THRESHOLD_VALUE = 0.0000000001; // to make alert input > 0
type SeriesItem = NonNullable<
SavedChartConfigWithSelectArray['select']
>[number];
@ -1381,7 +1379,6 @@ export default function EditTimeChartForm({
control={control}
/>
<NumberInput
min={MINIMUM_THRESHOLD_VALUE}
size="xs"
w={80}
control={control}

View file

@ -396,7 +396,7 @@ export const AlertBaseObjectSchema = z.object({
.max(MAX_SCHEDULE_OFFSET_MINUTES)
.optional(),
scheduleStartAt: scheduleStartAtSchema,
threshold: z.number().int().min(1),
threshold: z.number(),
thresholdType: z.nativeEnum(AlertThresholdType),
channel: zAlertChannel,
state: z.nativeEnum(AlertState).optional(),
@ -420,7 +420,7 @@ const AlertBaseValidatedSchema = AlertBaseObjectSchema.superRefine(
);
export const ChartAlertBaseSchema = AlertBaseObjectSchema.extend({
threshold: z.number().positive(),
threshold: z.number(),
});
const ChartAlertBaseValidatedSchema = ChartAlertBaseSchema.superRefine(