From 0191ca4b13815e80c6cb718a9259ae0f25da432f Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:05:08 +0200 Subject: [PATCH] feat(ui/alerts): render promotion warnings in wizard banner Fetches target-env apps (useCatalog) and env-allowed outbound connections, passes them to prefillFromPromotion, and renders the returned warnings in an amber banner above the step nav. Warnings list the field name and the remediation message so users see crossings that need manual adjustment before saving. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Alerts/RuleEditor/RuleEditorWizard.tsx | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/ui/src/pages/Alerts/RuleEditor/RuleEditorWizard.tsx b/ui/src/pages/Alerts/RuleEditor/RuleEditorWizard.tsx index 250f5bdc..d33f916b 100644 --- a/ui/src/pages/Alerts/RuleEditor/RuleEditorWizard.tsx +++ b/ui/src/pages/Alerts/RuleEditor/RuleEditorWizard.tsx @@ -20,7 +20,10 @@ import { ConditionStep } from './ConditionStep'; import { TriggerStep } from './TriggerStep'; import { NotifyStep } from './NotifyStep'; import { ReviewStep } from './ReviewStep'; -import { prefillFromPromotion } from './promotion-prefill'; +import { prefillFromPromotion, type PrefillWarning } from './promotion-prefill'; +import { useCatalog } from '../../../api/queries/catalog'; +import { useOutboundConnections } from '../../../api/queries/admin/outboundConnections'; +import { useSelectedEnv } from '../../../api/queries/alertMeta'; import css from './wizard.module.css'; const STEP_LABELS: Record = { @@ -47,8 +50,20 @@ export default function RuleEditorWizard() { const promoteRuleId = search.get('ruleId') ?? undefined; const sourceRuleQuery = useAlertRule(promoteFrom ? promoteRuleId : undefined); + // Target-env data for promotion warnings. + const env = useSelectedEnv(); + const targetEnv = search.get('targetEnv') ?? env; + const { data: targetCatalog } = useCatalog(targetEnv ?? undefined); + const { data: connections } = useOutboundConnections(); + + const targetAppSlugs = (targetCatalog ?? []).map((a) => a.slug); + const targetAllowedConnIds = (connections ?? []) + .filter((c) => c.allowedEnvironmentIds.length === 0 || (!!targetEnv && c.allowedEnvironmentIds.includes(targetEnv))) + .map((c) => c.id); + const [step, setStep] = useState('scope'); const [form, setForm] = useState(null); + const [warnings, setWarnings] = useState([]); // Initialize form once the existing or source rule loads. useEffect(() => { @@ -58,14 +73,29 @@ export default function RuleEditorWizard() { return; } if (promoteFrom && sourceRuleQuery.data) { - const { form: prefilled } = prefillFromPromotion(sourceRuleQuery.data); + const { form: prefilled, warnings: w } = prefillFromPromotion(sourceRuleQuery.data, { + targetEnvAppSlugs: targetAppSlugs, + targetEnvAllowedConnectionIds: targetAllowedConnIds, + }); setForm(prefilled); + setWarnings(w); return; } if (!isEdit && !promoteFrom) { setForm(initialForm()); } - }, [form, isEdit, existingQuery.data, promoteFrom, sourceRuleQuery.data]); + // Intentionally depend on join()'d slug/id strings so the effect + // doesn't retrigger on new array identities when contents are equal. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + form, + isEdit, + existingQuery.data, + promoteFrom, + sourceRuleQuery.data, + targetAppSlugs.join(','), + targetAllowedConnIds.join(','), + ]); const create = useCreateAlertRule(); const update = useUpdateAlertRule(id ?? ''); @@ -124,6 +154,18 @@ export default function RuleEditorWizard() { )} + {warnings.length > 0 && ( +
+ Review before saving: +
    + {warnings.map((w) => ( +
  • + {w.field}: {w.message} +
  • + ))} +
+
+ )}