-
Notifications
You must be signed in to change notification settings - Fork 455
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implement cron job for comment and reply email notification
GitOrigin-RevId: a378140f813856bd01c03bf8786fc26c785cba44
- Loading branch information
1 parent
2cc17f7
commit 3ebfc6b
Showing
5 changed files
with
526 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
138 changes: 115 additions & 23 deletions
138
platform/wab/src/wab/server/emails/comment-notification-email.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,127 @@ | ||
import { sendCommentNotificationEmail } from "@/wab/server/emails/comment-notification-email"; | ||
import { sendUserNotificationEmail } from "@/wab/server/emails/comment-notification-email"; | ||
import { setupEmailTest } from "@/wab/server/emails/test/email-test-util"; | ||
import { Project, User } from "@/wab/server/entities/Entities"; | ||
import { createProjectUrl } from "@/wab/shared/urls"; | ||
|
||
describe("sendCommentNotificationEmail", () => { | ||
it("sends an email", async () => { | ||
// Utility function to normalize HTML by removing extra whitespace | ||
const normalizeHtml = (html) => html.replace(/\s+/g, " "); | ||
|
||
describe("sendUserNotificationEmail", () => { | ||
it("sends an email with notifications grouped by project", async () => { | ||
const { req, config, mailer } = setupEmailTest(); | ||
await sendCommentNotificationEmail( | ||
req, | ||
{ | ||
name: "My Project", | ||
id: "proj-id", | ||
} as Project, | ||
{ | ||
email: "[email protected]", | ||
firstName: "Author", | ||
lastName: "Person", | ||
} as User, | ||
"[email protected]", | ||
"This is a comment" | ||
|
||
// Mock input | ||
const notifications = new Map([ | ||
[ | ||
"proj-1", | ||
{ | ||
projectName: "Project Alpha", | ||
threads: new Map([ | ||
[ | ||
"thread1", | ||
[ | ||
{ | ||
author: "John Doe", | ||
body: "What's this supposed to mean?", | ||
}, | ||
{ | ||
author: "Zoro", | ||
body: "This is a navigation system that I have developed", | ||
}, | ||
], | ||
], | ||
[ | ||
"thread2", | ||
[ | ||
{ | ||
author: "John Doe", | ||
body: "When can we expect to deliver this?", | ||
}, | ||
{ | ||
author: "Zoro", | ||
body: "In a week may be", | ||
}, | ||
], | ||
], | ||
]), | ||
}, | ||
], | ||
[ | ||
"proj-2", | ||
{ | ||
projectName: "Project Beta", | ||
threads: new Map([ | ||
[ | ||
"thread1", | ||
[ | ||
{ | ||
author: "John Doe", | ||
body: "Comment", | ||
}, | ||
{ | ||
author: "Sanji", | ||
body: "I can reply", | ||
}, | ||
{ | ||
author: "Nami", | ||
body: "I can aswell", | ||
}, | ||
], | ||
], | ||
[ | ||
"thread2", | ||
[ | ||
{ | ||
author: "John Doe", | ||
body: "this is a comment", | ||
}, | ||
{ | ||
author: "Nami", | ||
body: "this is a reply", | ||
}, | ||
], | ||
], | ||
]), | ||
}, | ||
], | ||
]); | ||
|
||
const expectedEmailBody = normalizeHtml( | ||
`<p> | ||
You have new activity in your projects:</p> <div><h2>New comments in project: <a href="${createProjectUrl( | ||
req.config.host, | ||
"proj-1" | ||
)}">${ | ||
notifications.get("proj-1")?.projectName | ||
}</a></h2><hr><p>What's this supposed to mean? by <strong>John Doe</strong></p><ul> <li><p>This is a navigation system that I have developed by <strong>Zoro</strong></p></li> </ul><hr><p>When can we expect to deliver this? by <strong>John Doe</strong></p><ul> <li><p>In a week may be by <strong>Zoro</strong></p></li> </ul></div><div><h2>New comments in project: <a href="${createProjectUrl( | ||
req.config.host, | ||
"proj-2" | ||
)}">${ | ||
notifications.get("proj-2")?.projectName | ||
}</a></h2><hr><p>Comment by <strong>John Doe</strong></p><ul> <li><p>I can reply by <strong>Sanji</strong></p></li> <li><p>I can aswell by <strong>Nami</strong></p></li> </ul><hr><p>this is a comment by <strong>John Doe</strong></p><ul> <li><p>this is a reply by <strong>Nami</strong></p></li> </ul></div> <p>If you wish to modify your notification settings, please visit the appropriate section in Plasmic Studio.</p>` | ||
); | ||
|
||
await sendUserNotificationEmail( | ||
mailer, | ||
"[email protected]", // User's email | ||
notifications, | ||
req.config.host, | ||
config.mailFrom, | ||
req.config.mailBcc // Optional BCC | ||
); | ||
|
||
// Get the actual email body sent | ||
const receivedHtml = normalizeHtml(mailer.sendMail.mock.calls[0][0].html); | ||
|
||
// Assert that the normalized HTML matches | ||
expect(receivedHtml).toBe(expectedEmailBody); | ||
|
||
// Assert other email properties | ||
expect(mailer.sendMail).toHaveBeenCalledWith({ | ||
from: config.mailFrom, | ||
to: "[email protected]", | ||
bcc: req.config.mailBcc, | ||
subject: `New comments from Author Person on My Project`, | ||
html: `<p><strong>Author Person</strong> replied to a comment on <strong>My Project</strong>:</p> | ||
<pre style="font: inherit;">This is a comment</pre> | ||
<p><a href="https://studio.plasmic.app/projects/proj-id">Open project in Plasmic Studio</a> to reply or change notification settings</p>`, | ||
subject: "New Activity in Your Projects", | ||
html: expect.any(String), // Already tested above | ||
}); | ||
}); | ||
}); |
82 changes: 61 additions & 21 deletions
82
platform/wab/src/wab/server/emails/comment-notification-email.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,71 @@ | ||
import { Project, User } from "@/wab/server/entities/Entities"; | ||
import { fullName } from "@/wab/shared/ApiSchemaUtil"; | ||
import { Mailer } from "@/wab/server/emails/Mailer"; | ||
import { | ||
ProjectThreads, | ||
UserComment, | ||
} from "@/wab/server/scripts/send-comments-notifications"; | ||
import { createProjectUrl } from "@/wab/shared/urls"; | ||
import { Request } from "express-serve-static-core"; | ||
|
||
export async function sendCommentNotificationEmail( | ||
req: Request, | ||
project: Project, | ||
author: User, | ||
function getComment(comment: UserComment) { | ||
return `<p>${comment.body} ${ | ||
comment.author ? `by <strong>${comment.author}</strong>` : "" | ||
}</p>`; | ||
} | ||
|
||
/** | ||
* Sends a user notification email with detailed project, thread, and comment breakdowns. | ||
*/ | ||
export async function sendUserNotificationEmail( | ||
mailer: Mailer, | ||
email: string, | ||
commentBody: string | ||
projects: Map<string, ProjectThreads>, | ||
host: string, | ||
mailFrom: string, | ||
mailBcc?: string | ||
) { | ||
const commentNotificationBody = `<p><strong>${fullName( | ||
author | ||
)}</strong> replied to a comment on <strong>${project.name}</strong>:</p> | ||
let commentsBody = ``; | ||
|
||
// Process each project in the Map | ||
for (const [projectId, { projectName, threads }] of projects) { | ||
const projectUrl = createProjectUrl(host, projectId); | ||
|
||
commentsBody += `<div><h2>New comments in project: <a href="${projectUrl}">${projectName}</a></h2>`; | ||
|
||
// Process each thread in the project (threads is a Map) | ||
for (const [threadId, comments] of threads) { | ||
if (comments.length === 0) { | ||
return; | ||
} // Skip empty threads | ||
|
||
commentsBody += `<hr>${getComment(comments[0])}`; | ||
|
||
if (comments.length > 1) { | ||
commentsBody += `<ul>`; | ||
|
||
// Add remaining comments | ||
comments.slice(1).forEach((comment) => { | ||
commentsBody += ` | ||
<li>${getComment(comment)}</li> | ||
`; | ||
}); | ||
|
||
commentsBody += `</ul>`; | ||
} | ||
} | ||
|
||
<pre style="font: inherit;">${commentBody}</pre> | ||
commentsBody += `</div>`; | ||
} | ||
|
||
<p><a href="${createProjectUrl( | ||
req.config.host, | ||
project.id | ||
)}">Open project in Plasmic Studio</a> to reply or change notification settings</p>`; | ||
const emailBody = `<p> | ||
You have new activity in your projects:</p> | ||
${commentsBody} | ||
<p>If you wish to modify your notification settings, please visit the appropriate section in Plasmic Studio.</p>`; | ||
|
||
await req.mailer.sendMail({ | ||
from: req.config.mailFrom, | ||
// Send the email | ||
await mailer.sendMail({ | ||
from: mailFrom, | ||
to: email, | ||
bcc: req.config.mailBcc, | ||
subject: `New comments from ${fullName(author)} on ${project.name}`, | ||
html: commentNotificationBody, | ||
bcc: mailBcc, // Optional BCC | ||
subject: "New Activity in Your Projects", | ||
html: emailBody, | ||
}); | ||
} |
Oops, something went wrong.