From 071551c66f12d18a90705ec26de213f210225cbe Mon Sep 17 00:00:00 2001 From: Kristina Date: Tue, 13 Aug 2024 15:12:12 +0400 Subject: [PATCH] Fix email notifications (#6328) Signed-off-by: Kristina Fefelova --- models/chunter/src/notifications.ts | 4 +- models/notification/src/index.ts | 4 +- models/server-chunter/src/index.ts | 4 ++ plugins/notification-assets/lang/en.json | 3 +- plugins/notification-assets/lang/es.json | 3 +- plugins/notification-assets/lang/fr.json | 3 +- plugins/notification-assets/lang/pt.json | 3 +- plugins/notification-assets/lang/ru.json | 3 +- plugins/notification-assets/lang/zh.json | 3 +- .../src/components/inbox/Inbox.svelte | 31 ++++++++++---- plugins/notification/src/index.ts | 3 +- server-plugins/chunter-resources/src/index.ts | 7 +++- server-plugins/chunter/src/index.ts | 1 + server-plugins/gmail-resources/package.json | 8 ++-- server-plugins/gmail-resources/src/index.ts | 12 +++--- .../notification-resources/src/index.ts | 40 +++++++++++++++---- .../notification-resources/src/utils.ts | 39 +++++++++++++++++- 17 files changed, 134 insertions(+), 37 deletions(-) diff --git a/models/chunter/src/notifications.ts b/models/chunter/src/notifications.ts index 2c4cd4bd05..cefebd1c50 100644 --- a/models/chunter/src/notifications.ts +++ b/models/chunter/src/notifications.ts @@ -129,8 +129,8 @@ export function defineNotifications (builder: Builder): void { defaultEnabled: false, group: chunter.ids.ChunterNotificationGroup, templates: { - textTemplate: '{body}', - htmlTemplate: '

{body}

', + textTemplate: '{sender} replied to {doc}:\n\n{message}', + htmlTemplate: '

{sender} replied to {doc}:

{message}

{link}

', subjectTemplate: '{title}' } }, diff --git a/models/notification/src/index.ts b/models/notification/src/index.ts index 04a2b8233c..aff1814fc6 100644 --- a/models/notification/src/index.ts +++ b/models/notification/src/index.ts @@ -556,8 +556,8 @@ export function createModel (builder: Builder): void { group: notification.ids.NotificationGroup, defaultEnabled: true, templates: { - textTemplate: '{sender} mentioned you in {doc} {message}', - htmlTemplate: '

{sender} mentioned you in {doc}

{message}', + textTemplate: '{sender} mentioned you in {doc}: {message}', + htmlTemplate: '

{sender} mentioned you in {doc}:

{message}

{link}

', subjectTemplate: 'You were mentioned in {doc}' } }, diff --git a/models/server-chunter/src/index.ts b/models/server-chunter/src/index.ts index 9302dfbf15..b74fa8fa47 100644 --- a/models/server-chunter/src/index.ts +++ b/models/server-chunter/src/index.ts @@ -37,6 +37,10 @@ export function createModel (builder: Builder): void { presenter: serverChunter.function.ChatMessageTextPresenter }) + builder.mixin(chunter.class.ChatMessage, core.class.Class, serverNotification.mixin.HTMLPresenter, { + presenter: serverChunter.function.ChatMessageTextPresenter + }) + builder.mixin, ObjectDDParticipant>( chunter.class.ChatMessage, core.class.Class, diff --git a/plugins/notification-assets/lang/en.json b/plugins/notification-assets/lang/en.json index 8f906a9060..741a4a0a22 100644 --- a/plugins/notification-assets/lang/en.json +++ b/plugins/notification-assets/lang/en.json @@ -56,6 +56,7 @@ "CommonNotificationCollectionRemoved": "{senderName} removed {collection}", "Sound": "Sound", "SoundNotificationsDescription": "Receive sound notifications for events.", - "NoAccessToObject": "You no longer have access to this object" + "NoAccessToObject": "You no longer have access to this object", + "ViewIn": "View in {app}" } } diff --git a/plugins/notification-assets/lang/es.json b/plugins/notification-assets/lang/es.json index eda8de8712..96f4c4c0eb 100644 --- a/plugins/notification-assets/lang/es.json +++ b/plugins/notification-assets/lang/es.json @@ -55,6 +55,7 @@ "SoundNotificationsDescription": "Reciba notificaciones de sonido para eventos.", "CommonNotificationCollectionAdded": "{senderName} añadió {collection}", "CommonNotificationCollectionRemoved": "{senderName} eliminó {collection}", - "NoAccessToObject": "Ya no tienes acceso a este objeto" + "NoAccessToObject": "Ya no tienes acceso a este objeto", + "ViewIn": "Ver en {app}" } } \ No newline at end of file diff --git a/plugins/notification-assets/lang/fr.json b/plugins/notification-assets/lang/fr.json index 97cc8e7587..27afae2d5d 100644 --- a/plugins/notification-assets/lang/fr.json +++ b/plugins/notification-assets/lang/fr.json @@ -56,6 +56,7 @@ "SoundNotificationsDescription": "Recevez des notifications sonores pour les événements.", "CommonNotificationCollectionAdded": "{senderName} a ajouté {collection}", "CommonNotificationCollectionRemoved": "{senderName} a supprimé {collection}", - "NoAccessToObject": "Vous n'avez plus accès à cet objet" + "NoAccessToObject": "Vous n'avez plus accès à cet objet", + "ViewIn": "Voir dans {app}" } } \ No newline at end of file diff --git a/plugins/notification-assets/lang/pt.json b/plugins/notification-assets/lang/pt.json index 6eda5c6b4b..a9e6901bdd 100644 --- a/plugins/notification-assets/lang/pt.json +++ b/plugins/notification-assets/lang/pt.json @@ -55,6 +55,7 @@ "SoundNotificationsDescription": "Receba notificações sonoras para eventos.", "CommonNotificationCollectionAdded": "{senderName} adicionou {collection}", "CommonNotificationCollectionRemoved": "{senderName} removeu {collection}", - "NoAccessToObject": "Você não tem mais acesso a este objeto" + "NoAccessToObject": "Você não tem mais acesso a este objeto", + "ViewIn": "Ver em {app}" } } \ No newline at end of file diff --git a/plugins/notification-assets/lang/ru.json b/plugins/notification-assets/lang/ru.json index ddcd2db164..ada9f03cc5 100644 --- a/plugins/notification-assets/lang/ru.json +++ b/plugins/notification-assets/lang/ru.json @@ -56,6 +56,7 @@ "SoundNotificationsDescription": "Получайте звуковые уведомления о событиях.", "CommonNotificationCollectionAdded": "{senderName} добавил {collection}", "CommonNotificationCollectionRemoved": "{senderName} удалил {collection}", - "NoAccessToObject": "У вас больше нет доступа к этому объекту" + "NoAccessToObject": "У вас больше нет доступа к этому объекту", + "ViewIn": "Посмотреть в {app}" } } diff --git a/plugins/notification-assets/lang/zh.json b/plugins/notification-assets/lang/zh.json index 43f9d51d1f..dd06de99ee 100644 --- a/plugins/notification-assets/lang/zh.json +++ b/plugins/notification-assets/lang/zh.json @@ -56,6 +56,7 @@ "SoundNotificationsDescription": "接收事件的声音通知。", "CommonNotificationCollectionAdded": "{senderName} 添加了 {collection}", "CommonNotificationCollectionRemoved": "{senderName} 移除了 {collection}", - "NoAccessToObject": "您不再可以访问此对象" + "NoAccessToObject": "您不再可以访问此对象", + "ViewIn": "在 {app} 中查看" } } diff --git a/plugins/notification-resources/src/components/inbox/Inbox.svelte b/plugins/notification-resources/src/components/inbox/Inbox.svelte index ca8e3624d4..0ddef087c6 100644 --- a/plugins/notification-resources/src/components/inbox/Inbox.svelte +++ b/plugins/notification-resources/src/components/inbox/Inbox.svelte @@ -15,7 +15,7 @@ , diff --git a/server-plugins/chunter-resources/src/index.ts b/server-plugins/chunter-resources/src/index.ts index 0b1186a6d5..1c1a6f8777 100644 --- a/server-plugins/chunter-resources/src/index.ts +++ b/server-plugins/chunter-resources/src/index.ts @@ -53,7 +53,7 @@ import { getDocCollaborators, getMixinTx } from '@hcengineering/server-notification-resources' -import { markupToText, stripTags } from '@hcengineering/text' +import { markupToHTML, markupToText, stripTags } from '@hcengineering/text' import { workbenchId } from '@hcengineering/workbench' import { NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification' @@ -91,6 +91,10 @@ export async function ChatMessageTextPresenter (doc: ChatMessage): Promise { + return markupToHTML(doc.message) +} + /** * @public */ @@ -580,6 +584,7 @@ export default async () => ({ ChannelTextPresenter: channelTextPresenter, ChunterNotificationContentProvider: getChunterNotificationContent, ChatMessageTextPresenter, + ChatMessageHtmlPresenter, JoinChannelTypeMatch } }) diff --git a/server-plugins/chunter/src/index.ts b/server-plugins/chunter/src/index.ts index d32a731e4f..dc92deda3d 100644 --- a/server-plugins/chunter/src/index.ts +++ b/server-plugins/chunter/src/index.ts @@ -40,6 +40,7 @@ export default plugin(serverChunterId, { ChannelTextPresenter: '' as Resource, ChunterNotificationContentProvider: '' as Resource, ChatMessageTextPresenter: '' as Resource, + ChatMessageHtmlPresenter: '' as Resource, JoinChannelTypeMatch: '' as TypeMatchFunc } }) diff --git a/server-plugins/gmail-resources/package.json b/server-plugins/gmail-resources/package.json index f885922d2b..888a13c03c 100644 --- a/server-plugins/gmail-resources/package.json +++ b/server-plugins/gmail-resources/package.json @@ -37,13 +37,15 @@ "@types/jest": "^29.5.5" }, "dependencies": { + "@hcengineering/activity": "^0.6.0", + "@hcengineering/contact": "^0.6.24", "@hcengineering/core": "^0.6.32", + "@hcengineering/gmail": "^0.6.22", + "@hcengineering/notification": "^0.6.23", "@hcengineering/platform": "^0.6.11", "@hcengineering/server-core": "^0.6.1", "@hcengineering/server-notification": "^0.6.1", "@hcengineering/server-notification-resources": "^0.6.0", - "@hcengineering/notification": "^0.6.23", - "@hcengineering/contact": "^0.6.24", - "@hcengineering/gmail": "^0.6.22" + "@hcengineering/text": "^0.6.5" } } diff --git a/server-plugins/gmail-resources/src/index.ts b/server-plugins/gmail-resources/src/index.ts index 732bfdb325..516e0c3d55 100644 --- a/server-plugins/gmail-resources/src/index.ts +++ b/server-plugins/gmail-resources/src/index.ts @@ -39,6 +39,7 @@ import serverNotification, { } from '@hcengineering/server-notification' import { getContentByTemplate } from '@hcengineering/server-notification-resources' import { getMetadata } from '@hcengineering/platform' +import { ActivityMessage } from '@hcengineering/activity' /** * @public @@ -134,7 +135,8 @@ async function notifyByEmail ( doc: Doc | undefined, sender: SenderInfo, receiver: ReceiverInfo, - data: InboxNotification + data: InboxNotification, + message?: ActivityMessage ): Promise { const account = receiver.account @@ -145,8 +147,7 @@ async function notifyByEmail ( const senderPerson = sender.person const senderName = senderPerson !== undefined ? formatName(senderPerson.name, control.branding?.lastNameFirst) : '' - const content = await getContentByTemplate(doc, senderName, type, control, '', data) - + const content = await getContentByTemplate(doc, senderName, type, control, '', data, message) if (content !== undefined) { await sendEmailNotification(control.ctx, content.text, content.html, content.subject, account.email) } @@ -158,7 +159,8 @@ const SendEmailNotifications: NotificationProviderFunc = async ( object: Doc, data: InboxNotification, receiver: ReceiverInfo, - sender: SenderInfo + sender: SenderInfo, + message?: ActivityMessage ): Promise => { if (types.length === 0) { return [] @@ -169,7 +171,7 @@ const SendEmailNotifications: NotificationProviderFunc = async ( } for (const type of types) { - await notifyByEmail(control, type._id, object, sender, receiver, data) + await notifyByEmail(control, type._id, object, sender, receiver, data, message) } return [] diff --git a/server-plugins/notification-resources/src/index.ts b/server-plugins/notification-resources/src/index.ts index b48118131d..b51314d057 100644 --- a/server-plugins/notification-resources/src/index.ts +++ b/server-plugins/notification-resources/src/index.ts @@ -80,7 +80,7 @@ import serverNotification, { SenderInfo } from '@hcengineering/server-notification' import serverView from '@hcengineering/server-view' -import { stripTags } from '@hcengineering/text' +import { markupToText, stripTags } from '@hcengineering/text' import { encodeObjectURI } from '@hcengineering/view' import { workbenchId } from '@hcengineering/workbench' import webpush, { WebPushError } from 'web-push' @@ -91,6 +91,7 @@ import { createPushCollaboratorsTx, getHTMLPresenter, getNotificationContent, + getNotificationLink, getTextPresenter, getUsersInfo, isAllowed, @@ -98,6 +99,7 @@ import { isShouldNotifyTx, isUserEmployeeInFieldValue, isUserInFieldValue, + messageToMarkup, replaceAll, toReceiverInfo, updateNotifyContextsSpace @@ -212,7 +214,8 @@ export async function getContentByTemplate ( type: Ref, control: TriggerControl, data: string, - notificationData?: InboxNotification + notificationData?: InboxNotification, + message?: ActivityMessage ): Promise { if (doc === undefined) return const notificationType = control.modelDb.getObject(type) @@ -220,11 +223,24 @@ export async function getContentByTemplate ( const textPart = await getTextPart(doc, control) if (textPart === undefined) return - const params = + const params: Record = notificationData !== undefined ? await getTranslatedNotificationContent(notificationData, notificationData._class, control) : {} + if (message !== undefined) { + const markup = await messageToMarkup(control, message) + params.message = markup !== undefined ? markupToText(markup) : params.message ?? '' + } else if (params.message === undefined) { + params.message = params.body ?? '' + } + + const link = await getNotificationLink(control, doc, message?._id) + const app = control.branding?.title ?? 'Huly' + const linkText = await translate(notification.string.ViewIn, { app }) + + params.link = `${linkText}` + const text = fillTemplate(notificationType.templates.textTemplate, sender, textPart, data, params) const htmlPart = await getHtmlPart(doc, control) const html = fillTemplate(notificationType.templates.htmlTemplate, sender, htmlPart ?? textPart, data, params) @@ -838,16 +854,24 @@ export async function createCollabDocInfo ( return res } - const notifyContexts = await control.findAllCtx(ctx, notification.class.DocNotifyContext, { objectId: object._id }) - - await updateContextsTimestamp(notifyContexts, originTx.modifiedOn, control, originTx.modifiedBy) - await removeContexts(notifyContexts, unsubscribe, control) - const docMessages = activityMessages.filter((message) => message.attachedTo === object._id) + if (docMessages.length === 0) { + if (unsubscribe.length > 0) { + const notifyContexts = await control.findAllCtx(ctx, notification.class.DocNotifyContext, { + objectId: object._id, + user: { $in: unsubscribe } + }) + await removeContexts(notifyContexts, unsubscribe, control) + } + return res } + const notifyContexts = await control.findAllCtx(ctx, notification.class.DocNotifyContext, { objectId: object._id }) + await removeContexts(notifyContexts, unsubscribe, control) + await updateContextsTimestamp(notifyContexts, originTx.modifiedOn, control, originTx.modifiedBy) + const targets = new Set(collaborators) // user is not collaborator of himself, but we should notify user of changes related to users account (mentions, comments etc) diff --git a/server-plugins/notification-resources/src/utils.ts b/server-plugins/notification-resources/src/utils.ts index 1fb631288e..aefe9df66f 100644 --- a/server-plugins/notification-resources/src/utils.ts +++ b/server-plugins/notification-resources/src/utils.ts @@ -55,10 +55,11 @@ import serverNotification, { SenderInfo, TextPresenter } from '@hcengineering/server-notification' -import { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity' +import activity, { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity' import serverView from '@hcengineering/server-view' import { workbenchId } from '@hcengineering/workbench' import { encodeObjectURI } from '@hcengineering/view' +import chunter, { ChatMessage } from '@hcengineering/chunter' import { NotifyResult } from './types' @@ -568,11 +569,45 @@ export async function getNotificationLink ( id = await encodeFn(doc, control) } + let thread: string | undefined + + if (control.hierarchy.isDerived(doc._class, activity.class.ActivityMessage)) { + const id = (doc as ActivityMessage)._id + + if (message === undefined) { + message = id + } else { + thread = id + } + } + const front = control.branding?.front ?? getMetadata(serverCore.metadata.FrontUrl) ?? '' - const path = [workbenchId, 'platform', notificationId, encodeObjectURI(id, doc._class)] + const path = [workbenchId, control.workspace.workspaceUrl, notificationId, encodeObjectURI(id, doc._class), thread] + .filter((x): x is string => x !== undefined) .map((p) => encodeURIComponent(p)) .join('/') + const link = concatLink(front, path) return message !== undefined ? `${link}?message=${message}` : link } + +export async function messageToMarkup (control: TriggerControl, message: ActivityMessage): Promise { + const { hierarchy } = control + if (hierarchy.isDerived(message._class, chunter.class.ChatMessage)) { + const chatMessage = message as ChatMessage + return chatMessage.message + } else { + const resource = getTextPresenter(message._class, control.hierarchy) + + if (resource !== undefined) { + const fn = await getResource(resource.presenter) + const textData = await fn(message, control) + if (textData !== undefined && textData !== '') { + return textData + } + } + } + + return undefined +}