diff --git a/messages/en-US.json b/messages/en-US.json index 0fefade48..30ed6c933 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1408,7 +1408,10 @@ "alertingSectionActions": "Actions", "alertingAddAction": "Add action", "alertingActionNotify": "Email", + "alertingActionNotifyDescription": "Send email notifications to users or roles", "alertingActionWebhook": "Webhook", + "alertingActionWebhookDescription": "Send an HTTP request to a custom endpoint", + "alertingExternalIntegration": "External Integration", "alertingActionType": "Action type", "alertingNotifyUsers": "Users", "alertingNotifyRoles": "Roles", diff --git a/public/third-party/incidentio.png b/public/third-party/incidentio.png new file mode 100644 index 000000000..e567d31fb Binary files /dev/null and b/public/third-party/incidentio.png differ diff --git a/public/third-party/opsgenie.png b/public/third-party/opsgenie.png new file mode 100644 index 000000000..3a1f5a849 Binary files /dev/null and b/public/third-party/opsgenie.png differ diff --git a/public/third-party/pgd.png b/public/third-party/pgd.png new file mode 100644 index 000000000..b084406a0 Binary files /dev/null and b/public/third-party/pgd.png differ diff --git a/public/third-party/servicenow.png b/public/third-party/servicenow.png new file mode 100644 index 000000000..b3fcca4dc Binary files /dev/null and b/public/third-party/servicenow.png differ diff --git a/src/components/alert-rule-editor/AlertRuleFields.tsx b/src/components/alert-rule-editor/AlertRuleFields.tsx index aa004357b..32fbbc5f1 100644 --- a/src/components/alert-rule-editor/AlertRuleFields.tsx +++ b/src/components/alert-rule-editor/AlertRuleFields.tsx @@ -44,13 +44,39 @@ import { } from "@app/lib/alertRuleForm"; import { orgQueries } from "@app/lib/queries"; import { useQuery } from "@tanstack/react-query"; -import { ChevronsUpDown, Plus, Trash2 } from "lucide-react"; +import { ContactSalesBanner } from "@app/components/ContactSalesBanner"; +import { Bell, Globe, ChevronsUpDown, Plus, Trash2 } from "lucide-react"; import { useTranslations } from "next-intl"; import { useEffect, useMemo, useRef, useState } from "react"; import type { Control, UseFormReturn } from "react-hook-form"; import { useFormContext, useWatch } from "react-hook-form"; import { useDebounce } from "use-debounce"; +const EXTERNAL_INTEGRATIONS = [ + { + id: "pagerduty", + name: "PagerDuty", + logo: "/third-party/pgd.png" + }, + { + id: "opsgenie", + name: "Opsgenie", + logo: "/third-party/opsgenie.png" + }, + { + id: "servicenow", + name: "ServiceNow", + logo: "/third-party/servicenow.png" + }, + { + id: "incidentio", + name: "Incident.io", + logo: "/third-party/incidentio.png" + } +] as const; + +const EXTERNAL_IDS = EXTERNAL_INTEGRATIONS.map((i) => i.id); + export function DropdownAddAction({ onAdd }: { @@ -58,8 +84,15 @@ export function DropdownAddAction({ }) { const t = useTranslations(); const [open, setOpen] = useState(false); + const [salesFor, setSalesFor] = useState(null); return ( - + { + setOpen(o); + if (!o) setSalesFor(null); + }} + > - - - - - { - onAdd("notify"); - setOpen(false); - }} - > - {t("alertingActionNotify")} - - { - onAdd("webhook"); - setOpen(false); - }} - > - {t("alertingActionWebhook")} - - - - + + {salesFor ? ( +
+
+ i.id === salesFor + )?.logo + } + alt={salesFor} + className="h-5 w-5 object-contain" + /> + + { + EXTERNAL_INTEGRATIONS.find( + (i) => i.id === salesFor + )?.name + } + +
+ + +
+ ) : ( + + + + { + onAdd("notify"); + setOpen(false); + }} + > + + {t("alertingActionNotify")} + + { + onAdd("webhook"); + setOpen(false); + }} + > + + {t("alertingActionWebhook")} + + + + {EXTERNAL_INTEGRATIONS.map((integration) => ( + + setSalesFor(integration.id) + } + > + {integration.name} + {integration.name} + + ))} + + + + )}
); @@ -382,6 +470,43 @@ export function ActionBlock({ }) { const t = useTranslations(); const type = useWatch({ control, name: `actions.${index}.type` }); + const [displayType, setDisplayType] = useState(type ?? "notify"); + + useEffect(() => { + if (!EXTERNAL_IDS.includes(displayType as any)) { + setDisplayType(type ?? "notify"); + } + }, [type]); + + const isPremium = EXTERNAL_IDS.includes(displayType as any); + + const actionTypeOptions = [ + { + id: "notify", + title: t("alertingActionNotify"), + description: t("alertingActionNotifyDescription"), + icon: + }, + { + id: "webhook", + title: t("alertingActionWebhook"), + description: t("alertingActionWebhookDescription"), + icon: + }, + ...EXTERNAL_INTEGRATIONS.map((integration) => ({ + id: integration.id, + title: integration.name, + description: t("alertingExternalIntegration"), + icon: ( + {integration.name} + ) + })) + ]; + return (
{canRemove && ( @@ -395,56 +520,44 @@ export function ActionBlock({ )} - ( - - {t("alertingActionType")} - - - )} - /> - {type === "notify" && ( +
+ + { + setDisplayType(v); + if (!EXTERNAL_IDS.includes(v as any)) { + const nt = v as AlertRuleFormAction["type"]; + if (nt === "notify") { + onUpdate({ + type: "notify", + userTags: [], + roleTags: [], + emailTags: [] + }); + } else { + onUpdate({ + type: "webhook", + url: "", + method: "POST", + headers: [], + authType: "none", + bearerToken: "", + basicCredentials: "", + customHeaderName: "", + customHeaderValue: "" + }); + } + } + }} + /> +
+ {isPremium && } + {!isPremium && type === "notify" && ( )} - {type === "webhook" && ( + {!isPremium && type === "webhook" && (