From 7c914af56ad6036387ef0e31a93cf0aec5f45451 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Tue, 24 Jun 2025 13:51:20 +0200 Subject: [PATCH] Alerting: Prevent incorrect prefill params to crash the rule editor (#106395) also introduces "zod" as dependency --- package.json | 3 +- .../alert-rule-form/AlertRuleForm.tsx | 4 +- .../unified/rule-editor/formDefaults.ts | 85 ++++++++++++++++++- yarn.lock | 8 ++ 4 files changed, 95 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index bb861f07a9d..0cf6fc87371 100644 --- a/package.json +++ b/package.json @@ -259,7 +259,8 @@ "webpack-subresource-integrity": "^5.2.0-rc.1", "webpackbar": "^7.0.0", "yaml": "^2.0.0", - "yargs": "^17.5.1" + "yargs": "^17.5.1", + "zod": "^3.25.55" }, "dependencies": { "@bsull/augurs": "^0.10.0", diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx index 79bfacfc04c..0a16d0022a0 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx @@ -106,9 +106,7 @@ export const AlertRuleForm = ({ existing, prefill, isManualRestore }: Props) => return formValuesFromPrefill(prefill); } - const defaultRuleType = ruleType || RuleFormType.grafana; - - return defaultFormValuesForRuleType(defaultRuleType); + return defaultFormValuesForRuleType(ruleType); }, [existing, prefill, ruleType]); const formAPI = useForm({ diff --git a/public/app/features/alerting/unified/rule-editor/formDefaults.ts b/public/app/features/alerting/unified/rule-editor/formDefaults.ts index 1dab9a35fa6..cc271460651 100644 --- a/public/app/features/alerting/unified/rule-editor/formDefaults.ts +++ b/public/app/features/alerting/unified/rule-editor/formDefaults.ts @@ -1,4 +1,5 @@ import { clamp } from 'lodash'; +import { z } from 'zod/v4'; import { config } from '@grafana/runtime'; import { RuleWithLocation } from 'app/types/unified-alerting'; @@ -136,9 +137,12 @@ export function formValuesFromQueryParams(ruleDefinition: string, type: RuleForm } export function formValuesFromPrefill(rule: Partial): RuleFormValues { + // coerce prefill params to a valid RuleFormValues interface + const parsedRule = ruleFormValuesSchema.parse(rule); + return revealHiddenQueries({ ...getDefaultFormValues(rule.type), - ...rule, + ...parsedRule, }); } @@ -169,3 +173,82 @@ export function translateRouteParamToRuleType(param = ''): RuleFormType { return RuleFormType.grafana; } + +// we use this schema to coerce prefilled query params into a valid "FormValues" interface +const ruleFormValuesSchema = z.looseObject({ + name: z.string().optional(), + type: z.enum(RuleFormType).catch(RuleFormType.grafana), + dataSourceName: z.string().optional().default(''), + group: z.string().optional(), + labels: z + .array( + z.object({ + key: z.string(), + value: z.string(), + }) + ) + .optional() + .default([]), + annotations: z + .array( + z.object({ + key: z.string(), + value: z.string(), + }) + ) + .optional() + .default([]), + queries: z.array(z.any()).optional(), + condition: z.string().optional(), + noDataState: z + .enum(GrafanaAlertStateDecision) + .optional() + .default(GrafanaAlertStateDecision.NoData) + .catch(GrafanaAlertStateDecision.NoData), + execErrState: z + .enum(GrafanaAlertStateDecision) + .optional() + .default(GrafanaAlertStateDecision.Error) + .catch(GrafanaAlertStateDecision.Error), + folder: z + .union([ + z.object({ + title: z.string(), + uid: z.string(), + }), + z.undefined(), + ]) + .optional(), + evaluateEvery: z.string().optional(), + evaluateFor: z.string().optional().default('0s'), + keepFiringFor: z.string().optional(), + isPaused: z.boolean().optional().default(false), + manualRouting: z.boolean().optional(), + contactPoints: z + .record( + z.string(), + z.object({ + selectedContactPoint: z.string(), + overrideGrouping: z.boolean(), + groupBy: z.array(z.string()), + overrideTimings: z.boolean(), + groupWaitValue: z.string(), + groupIntervalValue: z.string(), + repeatIntervalValue: z.string(), + muteTimeIntervals: z.array(z.string()), + activeTimeIntervals: z.array(z.string()), + }) + ) + .optional(), + editorSettings: z + .object({ + simplifiedQueryEditor: z.boolean(), + simplifiedNotificationEditor: z.boolean(), + }) + .optional(), + metric: z.string().optional(), + targetDatasourceUid: z.string().optional(), + namespace: z.string().optional(), + expression: z.string().optional(), + missingSeriesEvalsToResolve: z.number().optional(), +}); diff --git a/yarn.lock b/yarn.lock index 6b346ddb7b0..c5fa1586bbe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18450,6 +18450,7 @@ __metadata: whatwg-fetch: "npm:3.6.20" yaml: "npm:^2.0.0" yargs: "npm:^17.5.1" + zod: "npm:^3.25.55" dependenciesMeta: prettier@3.4.2: unplugged: true @@ -32978,3 +32979,10 @@ __metadata: checksum: 10/0e35432dcca7f053e63f5dd491a87c78abe0d981817547252c3b6d05f0f58788695d1a69724759c6501dff3fd62929be24c9f314a3625179bee889150f7a61fa languageName: node linkType: hard + +"zod@npm:^3.25.55": + version: 3.25.55 + resolution: "zod@npm:3.25.55" + checksum: 10/1f86d370730fc1eed10fe584079bfebd0008303722f2da21057d493e784b42f0b1edbac028b5a788e2e04bd31da070eac8e8d5f237ec968bc2a6aa30985fa9d6 + languageName: node + linkType: hard