feat: allow html content for notifications (#9613)
* feat: allow passing provider specific metadata to notification module * changed providerContext to snake cased * rename provider_context to content
This commit is contained in:
@@ -38,6 +38,10 @@ medusaIntegrationTestRunner({
|
|||||||
trigger_type: "order-created",
|
trigger_type: "order-created",
|
||||||
resource_id: "order-id",
|
resource_id: "order-id",
|
||||||
resource_type: "order",
|
resource_type: "order",
|
||||||
|
content: {
|
||||||
|
subject: "We received you order",
|
||||||
|
html: "<p>Thank you<p>",
|
||||||
|
},
|
||||||
} as CreateNotificationDTO
|
} as CreateNotificationDTO
|
||||||
|
|
||||||
const result = await service.createNotifications(notification)
|
const result = await service.createNotifications(notification)
|
||||||
@@ -67,6 +71,8 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
expect(result).toHaveProperty("content")
|
||||||
|
|
||||||
expect(fromDB).toEqual(
|
expect(fromDB).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
to: "test@medusajs.com",
|
to: "test@medusajs.com",
|
||||||
@@ -83,6 +89,8 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
expect(fromDB).not.toHaveProperty("content")
|
||||||
|
|
||||||
expect(logSpy).toHaveBeenCalledWith(
|
expect(logSpy).toHaveBeenCalledWith(
|
||||||
`Attempting to send a notification to: 'test@medusajs.com' on the channel: 'email' with template: 'order-created' and data: '{\"username\":\"john-doe\"}'`
|
`Attempting to send a notification to: 'test@medusajs.com' on the channel: 'email' with template: 'order-created' and data: '{\"username\":\"john-doe\"}'`
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -176,3 +176,23 @@ export interface FilterableNotificationProps
|
|||||||
*/
|
*/
|
||||||
created_at?: OperatorMap<string>
|
created_at?: OperatorMap<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface
|
||||||
|
*
|
||||||
|
* The structure for content passed to the notification provider.
|
||||||
|
*/
|
||||||
|
export interface NotificationContent {
|
||||||
|
/**
|
||||||
|
* the subject of the notification
|
||||||
|
*/
|
||||||
|
subject?: string
|
||||||
|
/**
|
||||||
|
* the text content of the notification
|
||||||
|
*/
|
||||||
|
text?: string
|
||||||
|
/**
|
||||||
|
* the html content of the notification
|
||||||
|
*/
|
||||||
|
html?: string
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { NotificationContent } from "./common"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @interface
|
* @interface
|
||||||
*
|
*
|
||||||
@@ -21,6 +23,10 @@ export interface CreateNotificationDTO {
|
|||||||
* The data that gets passed over to the provider for rendering the notification.
|
* The data that gets passed over to the provider for rendering the notification.
|
||||||
*/
|
*/
|
||||||
data?: Record<string, unknown> | null
|
data?: Record<string, unknown> | null
|
||||||
|
/**
|
||||||
|
* The content that gets passed over to the provider.
|
||||||
|
*/
|
||||||
|
content?: NotificationContent | null
|
||||||
/**
|
/**
|
||||||
* The event name, the workflow, or anything else that can help to identify what triggered the notification.
|
* The event name, the workflow, or anything else that can help to identify what triggered the notification.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Attachment } from "./common"
|
import { Attachment, NotificationContent } from "./common"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @interface
|
* @interface
|
||||||
@@ -30,6 +30,10 @@ export type ProviderSendNotificationDTO = {
|
|||||||
* The data that gets passed over to the provider for rendering the notification.
|
* The data that gets passed over to the provider for rendering the notification.
|
||||||
*/
|
*/
|
||||||
data?: Record<string, unknown> | null
|
data?: Record<string, unknown> | null
|
||||||
|
/**
|
||||||
|
* The content that gets passed to the provider.
|
||||||
|
*/
|
||||||
|
content?: NotificationContent | null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+24
@@ -85,6 +85,30 @@ moduleIntegrationTestRunner<INotificationModuleService>({
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should send a notification and don't store the content in the database", async () => {
|
||||||
|
const notification = {
|
||||||
|
to: "admin@medusa.com",
|
||||||
|
template: "signup-template",
|
||||||
|
channel: "email",
|
||||||
|
data: {},
|
||||||
|
content: {
|
||||||
|
html: "<p>Welcome to medusa</p>",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await service.createNotifications(notification)
|
||||||
|
const dbEntry = await service.retrieveNotification(result.id)
|
||||||
|
|
||||||
|
expect(dbEntry).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
provider_id: "test-provider",
|
||||||
|
external_id: "external_id",
|
||||||
|
status: NotificationStatus.SUCCESS,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
expect(dbEntry).not.toHaveProperty("content")
|
||||||
|
})
|
||||||
|
|
||||||
it("should emit an event when a notification is created", async () => {
|
it("should emit an event when a notification is created", async () => {
|
||||||
const notification = {
|
const notification = {
|
||||||
to: "admin@medusa.com",
|
to: "admin@medusa.com",
|
||||||
|
|||||||
+37
@@ -6,6 +6,7 @@ jest.setTimeout(100000)
|
|||||||
describe.skip("Sendgrid notification provider", () => {
|
describe.skip("Sendgrid notification provider", () => {
|
||||||
let sendgridService: SendgridNotificationService
|
let sendgridService: SendgridNotificationService
|
||||||
let emailTemplate = ""
|
let emailTemplate = ""
|
||||||
|
let emailContent = ""
|
||||||
let to = ""
|
let to = ""
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
sendgridService = new SendgridNotificationService(
|
sendgridService = new SendgridNotificationService(
|
||||||
@@ -19,6 +20,7 @@ describe.skip("Sendgrid notification provider", () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
emailTemplate = process.env.SENDGRID_TEST_TEMPLATE ?? ""
|
emailTemplate = process.env.SENDGRID_TEST_TEMPLATE ?? ""
|
||||||
|
emailContent = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office"><head><meta charset="UTF-8"></head><body>Welcome to medusa</body></html>`
|
||||||
to = process.env.SENDGRID_TEST_TO ?? ""
|
to = process.env.SENDGRID_TEST_TO ?? ""
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -35,6 +37,41 @@ describe.skip("Sendgrid notification provider", () => {
|
|||||||
expect(resp).toEqual({})
|
expect(resp).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("sends an email with the specified email body", async () => {
|
||||||
|
const resp = await sendgridService.send({
|
||||||
|
to,
|
||||||
|
channel: "email",
|
||||||
|
template: "signup-template",
|
||||||
|
content: {
|
||||||
|
subject: "It's a test",
|
||||||
|
html: emailContent,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
username: "john-doe",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(resp).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throws an exception if the subject is not present for html content", async () => {
|
||||||
|
const error = await sendgridService
|
||||||
|
.send({
|
||||||
|
to,
|
||||||
|
template: "signup-template",
|
||||||
|
channel: "email",
|
||||||
|
content: { html: emailContent },
|
||||||
|
data: {
|
||||||
|
username: "john-doe",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch((e) => e)
|
||||||
|
|
||||||
|
expect(error.message).toEqual(
|
||||||
|
"Failed to send email: 400 - The subject is required. You can get around this requirement if you use a template with a subject defined or if every personalization has a subject defined."
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
it("throws an exception if the template does not exist", async () => {
|
it("throws an exception if the template does not exist", async () => {
|
||||||
const error = await sendgridService
|
const error = await sendgridService
|
||||||
.send({
|
.send({
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ type InjectedDependencies = {
|
|||||||
logger: Logger
|
logger: Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MailContent = Required<Omit<NotificationTypes.NotificationContent, "text">>
|
||||||
|
|
||||||
interface SendgridServiceConfig {
|
interface SendgridServiceConfig {
|
||||||
apiKey: string
|
apiKey: string
|
||||||
from: string
|
from: string
|
||||||
@@ -59,14 +61,32 @@ export class SendgridNotificationService extends AbstractNotificationProviderSer
|
|||||||
|
|
||||||
const from = notification.from?.trim() || this.config_.from
|
const from = notification.from?.trim() || this.config_.from
|
||||||
|
|
||||||
|
let mailContent:
|
||||||
|
| MailContent
|
||||||
|
| {
|
||||||
|
templateId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("content" in notification && !!notification.content) {
|
||||||
|
mailContent = {
|
||||||
|
subject: notification.content?.subject,
|
||||||
|
html: notification.content?.html,
|
||||||
|
} as MailContent
|
||||||
|
} else {
|
||||||
|
// we can't mix html and templates for sendgrid
|
||||||
|
mailContent = {
|
||||||
|
templateId: notification.template,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const message: sendgrid.MailDataRequired = {
|
const message: sendgrid.MailDataRequired = {
|
||||||
to: notification.to,
|
to: notification.to,
|
||||||
from: from,
|
from: from,
|
||||||
templateId: notification.template,
|
|
||||||
dynamicTemplateData: notification.data as
|
dynamicTemplateData: notification.data as
|
||||||
| { [key: string]: any }
|
| { [key: string]: any }
|
||||||
| undefined,
|
| undefined,
|
||||||
attachments: attachments,
|
attachments: attachments,
|
||||||
|
...mailContent,
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user