diff --git a/web/src/components/alerts/AddTemplate.spec.ts b/web/src/components/alerts/AddTemplate.spec.ts index 9101e1448c..342abc4216 100644 --- a/web/src/components/alerts/AddTemplate.spec.ts +++ b/web/src/components/alerts/AddTemplate.spec.ts @@ -990,6 +990,40 @@ describe("AddTemplate - applies to", () => { expect(sent.data.applies_to).toEqual(["anomaly", "on_call"]); }); + it("lists All kinds first and ticking it clears the chosen kinds in one click", async () => { + const w = await mountComp(); + await selectCustomMode(w); + getForm(w).setFieldValue("name", "ops-card"); + await w.findComponent({ name: "QueryEditor" }).vm.$emit("update:query", '{"text":"x"}'); + expect((appliesTo(w).props("options") as any[])[0]).toEqual({ + label: "All kinds", + value: "all_kinds", + }); + expect(appliesTo(w).props("modelValue")).toEqual(["all_kinds"]); + + await appliesTo(w).vm.$emit("update:modelValue", ["anomaly", "incident"]); + await appliesTo(w).vm.$emit("update:modelValue", ["anomaly", "incident", "all_kinds"]); + await flushPromises(); + expect(appliesTo(w).props("modelValue")).toEqual(["all_kinds"]); + expect(w.find('[data-test="add-template-applies-to-mode"]').text()).toBe( + String(i18n.global.t("alert_templates.appliesToManual")), + ); + expect(w.find('[data-test="add-template-applies-to-reset"]').exists()).toBe(true); + + await submit(w); + expect((templateService.create as any).mock.calls[0][0].data.applies_to).toEqual([]); + }); + + it("ticking a kind while All kinds is ticked selects that kind alone", async () => { + const w = await mountComp(); + await selectCustomMode(w); + expect(appliesTo(w).props("modelValue")).toEqual(["all_kinds"]); + + await appliesTo(w).vm.$emit("update:modelValue", ["all_kinds", "synthetics"]); + await flushPromises(); + expect(appliesTo(w).props("modelValue")).toEqual(["synthetics"]); + }); + it("an existing template starts in manual mode with its stored list", async () => { const w = await mountComp({ template: { @@ -1008,7 +1042,7 @@ describe("AddTemplate - applies to", () => { const w = await mountComp({ template: { name: "ops-card", body: '{"text":"x"}', type: "http", title: "", applies_to: [] }, }); - expect(appliesTo(w).props("modelValue")).toEqual([]); + expect(appliesTo(w).props("modelValue")).toEqual(["all_kinds"]); expect(w.find('[data-test="add-template-applies-to-mode"]').text()).toBe( String(i18n.global.t("alert_templates.appliesToInferred")), ); diff --git a/web/src/components/alerts/AddTemplate.vue b/web/src/components/alerts/AddTemplate.vue index 9d9d809712..17b210190e 100644 --- a/web/src/components/alerts/AddTemplate.vue +++ b/web/src/components/alerts/AddTemplate.vue @@ -80,7 +80,7 @@ along with this program. If not, see .
([]); const appliesToMode = ref<"inferred" | "manual">("inferred"); const appliesToError = ref(null); const isPrebuiltTemplate = computed(() => !!props.template?.isPrebuilt && !props.isClone); -const appliesToOptions = computed(() => - NOTIFICATION_KINDS.map((kind) => ({ label: t(kindLabelKey(kind)), value: kind })), +// An empty list is stored for every kind; the select shows it as the ticked "All kinds" item. +const ALL_KINDS = "all_kinds"; +const appliesToOptions = computed(() => [ + { label: t("alert_templates.appliesToAll"), value: ALL_KINDS }, + ...NOTIFICATION_KINDS.map((kind) => ({ label: t(kindLabelKey(kind)), value: kind })), +]); +const appliesToSelection = computed(() => + appliesTo.value.length ? appliesTo.value : [ALL_KINDS], ); const isKind = (k: unknown): k is NotificationKind => NOTIFICATION_KINDS.includes(k as NotificationKind); @@ -491,7 +497,9 @@ const inferenceSource = computed(() => ); const onAppliesToChange = (value: unknown) => { - appliesTo.value = NOTIFICATION_KINDS.filter((k) => Array.isArray(value) && value.includes(k)); + const picked = Array.isArray(value) ? value : []; + const allKindsJustTicked = picked.includes(ALL_KINDS) && appliesTo.value.length > 0; + appliesTo.value = allKindsJustTicked ? [] : NOTIFICATION_KINDS.filter((k) => picked.includes(k)); appliesToMode.value = "manual"; appliesToError.value = null; }; diff --git a/web/src/components/alerts/template-content/TemplatePreviewPanel.spec.ts b/web/src/components/alerts/template-content/TemplatePreviewPanel.spec.ts index ca9a3eda7a..755841bcde 100644 --- a/web/src/components/alerts/template-content/TemplatePreviewPanel.spec.ts +++ b/web/src/components/alerts/template-content/TemplatePreviewPanel.spec.ts @@ -270,6 +270,38 @@ describe("TemplatePreviewPanel", () => { expect(button.text()).toBe("View in OpenObserve"); }); + it("puts Channel first, Severity next and Kind last in the preview row", async () => { + vi.spyOn(templateService, "preview").mockResolvedValue(makePreviewResponse() as any); + const w = mount(TemplatePreviewPanel, { + props: { spec: emptyContentSpec(), kinds: ["alert", "synthetics"] }, + global: { plugins: [i18n, makeStore()] }, + }); + await vi.advanceTimersByTimeAsync(300); + await flushPromises(); + + const selects = w + .find('[data-test="template-preview-panel-channel-select"]') + .element.parentElement!.querySelectorAll(":scope > [data-test]"); + expect(Array.from(selects).map((el) => el.getAttribute("data-test"))).toEqual([ + "template-preview-panel-channel-select", + "template-preview-panel-severity-select", + "template-preview-panel-kind-select", + ]); + const labels = w + .findAllComponents({ name: "OSelect" }) + .slice(0, 3) + .map((c) => c.props("label")); + expect(labels).toEqual(["Channel", "Severity", "Kind"]); + expect( + ( + w + .find('[data-test="template-preview-panel-channel-select"]') + .findComponent({ name: "OSelect" }) + .props("options") as any[] + ).length, + ).toBe(10); + }); + it("previews as the first selected kind and sends kind on each request", async () => { const previewSpy = vi .spyOn(templateService, "preview") diff --git a/web/src/components/alerts/template-content/TemplatePreviewPanel.vue b/web/src/components/alerts/template-content/TemplatePreviewPanel.vue index a6b169659e..b44f73798c 100644 --- a/web/src/components/alerts/template-content/TemplatePreviewPanel.vue +++ b/web/src/components/alerts/template-content/TemplatePreviewPanel.vue @@ -46,14 +46,6 @@ along with this program. If not, see . {{ t("alert_templates.previewGroupLabel") }}
- . :searchable="false" data-test="template-preview-panel-severity-select" /> +
diff --git a/web/src/locales/languages/en-US.json b/web/src/locales/languages/en-US.json index 30191475d5..ee9a4eaec5 100644 --- a/web/src/locales/languages/en-US.json +++ b/web/src/locales/languages/en-US.json @@ -13805,7 +13805,7 @@ "appliesToInferred": "Inferred from the variables in the body", "appliesToManual": "Chosen by you", "appliesToReset": "Reset to inferred", - "previewAs": "Preview as", + "previewKind": "Kind", "previewForbidden": "You do not have permission to preview templates", "previewFailed": "Preview failed: {message}", "severityFailed": "Failed",