diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index 92d7848f7b..31464f8849 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -36578,6 +36578,12 @@ importers:
'@hcengineering/drive-assets':
specifier: workspace:^0.7.0
version: link:../../plugins/drive-assets
+ '@hcengineering/export':
+ specifier: workspace:^0.7.0
+ version: link:../../plugins/export
+ '@hcengineering/export-assets':
+ specifier: workspace:^0.7.0
+ version: link:../../plugins/export-assets
'@hcengineering/github':
specifier: workspace:^0.7.0
version: link:../../services/github/github
diff --git a/models/export/src/index.ts b/models/export/src/index.ts
index 05cb3384b1..14fc130562 100644
--- a/models/export/src/index.ts
+++ b/models/export/src/index.ts
@@ -89,7 +89,12 @@ export function createModel (builder: Builder): void {
group: exportPlugin.ids.ImportNotificationGroup,
txClasses: [],
objectClass: exportPlugin.class.ExportResultRecord,
- defaultEnabled: true
+ defaultEnabled: true,
+ templates: {
+ textTemplate: '{body}',
+ htmlTemplate: '
{body}
{link}
',
+ subjectTemplate: '{title}'
+ }
},
exportPlugin.ids.ImportedDocumentsNotification
)
diff --git a/server-plugins/gmail-resources/src/index.ts b/server-plugins/gmail-resources/src/index.ts
index cc62d3db4d..d8fbbf0c4c 100644
--- a/server-plugins/gmail-resources/src/index.ts
+++ b/server-plugins/gmail-resources/src/index.ts
@@ -44,7 +44,10 @@ import serverNotification from '@hcengineering/server-notification'
import {
AvailableProvidersCache,
AvailableProvidersCacheKey,
- getContentByTemplate
+ getContentByTemplate,
+ getNotificationProviderControl,
+ getReceiversInfo,
+ getAllowedProviders
} from '@hcengineering/server-notification-resources'
import { getMetadata } from '@hcengineering/platform'
import activity, { ActivityMessage } from '@hcengineering/activity'
@@ -156,7 +159,7 @@ async function notifyByEmail (
senderSocialId: PersonId,
email: string,
data: InboxNotification,
- message: ActivityMessage
+ message?: ActivityMessage
): Promise {
let senderName = sender !== undefined ? formatName(sender.name, control.branding?.lastNameFirst) : ''
if (senderName === '' && senderSocialId === core.account.System) {
@@ -166,6 +169,12 @@ async function notifyByEmail (
if (content !== undefined) {
await sendEmailNotification(control.ctx, content.text, content.html, content.subject, email)
+ } else {
+ control.ctx.info('notifyByEmail: getContentByTemplate returned undefined, email not sent', {
+ notificationId: data._id,
+ type,
+ docClass: doc._class
+ })
}
}
@@ -197,7 +206,10 @@ async function processEmailNotifications (control: TriggerControl, notifications
const docId = notifications[0].objectId
const docClass = notifications[0].objectClass
const doc = (await control.findAll(control.ctx, docClass, { _id: docId }))[0]
- if (doc === undefined) return
+ if (doc === undefined) {
+ control.ctx.info('processEmailNotifications: doc not found', { docId, docClass })
+ return
+ }
const messages = await getNotificationMessages(notifications, control)
const { hierarchy } = control
@@ -205,7 +217,10 @@ async function processEmailNotifications (control: TriggerControl, notifications
for (const n of notifications) {
const type = (n.types ?? [])[0]
- if (type === undefined) continue
+ if (type === undefined) {
+ control.ctx.info('processEmailNotifications: skipping notification without type', { notificationId: n._id })
+ continue
+ }
let message: ActivityMessage | undefined
if (hierarchy.isDerived(n._class, notification.class.ActivityInboxNotification)) {
const activityNotification = n as ActivityInboxNotification
@@ -215,11 +230,22 @@ async function processEmailNotifications (control: TriggerControl, notifications
if (hierarchy.isDerived(mentionNotification.mentionedInClass, activity.class.ActivityMessage)) {
message = messages.find((m) => m._id === mentionNotification.mentionedIn)
}
+ } else if (hierarchy.isDerived(n._class, notification.class.CommonInboxNotification)) {
+ message = undefined
}
- if (message === undefined) continue
+ if (message === undefined && !hierarchy.isDerived(n._class, notification.class.CommonInboxNotification)) {
+ control.ctx.info('processEmailNotifications: skipping - no ActivityMessage and not CommonInboxNotification', {
+ notificationId: n._id,
+ notificationClass: n._class
+ })
+ continue
+ }
const employee = await getEmployeeByAcc(control, n.user)
- if (employee === undefined) continue
+ if (employee === undefined) {
+ control.ctx.info('processEmailNotifications: no employee for user', { notificationId: n._id, user: n.user })
+ continue
+ }
const emailQuery = {
attachedTo: employee._id,
type: { $in: [SocialIdType.EMAIL, SocialIdType.GOOGLE] },
@@ -247,30 +273,85 @@ async function processEmailNotifications (control: TriggerControl, notifications
continue
}
- const senderSocialId = message.createdBy ?? message.modifiedBy
+ const senderSocialId = message !== undefined ? (message.createdBy ?? message.modifiedBy) : core.account.System
const sender = senders.get(senderSocialId) ?? (await getPerson(control, senderSocialId))
if (sender != null) {
senders.set(senderSocialId, sender)
}
+ control.ctx.info('processEmailNotifications: sending email', {
+ notificationId: n._id,
+ type,
+ notificationClass: n._class
+ })
await notifyByEmail(control, type, doc, sender, senderSocialId, emails[0].value, n, message)
}
}
+function hasEmailProvider (n: InboxNotification, availableProviders: AvailableProvidersCache): boolean {
+ const providers = availableProviders.get(n._id) ?? availableProviders.get(n.objectId as Ref)
+ return providers?.find((p) => p === gmail.providers.EmailNotificationProvider) !== undefined
+}
+
async function NotificationsHandler (txes: TxCreateDoc[], control: TriggerControl): Promise {
+ control.ctx.info('NotificationsHandler: received InboxNotification txes', {
+ count: txes.length,
+ workspace: control.workspace?.url,
+ objectClasses: [...new Set(txes.map((tx) => tx.objectClass))]
+ })
+
const availableProviders: AvailableProvidersCache = control.contextCache.get(AvailableProvidersCacheKey) ?? new Map()
- const all: InboxNotification[] = txes
- .map((tx) => TxProcessor.createDoc2Doc(tx))
- .filter(
- (it) => availableProviders.get(it._id)?.find((p) => p === gmail.providers.EmailNotificationProvider) !== undefined
- )
+ const all: InboxNotification[] = txes.map((tx) => TxProcessor.createDoc2Doc(tx))
- if (all.length === 0) {
+ const notificationsWithEmail = all.filter((it) => hasEmailProvider(it, availableProviders))
+
+ control.ctx.info('NotificationsHandler: processing inbox notifications', {
+ total: all.length,
+ withEmailFromCache: notificationsWithEmail.length,
+ notificationClasses: [...new Set(all.map((n) => n._class))],
+ notificationTypes: [...new Set(all.flatMap((n) => n.types ?? []))]
+ })
+
+ if (notificationsWithEmail.length < all.length) {
+ const notificationControl = await getNotificationProviderControl(control.ctx, control)
+ const receivers = await getReceiversInfo(control.ctx, [...new Set(all.map((n) => n.user))], control)
+ const receiverByAccount = new Map(receivers.map((r) => [r.account, r]))
+ for (const n of all) {
+ if (hasEmailProvider(n, availableProviders)) continue
+ const type = (n.types ?? [])[0]
+ if (type === undefined) {
+ control.ctx.info('NotificationsHandler: skipping notification without type', { notificationId: n._id })
+ continue
+ }
+ const notificationType = control.modelDb.getObject(type)
+ const receiver = receiverByAccount.get(n.user)
+ if (receiver === undefined) {
+ control.ctx.info('NotificationsHandler: no receiver info for user', {
+ notificationId: n._id,
+ type,
+ reason: 'user not in getReceiversInfo result'
+ })
+ continue
+ }
+ const allowedProviders = getAllowedProviders(control, receiver.socialIds, notificationType, notificationControl)
+ if (allowedProviders.includes(gmail.providers.EmailNotificationProvider)) {
+ notificationsWithEmail.push(n)
+ } else {
+ control.ctx.info('NotificationsHandler: email provider not enabled for notification type', {
+ notificationId: n._id,
+ type
+ })
+ }
+ }
+ }
+
+ if (notificationsWithEmail.length === 0) {
+ control.ctx.info('NotificationsHandler: no notifications with email provider, skipping')
return []
}
- const notificationsByDocId = groupByArray(all, (n) => n.objectId)
+ const notificationsByDocId = groupByArray(notificationsWithEmail, (n) => n.objectId)
await Promise.all(
Array.from(notificationsByDocId.entries()).map(([docId, notifications]) =>
diff --git a/server-plugins/notification-resources/src/index.ts b/server-plugins/notification-resources/src/index.ts
index f79922f42d..4a29f17781 100644
--- a/server-plugins/notification-resources/src/index.ts
+++ b/server-plugins/notification-resources/src/index.ts
@@ -183,13 +183,22 @@ export async function getContentByTemplate (
const notificationType = control.modelDb.getObject(type)
if (notificationType.templates === undefined) return
- const textPart = await getTextPart(doc, control)
- if (textPart === undefined) return
const params: Record =
notificationData !== undefined
? await getTranslatedNotificationContent(notificationData, notificationData._class, control)
: {}
+ let textPart = await getTextPart(doc, control)
+ if (textPart === undefined) {
+ if (
+ notificationData !== undefined &&
+ control.hierarchy.isDerived(notificationData._class, notification.class.CommonInboxNotification)
+ ) {
+ textPart = params.title ?? params.body ?? ''
+ }
+ if (textPart === undefined || textPart === '') return
+ }
+
if (
notificationData !== undefined &&
control.hierarchy.isDerived(notificationData._class, notification.class.MentionInboxNotification)
diff --git a/server/server-pipeline/package.json b/server/server-pipeline/package.json
index 879cc60a3f..574db66844 100644
--- a/server/server-pipeline/package.json
+++ b/server/server-pipeline/package.json
@@ -127,6 +127,8 @@
"@hcengineering/hr-assets": "workspace:^0.7.0",
"@hcengineering/request-assets": "workspace:^0.7.0",
"@hcengineering/document-assets": "workspace:^0.7.0",
+ "@hcengineering/export": "workspace:^0.7.0",
+ "@hcengineering/export-assets": "workspace:^0.7.0",
"@hcengineering/controlled-documents-assets": "workspace:^0.7.0",
"@hcengineering/products-assets": "workspace:^0.7.0",
"@hcengineering/training-assets": "workspace:^0.7.0",
diff --git a/server/server-pipeline/src/internationalization.ts b/server/server-pipeline/src/internationalization.ts
index eb44651f5c..e24082a7c4 100644
--- a/server/server-pipeline/src/internationalization.ts
+++ b/server/server-pipeline/src/internationalization.ts
@@ -8,6 +8,7 @@ import { chunterId } from '@hcengineering/chunter'
import { contactId } from '@hcengineering/contact'
import { documentsId } from '@hcengineering/controlled-documents'
import { documentId } from '@hcengineering/document'
+import { exportId } from '@hcengineering/export'
import { driveId } from '@hcengineering/drive'
import { githubId } from '@hcengineering/github'
import { gmailId } from '@hcengineering/gmail'
@@ -50,6 +51,7 @@ import chunterEn from '@hcengineering/chunter-assets/lang/en.json'
import contactEn from '@hcengineering/contact-assets/lang/en.json'
import documentsEn from '@hcengineering/controlled-documents-assets/lang/en.json'
import documentEn from '@hcengineering/document-assets/lang/en.json'
+import exportEn from '@hcengineering/export-assets/lang/en.json'
import driveEn from '@hcengineering/drive-assets/lang/en.json'
import githubEn from '@hcengineering/github-assets/lang/en.json'
import gmailEn from '@hcengineering/gmail-assets/lang/en.json'
@@ -109,6 +111,7 @@ export function registerStringLoaders (): void {
addStringsLoader(preferenceId, async (lang: string) => preferenceEn)
addStringsLoader(hrId, async (lang: string) => hrEn)
addStringsLoader(documentId, async (lang: string) => documentEn)
+ addStringsLoader(exportId, async (lang: string) => exportEn)
addStringsLoader(requestId, async (lang: string) => requestEn)
addStringsLoader(loveId, async (lang: string) => loveEn)
addStringsLoader(driveId, async (lang: string) => driveEn)
diff --git a/services/export/pod-export/src/notifications.ts b/services/export/pod-export/src/notifications.ts
index be49c23d64..ff378253b6 100644
--- a/services/export/pod-export/src/notifications.ts
+++ b/services/export/pod-export/src/notifications.ts
@@ -85,6 +85,7 @@ export async function sendExportCompletionNotification (
objectId: resultId,
objectClass: exportPlugin.class.ExportResultRecord,
icon: exportPlugin.icon.Export,
+ header: exportPlugin.string.ImportCompleted,
message: exportPlugin.string.ImportToWorkspaceNotificationMessage,
props: {
count,