From 55595ec0422af398717c5f4e7a39ef40cb130244 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 15 Apr 2026 15:51:41 -0700 Subject: [PATCH] Trying to use more consistant components --- messages/en-US.json | 7 +- .../alert-rule-editor/AlertRuleFields.tsx | 426 ++++++------------ .../AlertRuleGraphEditor.tsx | 41 +- src/lib/alertRuleForm.ts | 54 +-- src/lib/alertRulesLocalStorage.ts | 4 - 5 files changed, 180 insertions(+), 352 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index df252ef4a..db140de0a 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1382,16 +1382,13 @@ "alertingTriggerHcUnhealthy": "Health check unhealthy", "alertingSectionActions": "Actions", "alertingAddAction": "Add action", - "alertingActionNotify": "Notify", - "alertingActionSms": "SMS", + "alertingActionNotify": "Email", "alertingActionWebhook": "Webhook", "alertingActionType": "Action type", "alertingNotifyUsers": "Users", "alertingNotifyRoles": "Roles", "alertingNotifyEmails": "Email addresses", "alertingEmailPlaceholder": "Add email and press Enter", - "alertingSmsNumbers": "Phone numbers", - "alertingSmsPlaceholder": "Add number and press Enter", "alertingWebhookMethod": "HTTP method", "alertingWebhookSecret": "Signing secret (optional)", "alertingWebhookSecretPlaceholder": "HMAC secret", @@ -1416,8 +1413,6 @@ "alertingErrorTriggerSite": "Choose a site trigger", "alertingErrorTriggerHealth": "Choose a health check trigger", "alertingErrorNotifyRecipients": "Pick users, roles, or at least one email", - "alertingErrorSmsPhones": "Add at least one phone number", - "alertingErrorWebhookUrl": "Enter a valid webhook URL", "alertingConfigureSource": "Configure Source", "alertingConfigureTrigger": "Configure Trigger", "alertingConfigureActions": "Configure Actions", diff --git a/src/components/alert-rule-editor/AlertRuleFields.tsx b/src/components/alert-rule-editor/AlertRuleFields.tsx index ec57ae065..73b2302e0 100644 --- a/src/components/alert-rule-editor/AlertRuleFields.tsx +++ b/src/components/alert-rule-editor/AlertRuleFields.tsx @@ -30,7 +30,7 @@ import { SelectTrigger, SelectValue } from "@app/components/ui/select"; -import { TagInput } from "@app/components/tags/tag-input"; +import { TagInput, type Tag } from "@app/components/tags/tag-input"; import { getUserDisplayName } from "@app/lib/getUserDisplayName"; import { type AlertRuleFormAction, @@ -46,9 +46,9 @@ import { useFormContext, useWatch } from "react-hook-form"; import { useDebounce } from "use-debounce"; export function DropdownAddAction({ - onPick + onAdd }: { - onPick: (type: "notify" | "sms" | "webhook") => void; + onAdd: (type: AlertRuleFormAction["type"]) => void; }) { const t = useTranslations(); const [open, setOpen] = useState(false); @@ -58,44 +58,32 @@ export function DropdownAddAction({ - -
- - - -
+ + + + + { + onAdd("notify"); + setOpen(false); + }} + > + {t("alertingActionNotify")} + + { + onAdd("webhook"); + setOpen(false); + }} + > + {t("alertingActionWebhook")} + + + + ); @@ -325,15 +313,10 @@ export function ActionBlock({ if (nt === "notify") { form.setValue(`actions.${index}`, { type: "notify", - userIds: [], - roleIds: [], + userTags: [], + roleTags: [], emailTags: [] }); - } else if (nt === "sms") { - form.setValue(`actions.${index}`, { - type: "sms", - phoneTags: [] - }); } else { form.setValue(`actions.${index}`, { type: "webhook", @@ -354,9 +337,6 @@ export function ActionBlock({ {t("alertingActionNotify")} - - {t("alertingActionSms")} - {t("alertingActionWebhook")} @@ -373,9 +353,6 @@ export function ActionBlock({ form={form} /> )} - {type === "sms" && ( - - )} {type === "webhook" && ( ; }) { const t = useTranslations(); + const [emailActiveIdx, setEmailActiveIdx] = useState(null); - const userIds = form.watch(`actions.${index}.userIds`) ?? []; - const roleIds = form.watch(`actions.${index}.roleIds`) ?? []; - const emailTags = form.watch(`actions.${index}.emailTags`) ?? []; + const [activeUsersTagIndex, setActiveUsersTagIndex] = useState< + number | null + >(null); + const [activeRolesTagIndex, setActiveRolesTagIndex] = useState< + number | null + >(null); + + const { data: orgUsers = [] } = useQuery(orgQueries.users({ orgId })); + const { data: orgRoles = [] } = useQuery(orgQueries.roles({ orgId })); + + const allUsers = useMemo( + () => + orgUsers.map((u) => ({ + id: String(u.id), + text: getUserDisplayName({ + email: u.email, + name: u.name, + username: u.username + }) + })), + [orgUsers] + ); + + const allRoles = useMemo( + () => + orgRoles + .map((r) => ({ id: String(r.roleId), text: r.name })) + .filter((r) => r.text !== "Admin"), + [orgRoles] + ); + + const userTags = (form.watch(`actions.${index}.userTags`) ?? []) as Tag[]; + const roleTags = (form.watch(`actions.${index}.roleTags`) ?? []) as Tag[]; + const emailTags = (form.watch(`actions.${index}.emailTags`) ?? []) as Tag[]; return (
- - {t("alertingNotifyUsers")} - - form.setValue(`actions.${index}.userIds`, ids) - } - /> - - - {t("alertingNotifyRoles")} - - form.setValue(`actions.${index}.roleIds`, ids) - } - /> - + ( + + {t("alertingNotifyUsers")} + + { + const next = + typeof newTags === "function" + ? newTags(userTags) + : newTags; + form.setValue( + `actions.${index}.userTags`, + next as Tag[] + ); + }} + enableAutocomplete={true} + autocompleteOptions={allUsers} + allowDuplicates={false} + restrictTagsToAutocompleteOptions={true} + sortTags={true} + /> + + + + )} + /> + ( + + {t("alertingNotifyRoles")} + + { + const next = + typeof newTags === "function" + ? newTags(roleTags) + : newTags; + form.setValue( + `actions.${index}.roleTags`, + next as Tag[] + ); + }} + enableAutocomplete={true} + autocompleteOptions={allRoles} + allowDuplicates={false} + restrictTagsToAutocompleteOptions={true} + sortTags={true} + /> + + + + )} + /> ( - + render={({ field }) => ( + {t("alertingNotifyEmails")} { const next = @@ -442,12 +502,18 @@ function NotifyActionFields({ : updater; form.setValue( `actions.${index}.emailTags`, - next + next as Tag[] ); }} activeTagIndex={emailActiveIdx} setActiveTagIndex={setEmailActiveIdx} placeholder={t("alertingEmailPlaceholder")} + size="sm" + allowDuplicates={false} + sortTags={true} + validateTag={(tag) => + /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(tag) + } delimiterList={[",", "Enter"]} /> @@ -459,51 +525,6 @@ function NotifyActionFields({ ); } -function SmsActionFields({ - index, - control, - form -}: { - index: number; - control: Control; - form: UseFormReturn; -}) { - const t = useTranslations(); - const [phoneActiveIdx, setPhoneActiveIdx] = useState(null); - const phoneTags = form.watch(`actions.${index}.phoneTags`) ?? []; - return ( - ( - - {t("alertingSmsNumbers")} - - { - const next = - typeof updater === "function" - ? updater(phoneTags) - : updater; - form.setValue( - `actions.${index}.phoneTags`, - next - ); - }} - activeTagIndex={phoneActiveIdx} - setActiveTagIndex={setPhoneActiveIdx} - placeholder={t("alertingSmsPlaceholder")} - delimiterList={[",", "Enter"]} - /> - - - - )} - /> - ); -} - function WebhookActionFields({ index, control, @@ -663,160 +684,6 @@ function WebhookHeadersField({ ); } -function UserMultiSelect({ - orgId, - value, - onChange -}: { - orgId: string; - value: string[]; - onChange: (v: string[]) => void; -}) { - const t = useTranslations(); - const [open, setOpen] = useState(false); - const [q, setQ] = useState(""); - const [debounced] = useDebounce(q, 150); - const { data: users = [] } = useQuery(orgQueries.users({ orgId })); - const shown = useMemo(() => { - const qq = debounced.trim().toLowerCase(); - if (!qq) return users.slice(0, 200); - return users - .filter((u) => { - const label = getUserDisplayName({ - email: u.email, - name: u.name, - username: u.username - }).toLowerCase(); - return ( - label.includes(qq) || - (u.email ?? "").toLowerCase().includes(qq) - ); - }) - .slice(0, 200); - }, [users, debounced]); - const toggle = (id: string) => { - if (value.includes(id)) { - onChange(value.filter((x) => x !== id)); - } else { - onChange([...value, id]); - } - }; - const summary = - value.length === 0 - ? t("alertingSelectUsers") - : t("alertingUsersSelected", { count: value.length }); - return ( - - - - - - - - - {t("noResults")} - - {shown.map((u) => { - const uid = String(u.id); - return ( - toggle(uid)} - className="cursor-pointer" - > - - {getUserDisplayName({ - email: u.email, - name: u.name, - username: u.username - })} - - ); - })} - - - - - - ); -} - -function RoleMultiSelect({ - orgId, - value, - onChange -}: { - orgId: string; - value: number[]; - onChange: (v: number[]) => void; -}) { - const t = useTranslations(); - const [open, setOpen] = useState(false); - const { data: roles = [] } = useQuery(orgQueries.roles({ orgId })); - const toggle = (id: number) => { - if (value.includes(id)) { - onChange(value.filter((x) => x !== id)); - } else { - onChange([...value, id]); - } - }; - const summary = - value.length === 0 - ? t("alertingSelectRoles") - : t("alertingRolesSelected", { count: value.length }); - return ( - - - - - - - - - {roles.map((r) => ( - toggle(r.roleId)} - className="cursor-pointer" - > - - {r.name} - - ))} - - - - - - ); -} - export function AlertRuleSourceFields({ orgId, control @@ -838,7 +705,8 @@ export function AlertRuleSourceFields({