Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions backend/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ type notificationPayload struct {
RecipientName string `json:"recipient_name"`
ActorName string `json:"actor_name"`
LinkURL string `json:"link_url"`
// EntityTitle is the title of the task (assigned/mentioned/
// task_description_mentioned) or document (doc_mentioned) the
// notification is about. May be empty — an older core that predates the
// field, or one that couldn't resolve the title — in which case the copy
// below falls back to its original title-less phrasing.
EntityTitle string `json:"entity_title"`
}

// handleEvent returns an EventHandler that sends the appropriate email for
Expand Down
35 changes: 31 additions & 4 deletions backend/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,36 +278,63 @@ type notificationCopy struct {
buttonLabel string // "View task" / "View document"
}

// copyForNotificationTopic builds the per-topic copy. When the event carries
// an entity_title (the task/document the notification is about), it's woven
// into both the heading (which doubles as the subject) and the body so the
// recipient knows *which* task/doc from the subject line alone; when it's
// empty (older core, or title couldn't be resolved) the original title-less
// phrasing is used verbatim, so this degrades gracefully.
func copyForNotificationTopic(topic string, data notificationPayload) notificationCopy {
actor := data.ActorName
if actor == "" {
actor = "Someone"
}
title := strings.TrimSpace(data.EntityTitle)
switch topic {
case topicNotificationMentioned:
return notificationCopy{
c := notificationCopy{
heading: "You were mentioned",
paragraph: fmt.Sprintf("%s mentioned you in a comment.", actor),
buttonLabel: "View task",
}
if title != "" {
c.heading = fmt.Sprintf(`You were mentioned in "%s"`, title)
c.paragraph = fmt.Sprintf(`%s mentioned you in a comment on "%s".`, actor, title)
}
return c
case topicNotificationDocMentioned:
return notificationCopy{
c := notificationCopy{
heading: "You were mentioned",
paragraph: fmt.Sprintf("%s mentioned you in a document.", actor),
buttonLabel: "View document",
}
if title != "" {
c.heading = fmt.Sprintf(`You were mentioned in "%s"`, title)
c.paragraph = fmt.Sprintf(`%s mentioned you in the document "%s".`, actor, title)
}
return c
case topicNotificationTaskDescMentioned:
return notificationCopy{
c := notificationCopy{
heading: "You were mentioned",
paragraph: fmt.Sprintf("%s mentioned you in a task's description.", actor),
buttonLabel: "View task",
}
if title != "" {
c.heading = fmt.Sprintf(`You were mentioned in "%s"`, title)
c.paragraph = fmt.Sprintf(`%s mentioned you in the description of "%s".`, actor, title)
}
return c
default: // topicNotificationAssigned
return notificationCopy{
c := notificationCopy{
heading: "A task was assigned to you",
paragraph: fmt.Sprintf("%s assigned a task to you.", actor),
buttonLabel: "View task",
}
if title != "" {
c.heading = fmt.Sprintf("Task assigned: %s", title)
c.paragraph = fmt.Sprintf(`%s assigned you the task "%s".`, actor, title)
}
return c
}
}

Expand Down
52 changes: 52 additions & 0 deletions backend/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,55 @@ func TestRenderLayout_EscapesBrandingFields(t *testing.T) {
t.Error("expected invalid branding PrimaryColorLight to fall back to defaultPrimaryColor")
}
}

// TestRenderNotificationEmail_IncludesEntityTitle covers the entity_title
// support: when the event carries the task/document title it must appear in
// both the subject and the body, and when it's empty the copy must fall back
// to the original title-less phrasing unchanged.
func TestRenderNotificationEmail_IncludesEntityTitle(t *testing.T) {
const title = "Ship the landing page"

cases := []struct {
name string
topic string
wantText string // a phrase that must appear in the body paragraph when titled
}{
{"assigned", topicNotificationAssigned, "assigned you the task \"" + title + "\""},
{"comment", topicNotificationMentioned, "mentioned you in a comment on \"" + title + "\""},
{"doc", topicNotificationDocMentioned, "mentioned you in the document \"" + title + "\""},
{"taskdesc", topicNotificationTaskDescMentioned, "in the description of \"" + title + "\""},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
data := notificationPayload{ActorName: "Ada", EntityTitle: title, LinkURL: "https://paca.example/x"}
subject, html, text := renderNotificationEmail(nil, tc.topic, data)

if !strings.Contains(subject, title) {
t.Errorf("subject should contain the title %q, got %q", title, subject)
}
if !strings.Contains(text, tc.wantText) {
t.Errorf("plain-text body should contain %q, got %q", tc.wantText, text)
}
if !strings.Contains(html, title) {
t.Errorf("html body should contain the title %q", title)
}
})
}
}

func TestRenderNotificationEmail_FallsBackWithoutTitle(t *testing.T) {
// No EntityTitle → original copy, no stray quotes, no title fragment.
data := notificationPayload{ActorName: "Ada"}
subject, _, text := renderNotificationEmail(nil, topicNotificationAssigned, data)

if subject != "A task was assigned to you" {
t.Errorf("expected the original title-less subject, got %q", subject)
}
if !strings.Contains(text, "Ada assigned a task to you.") {
t.Errorf("expected the original title-less body, got %q", text)
}
if strings.Contains(text, "\"\"") {
t.Errorf("empty title must not leave empty quotes in the body: %q", text)
}
}
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"id": "com.paca.smtp",
"displayName": "SMTP Email",
"description": "Sends transactional emails — new-account invites, task-assignment and mention notifications — over an admin-configured SMTP server.",
"version": "0.1.2",
"version": "0.1.3",
"minCoreVersion": "v0.13.3",
"permissions": ["db.read", "db.write", "events.subscribe", "email.send", "users.password_set_token.issue"],
"backend": {
Expand Down
Loading