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 common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion models/export/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '<p>{body}</p><p>{link}</p>',
subjectTemplate: '{title}'
}
},
exportPlugin.ids.ImportedDocumentsNotification
)
Expand Down
109 changes: 95 additions & 14 deletions server-plugins/gmail-resources/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -156,7 +159,7 @@ async function notifyByEmail (
senderSocialId: PersonId,
email: string,
data: InboxNotification,
message: ActivityMessage
message?: ActivityMessage
): Promise<void> {
let senderName = sender !== undefined ? formatName(sender.name, control.branding?.lastNameFirst) : ''
if (senderName === '' && senderSocialId === core.account.System) {
Expand All @@ -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
})
}
}

Expand Down Expand Up @@ -197,15 +206,21 @@ 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

const senders = new Map<PersonId, Person>()

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
Expand All @@ -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] },
Expand Down Expand Up @@ -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<InboxNotification>)
return providers?.find((p) => p === gmail.providers.EmailNotificationProvider) !== undefined
}

async function NotificationsHandler (txes: TxCreateDoc<InboxNotification>[], control: TriggerControl): Promise<Tx[]> {
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))

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 (all.length === 0) {
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]) =>
Expand Down
13 changes: 11 additions & 2 deletions server-plugins/notification-resources/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> =
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)
Expand Down
2 changes: 2 additions & 0 deletions server/server-pipeline/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions server/server-pipeline/src/internationalization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions services/export/pod-export/src/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading