From 780f0400a060d22b1079998a5db716644f6ea8ed Mon Sep 17 00:00:00 2001 From: Subhra264 Date: Mon, 26 Oct 2026 12:19:47 +0000 Subject: [PATCH] fix(alerts): email destination form fixes (#15580) --- .../Alerts/dl-email-destinations.spec.js | 25 +++---- .../components/alerts/AddDestination.spec.ts | 69 ++++++++++++++++++- web/src/components/alerts/AddDestination.vue | 19 +++-- .../alerts/DestinationPreview.spec.ts | 11 ++- .../components/alerts/DestinationPreview.vue | 13 ++-- .../alerts/ImportDestination.spec.ts | 18 +++++ .../components/alerts/ImportDestination.vue | 11 ++- .../usePrebuiltDestinations.spec.ts | 47 +++++++++++++ .../composables/usePrebuiltDestinations.ts | 7 +- 9 files changed, 185 insertions(+), 35 deletions(-) diff --git a/tests/ui-testing/playwright-tests/Alerts/dl-email-destinations.spec.js b/tests/ui-testing/playwright-tests/Alerts/dl-email-destinations.spec.js index bb52c9ebf2..86e7f621dc 100644 --- a/tests/ui-testing/playwright-tests/Alerts/dl-email-destinations.spec.js +++ b/tests/ui-testing/playwright-tests/Alerts/dl-email-destinations.spec.js @@ -682,19 +682,10 @@ test.describe('Email destinations and distribution lists', () => { }); // end delivery - // ══ REGRESSION TESTS FOR OPEN DEFECTS (#2471) ════════════════════════════ - // These assert the CORRECT behaviour and therefore fail on today's build. - // Skipped rather than left failing, since none of the defects is P0/blocking. - // Un-skip the matching test as each issue closes — do not soften the assertion. - // (#2471 B4, semicolon-separated lists, no longer has a subject: recipients are - // picked from accounts, so no separator is ever parsed.) - - test.skip('UI-13 · the email form shows no Skip TLS Verify control [#2471 B5]', { + test('UI-13 · the email form shows no Skip TLS Verify control [#2471 B5]', { tag: ['@dlEmailDestinations', '@email', '@regression', '@P3', '@all'], }, async () => { - // BLOCKED BY #2471 (B5): the toggle is rendered on the email form but has no - // effect — an email destination carries only recipients, and SMTP transport - // security is server configuration (ZO_SMTP_ENCRYPTION). + // SMTP transport security is server configuration (ZO_SMTP_ENCRYPTION), so the toggle would do nothing. await pm.alertDestinationsPage.clickNewDestination(); await pm.alertDestinationsPage.selectDestinationType('email'); @@ -702,12 +693,9 @@ test.describe('Email destinations and distribution lists', () => { 'a control with no effect on email must not be shown').toBe(false); }); - test.skip('UI-14 · Preview shows the configured sender and the entered recipients [#2471 B7]', { + test('UI-14 · Preview shows the entered recipients [#2471 B7]', { tag: ['@dlEmailDestinations', '@email', '@regression', '@P3', '@all'], }, async () => { - // BLOCKED BY #2471 (B7): the dialog renders hardcoded - // From: alerts@openobserve.ai / To: admin@example.com, so it answers - // "will this reach the right people?" wrongly. const destName = `auto_dest_dl_preview_${RUN}`; await fillEmailForm(pm, destName, ORG_USER); const preview = await pm.alertDestinationsPage.openPreviewAndGetText(); @@ -716,6 +704,13 @@ test.describe('Email destinations and distribution lists', () => { expect(preview, 'Preview must not show a placeholder recipient').not.toContain('admin@example.com'); }); + // ══ REGRESSION TESTS FOR OPEN DEFECTS (#2471) ════════════════════════════ + // These assert the CORRECT behaviour and therefore fail on today's build. + // Skipped rather than left failing, since none of the defects is P0/blocking. + // Un-skip the matching test as each issue closes — do not soften the assertion. + // (#2471 B4, semicolon-separated lists, no longer has a subject: recipients are + // picked from accounts, so no separator is ever parsed.) + // BLOCKED BY #2471 (B2): membership is validated in destinations.rs save() // and test_email() only; the send path never re-checks, so alerts keep // reaching a removed user while the destination can no longer be saved. diff --git a/web/src/components/alerts/AddDestination.spec.ts b/web/src/components/alerts/AddDestination.spec.ts index 76bf328eda..039520691b 100644 --- a/web/src/components/alerts/AddDestination.spec.ts +++ b/web/src/components/alerts/AddDestination.spec.ts @@ -143,7 +143,7 @@ function mountComp(props: Record = {}) { DestinationPreview: { name: "DestinationPreview", template: '
', - props: ["modelValue", "type", "templateContent"], + props: ["modelValue", "type", "templateContent", "recipients"], }, AppTabs: { template: '
', @@ -1367,3 +1367,70 @@ describe("AddDestination — email recipients are org users", () => { ]); }); }); + +describe("AddDestination — email destinations", () => { + const TEMPLATES = [ + { name: "email_tpl", type: "email", body: "" }, + { name: "raw_http_tpl", type: "http", kind: "custom", body: "" }, + { name: "content_tpl", type: "http", kind: "content", body: "" }, + ]; + + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(usersService.orgUsers).mockResolvedValue({ data: { data: [] } } as any); + vi.mocked(usersService.getRoles).mockResolvedValue({ data: [] } as any); + }); + + const toPrebuiltEmail = async (w: any) => { + const form = getForm(w); + form.setFieldValue("destination_type", "email"); + form.setFieldValue("type", "email"); + form.setFieldValue("credentials", { recipients: ["a@acme.io", "b@acme.io"] }); + await flushPromises(); + return form; + }; + + it("offers content templates alongside email templates, on both paths", async () => { + wrapper = mountComp({ templates: TEMPLATES }); + const form = getForm(wrapper); + form.setFieldValue("destination_type", "custom"); + form.setFieldValue("type", "email"); + await nextTick(); + expect(wrapper.vm.getFormattedTemplates).toEqual(["email_tpl", "content_tpl"]); + + await toPrebuiltEmail(wrapper); + expect(wrapper.vm.prebuiltTemplateOptions.map((o: any) => o.value)).toEqual([ + "prebuilt_email", + "email_tpl", + "content_tpl", + ]); + }); + + it("hides Additional Settings for prebuilt email, keeps it for other prebuilt types", async () => { + wrapper = mountComp(); + await toPrebuiltEmail(wrapper); + expect(wrapper.find('[data-test="prebuilt-additional-settings"]').exists()).toBe(false); + expect(wrapper.find('[data-test="add-destination-skip-tls-verify-toggle"]').exists()).toBe( + false, + ); + + const form = getForm(wrapper); + form.setFieldValue("destination_type", "teams"); + form.setFieldValue("type", "http"); + form.setFieldValue("credentials", {}); + await flushPromises(); + expect(wrapper.find('[data-test="prebuilt-additional-settings"]').exists()).toBe(true); + expect(wrapper.find('[data-test="add-destination-skip-tls-verify-toggle"]').exists()).toBe( + true, + ); + }); + + it("passes the entered recipients to the preview", async () => { + wrapper = mountComp(); + await toPrebuiltEmail(wrapper); + expect(wrapper.findComponent({ name: "DestinationPreview" }).props("recipients")).toEqual([ + "a@acme.io", + "b@acme.io", + ]); + }); +}); diff --git a/web/src/components/alerts/AddDestination.vue b/web/src/components/alerts/AddDestination.vue index b9fe15a67f..ac1368d462 100644 --- a/web/src/components/alerts/AddDestination.vue +++ b/web/src/components/alerts/AddDestination.vue @@ -140,9 +140,9 @@ along with this program. If not, see . - +
@@ -150,8 +150,8 @@ along with this program. If not, see . {{ t("alert_destinations.additional_settings") }}
- -
+ +
{{ t("alert_destinations.custom_headers") }}
@@ -479,6 +479,7 @@ along with this program. If not, see . v-model="showPreviewModal" :type="dtVal" :template-content="previewContent" + :recipients="previewRecipients" data-test="destination-preview-modal" /> @@ -709,6 +710,9 @@ const prebuiltCredentials = form.useStore( const destinationSearchQuery = ref(""); const showPreviewModal = ref(false); const previewContent = ref(""); +const previewRecipients = computed(() => + Array.isArray(prebuiltCredentials.value.recipients) ? prebuiltCredentials.value.recipients : [], +); // A test result is stale once anything it was sent with changes; an in-flight // test is discarded too. @@ -1150,7 +1154,8 @@ const extractPrebuiltCredentials = (typeId: string): Record => { const getFormattedTemplates = computed(() => props.templates .filter((template) => { - if (typeVal.value === "email" && template.type === "email") return true; + if (typeVal.value === "email" && (template.type === "email" || template.kind === "content")) + return true; else if (typeVal.value !== "email") return true; return false; }) @@ -1171,13 +1176,13 @@ const defaultPrebuiltTemplateName = computed(() => templateNameFor(dtVal.value)) // Template choices for a prebuilt destination: the API-sourced prebuilt // template for this type as the first (default) option, followed by any -// user-created custom templates of the matching kind (email vs http). +// user-created templates that fit the channel (email or content for email). // Other prebuilt types are excluded to prevent cross-type mismatches. const prebuiltTemplateOptions = computed(() => { const isEmailType = dtVal.value === "email"; const matching = props.templates.filter((template) => { if (template.isPrebuilt) return false; - if (isEmailType) return template.type === "email"; + if (isEmailType) return template.type === "email" || template.kind === "content"; return template.type !== "email"; }); diff --git a/web/src/components/alerts/DestinationPreview.spec.ts b/web/src/components/alerts/DestinationPreview.spec.ts index 59d37845bd..0416cfeed6 100644 --- a/web/src/components/alerts/DestinationPreview.spec.ts +++ b/web/src/components/alerts/DestinationPreview.spec.ts @@ -199,10 +199,15 @@ describe("DestinationPreview", () => { expect(subject.text()).toContain("OpenObserve Alert Notification"); }); - it("should display Email from address", () => { + it("shows the entered recipients as To and no hardcoded From", () => { + wrapper = mountComponent({ type: "email", recipients: ["a@acme.io", "b@acme.io"] }); + expect(wrapper.find('[data-test="email-to"]').text()).toBe("To: a@acme.io, b@acme.io"); + expect(wrapper.find('[data-test="email-from"]').exists()).toBe(false); + }); + + it("shows no To line before any recipient is picked", () => { wrapper = mountComponent({ type: "email" }); - const from = wrapper.find('[data-test="email-from"]'); - expect(from.text()).toContain("alerts@openobserve.ai"); + expect(wrapper.find('[data-test="email-to"]').exists()).toBe(false); }); it("should display Email body", () => { diff --git a/web/src/components/alerts/DestinationPreview.vue b/web/src/components/alerts/DestinationPreview.vue index 533b05959e..3a538619ab 100644 --- a/web/src/components/alerts/DestinationPreview.vue +++ b/web/src/components/alerts/DestinationPreview.vue @@ -191,11 +191,12 @@ limitations under the License. > {{ raw("Subject: 🚨 OpenObserve Alert Notification") }}
- -