From af1563f580f7d39e4ad2b5f4795a903efa3711db Mon Sep 17 00:00:00 2001 From: Kristina Date: Wed, 26 Mar 2025 22:20:20 +0400 Subject: [PATCH] Reduce finds on create notifications (#8352) Signed-off-by: Kristina Fefelova --- .../activity-resources/src/index.ts | 3 +- .../activity-resources/src/references.ts | 95 +++++----- server-plugins/chunter-resources/src/index.ts | 4 +- server-plugins/contact/src/utils.ts | 10 + .../src/index.ts | 11 +- server-plugins/gmail-resources/src/index.ts | 2 +- .../notification-resources/src/index.ts | 155 +++++----------- .../notification-resources/src/utils.ts | 173 ++++++++++-------- server-plugins/notification/src/index.ts | 17 +- server-plugins/request-resources/src/index.ts | 32 +--- .../telegram-resources/src/index.ts | 2 +- server-plugins/time-resources/src/index.ts | 43 ++--- server-plugins/tracker-resources/src/index.ts | 11 +- .../TrainingRequestNotificationTypeMatch.ts | 6 +- 14 files changed, 243 insertions(+), 321 deletions(-) diff --git a/server-plugins/activity-resources/src/index.ts b/server-plugins/activity-resources/src/index.ts index ee1a9b503b..0f29f802fc 100644 --- a/server-plugins/activity-resources/src/index.ts +++ b/server-plugins/activity-resources/src/index.ts @@ -48,6 +48,7 @@ import { getTextPresenter, removeDocInboxNotifications } from '@hcengineering/server-notification-resources' +import { Person } from '@hcengineering/contact' import { ReferenceTrigger } from './references' import { getAttrName, getCollectionAttribute, getDocUpdateAction, getTxAttributesUpdates } from './utils' @@ -417,7 +418,7 @@ async function OnDocRemoved (txes: TxCUD[], control: TriggerControl): Promi async function ReactionNotificationContentProvider ( doc: ActivityMessage, originTx: TxCUD, - _: PersonId, + _: Ref, control: TriggerControl ): Promise { const tx = originTx as TxCreateDoc diff --git a/server-plugins/activity-resources/src/references.ts b/server-plugins/activity-resources/src/references.ts index 9625222d05..5cf0c83ab4 100644 --- a/server-plugins/activity-resources/src/references.ts +++ b/server-plugins/activity-resources/src/references.ts @@ -14,7 +14,7 @@ // import activity, { ActivityMessage, ActivityReference, UserMentionInfo } from '@hcengineering/activity' -import contact, { Employee, Person, pickPrimarySocialId } from '@hcengineering/contact' +import contact, { Employee, Person } from '@hcengineering/contact' import core, { PersonId, Blob, @@ -47,11 +47,10 @@ import { getNotificationContent, getNotificationProviderControl, getPushCollaboratorTx, - isShouldNotifyTx, NotifyResult, shouldNotifyCommon, - toReceiverInfo, - type NotificationProviderControl + type NotificationProviderControl, + isShouldNotifyTx } from '@hcengineering/server-notification-resources' import { areEqualJson, extractReferences, jsonToMarkup, markupToJSON } from '@hcengineering/text-core' @@ -79,27 +78,31 @@ export async function getPersonNotificationTxes ( originTx: TxCUD, notificationControl: NotificationProviderControl ): Promise { - const receiver = reference.attachedTo as Ref - const receiverSocialIds = await control.findAll(ctx, contact.class.SocialIdentity, { attachedTo: receiver }) - const receiverSocialStrings = receiverSocialIds.map((si) => si._id) as PersonId[] + const receiverPersonRef = reference.attachedTo as Ref + const receiverSocialIdentity = await control.findAll(ctx, contact.class.SocialIdentity, { + attachedTo: receiverPersonRef + }) + const receiverSocialIds = receiverSocialIdentity.map((si) => si._id) as PersonId[] - if (receiverSocialStrings.includes(senderId)) { + if (receiverSocialIds.includes(senderId)) { return [] } - const employee = await control.findAll( - ctx, - contact.mixin.Employee, - { _id: receiver as Ref, active: true }, - { limit: 1 } - ) - const account = employee[0]?.personUuid - if (account == null) { + const receiverEmployee = ( + await control.findAll( + ctx, + contact.mixin.Employee, + { _id: receiverPersonRef as Ref, active: true }, + { limit: 1 } + ) + )[0] + const receiverAccount = receiverEmployee?.personUuid + if (receiverAccount == null) { return [] } const res: Tx[] = [] - const isAvailable = await checkSpace(account, space, control, res) + const isAvailable = await checkSpace(receiverAccount, space, control, res) if (!isAvailable) { return [] @@ -107,10 +110,12 @@ export async function getPersonNotificationTxes ( const doc = (await control.findAll(ctx, reference.srcDocClass, { _id: reference.srcDocId }))[0] - const receiverSpace = (await control.findAll(ctx, contact.class.PersonSpace, { person: receiver }, { limit: 1 }))[0] + const receiverSpace = ( + await control.findAll(ctx, contact.class.PersonSpace, { person: receiverPersonRef }, { limit: 1 }) + )[0] if (receiverSpace === undefined) return res - const collaboratorsTx = await getCollaboratorsTxes(reference, control, account, doc) + const collaboratorsTx = await getCollaboratorsTxes(reference, control, receiverAccount, doc) res.push(...collaboratorsTx) @@ -120,7 +125,7 @@ export async function getPersonNotificationTxes ( const info = ( await control.findAll(ctx, activity.class.UserMentionInfo, { - user: receiver, + user: receiverPersonRef, attachedTo: reference.attachedDocId }) )[0] @@ -130,7 +135,7 @@ export async function getPersonNotificationTxes ( control.txFactory.createTxCreateDoc(activity.class.UserMentionInfo, space, { attachedTo: reference.attachedDocId ?? reference.srcDocId, attachedToClass: reference.attachedDocClass ?? reference.srcDocClass, - user: receiver, + user: receiverPersonRef, content: reference.message, collection: 'mentions' }) @@ -150,42 +155,35 @@ export async function getPersonNotificationTxes ( mentionedInClass: reference.attachedDocClass ?? reference.srcDocClass, objectId: reference.srcDocId, objectClass: reference.srcDocClass, - user: account, + user: receiverAccount, isViewed: false, archived: false } const senderPerson = await getPerson(control, senderId) - const senderSocialIds = - senderPerson !== undefined - ? await control.findAll(ctx, contact.class.SocialIdentity, { attachedTo: senderPerson._id }) - : [] - const receiverSocialString = pickPrimarySocialId(receiverSocialStrings) - const receiverInfo = toReceiverInfo(control.hierarchy, { - _id: receiverSocialString, - person: employee[0], + const receiver = { + account: receiverAccount, + socialIds: receiverSocialIds, space: receiverSpace._id, - socialStrings: receiverSocialStrings - }) - if (receiverInfo === undefined) return res - - const senderInfo = { - _id: senderId, - person: senderPerson, - socialStrings: senderSocialIds.map((si) => si._id) + employee: receiverEmployee._id + } + const sender = { + socialId: senderId, + person: senderPerson } const notifyResult = await shouldNotifyCommon( control, - receiverSocialStrings, + receiverSocialIds, notification.ids.MentionCommonNotificationType, notificationControl ) const messageNotifyResult = await getMessageNotifyResult( reference, - account, - receiverSocialStrings, + receiverAccount, + receiverEmployee, + receiverSocialIds, control, originTx, doc, @@ -204,8 +202,8 @@ export async function getPersonNotificationTxes ( control, doc, data, - receiverInfo, - senderInfo, + receiver, + sender, reference.srcDocId, reference.srcDocClass, doc.space, @@ -220,12 +218,12 @@ export async function getPersonNotificationTxes ( await control.findAll( ctx, notification.class.DocNotifyContext, - { objectId: reference.srcDocId, user: account }, + { objectId: reference.srcDocId, user: receiverAccount }, { projection: { _id: 1 } } ) )[0] if (context !== undefined) { - const content = await getNotificationContent(originTx, receiverSocialStrings, senderInfo, doc, control) + const content = await getNotificationContent(originTx, receiverPersonRef, sender, doc, control) const notificationData: CommonInboxNotification = { ...data, ...content, @@ -246,8 +244,8 @@ export async function getPersonNotificationTxes ( control, res, doc, - receiverInfo, - senderInfo, + receiver, + sender, notification.class.MentionInboxNotification, msg as ActivityMessage ) @@ -331,6 +329,7 @@ async function getCollaboratorsTxes ( async function getMessageNotifyResult ( reference: Data, account: AccountUuid, + person: Person, personIds: PersonId[], control: TriggerControl, tx: TxCUD, @@ -357,7 +356,7 @@ async function getMessageNotifyResult ( return new Map() } - return await isShouldNotifyTx(control, tx, doc, personIds, false, false, notificationControl, undefined) + return await isShouldNotifyTx(control, tx, doc, person._id, personIds, false, false, notificationControl, undefined) } function isMarkupType (type: Ref>>): boolean { diff --git a/server-plugins/chunter-resources/src/index.ts b/server-plugins/chunter-resources/src/index.ts index 4d13981b97..26b9bb4ea7 100644 --- a/server-plugins/chunter-resources/src/index.ts +++ b/server-plugins/chunter-resources/src/index.ts @@ -307,7 +307,7 @@ export async function ChunterTrigger (txes: TxCUD[], control: TriggerContro export async function getChunterNotificationContent ( _: Doc, tx: TxCUD, - target: PersonId, + target: Ref, control: TriggerControl ): Promise { let title: IntlString = notification.string.CommonNotificationTitle @@ -494,7 +494,7 @@ async function OnUserStatus (txes: TxCUD[], control: TriggerControl) return [] } -function JoinChannelTypeMatch (originTx: Tx, _: Doc, person: Person, user: PersonId[]): boolean { +function JoinChannelTypeMatch (originTx: Tx, _: Doc, person: Ref, user: PersonId[]): boolean { if (user.includes(originTx.modifiedBy)) return false if (originTx._class !== core.class.TxUpdateDoc) return false diff --git a/server-plugins/contact/src/utils.ts b/server-plugins/contact/src/utils.ts index e4d169be42..f89ffaa754 100644 --- a/server-plugins/contact/src/utils.ts +++ b/server-plugins/contact/src/utils.ts @@ -202,6 +202,16 @@ export async function getPrimarySocialIdsByAccounts ( } export async function getAccountBySocialId (control: TriggerControl, socialId: PersonId): Promise { + const contextAccount = control.ctx.contextData.socialStringsToUsers.get(socialId) + if (contextAccount != null) { + return contextAccount + } + + const controlAccount = control.ctx.contextData.account + if (controlAccount.socialIds.includes(socialId)) { + return controlAccount.uuid + } + const socialIdentity = await control.findAll( control.ctx, contact.class.SocialIdentity, diff --git a/server-plugins/controlled-documents-resources/src/index.ts b/server-plugins/controlled-documents-resources/src/index.ts index 2fb3a9b996..8f48d284be 100644 --- a/server-plugins/controlled-documents-resources/src/index.ts +++ b/server-plugins/controlled-documents-resources/src/index.ts @@ -6,7 +6,6 @@ import core, { AccountRole, combineAttributes, DocumentQuery, - includesAny, PersonId, Ref, SortingOrder, @@ -21,7 +20,7 @@ import core, { systemAccountUuid } from '@hcengineering/core' import { NotificationType } from '@hcengineering/notification' -import { getEmployees, getSocialStrings, getSocialStringsByPersons } from '@hcengineering/server-contact' +import { getEmployees, getSocialStrings } from '@hcengineering/server-contact' import { TriggerControl } from '@hcengineering/server-core' import documents, { @@ -391,7 +390,7 @@ export async function documentTextPresenter (doc: ControlledDocument): Promise, _doc: Doc, - person: Person, + person: Ref, socialIds: PersonId[], _type: NotificationType, control: TriggerControl @@ -402,15 +401,13 @@ async function CoAuthorsTypeMatch ( const employees = Array.isArray(tx.operations.coAuthors) ? tx.operations.coAuthors ?? [] : (combineAttributes([tx.operations], 'coAuthors', '$push', '$each') as Ref[]) - const employeeSocialStrings = Object.values(await getSocialStringsByPersons(control, employees)).flat() - return includesAny(socialIds, employeeSocialStrings) + return employees.some((it) => it === person) } else if (originTx._class === core.class.TxCreateDoc) { const tx = originTx as TxCreateDoc const employees = tx.attributes.coAuthors - const employeeSocialStrings = Object.values(await getSocialStringsByPersons(control, employees)).flat() - return includesAny(socialIds, employeeSocialStrings) + return employees.some((it) => it === person) } return false diff --git a/server-plugins/gmail-resources/src/index.ts b/server-plugins/gmail-resources/src/index.ts index d3b7eeddf9..49f92f808a 100644 --- a/server-plugins/gmail-resources/src/index.ts +++ b/server-plugins/gmail-resources/src/index.ts @@ -95,7 +95,7 @@ export async function OnMessageCreate (txes: Tx[], control: TriggerControl): Pro export function IsIncomingMessageTypeMatch ( tx: Tx, doc: Doc, - person: Person, + person: Ref, user: PersonId[], type: NotificationType, control: TriggerControl diff --git a/server-plugins/notification-resources/src/index.ts b/server-plugins/notification-resources/src/index.ts index badf482b51..bc4d433cfd 100644 --- a/server-plugins/notification-resources/src/index.ts +++ b/server-plugins/notification-resources/src/index.ts @@ -28,7 +28,6 @@ import core, { Data, Doc, DocumentUpdate, - generateId, MeasureContext, MixinUpdate, Ref, @@ -36,7 +35,6 @@ import core, { SortingOrder, Space, Timestamp, - toIdMap, Tx, TxCreateDoc, TxCUD, @@ -45,12 +43,13 @@ import core, { TxRemoveDoc, TxUpdateDoc, AccountUuid, - notEmpty + notEmpty, + generateId, + toIdMap } from '@hcengineering/core' import notification, { ActivityInboxNotification, BaseNotificationType, - BrowserNotification, ClassCollaborators, Collaborators, CommonInboxNotification, @@ -61,13 +60,6 @@ import notification, { } from '@hcengineering/notification' import { getResource, translate } from '@hcengineering/platform' import { type TriggerControl } from '@hcengineering/server-core' -import { - getEmployeeByAcc, - getPrimarySocialIdsByAccounts, - getAccountBySocialId, - getSocialIdsByAccounts, - getEmployeesBySocialIds -} from '@hcengineering/server-contact' import serverNotification, { NOTIFICATION_BODY_SIZE, ReceiverInfo, @@ -75,6 +67,7 @@ import serverNotification, { } from '@hcengineering/server-notification' import { markupToText, stripTags } from '@hcengineering/text-core' import { Analytics } from '@hcengineering/analytics' +import { getAccountBySocialId, getEmployeesBySocialIds } from '@hcengineering/server-contact' import { AvailableProvidersCache, @@ -89,22 +82,21 @@ import { createPullCollaboratorsTx, createPushCollaboratorsTx, getHTMLPresenter, - getNotificationContent, getNotificationLink, getNotificationProviderControl, getObjectSpace, getTextPresenter, - getUsersInfo, + getReceiversInfo, isAllowed, isMixinTx, isShouldNotifyTx, isUserEmployeeInFieldValueTypeMatch, - isUserInFieldValueTypeMatch, messageToMarkup, replaceAll, - toReceiverInfo, updateNotifyContextsSpace, - type NotificationProviderControl + type NotificationProviderControl, + getNotificationContent, + getSenderInfo } from './utils' import { PushNotificationsHandler } from './push' @@ -381,7 +373,7 @@ export async function pushInboxNotifications ( objectClass, objectSpace, receiver, - sender._id, + sender.socialId, shouldUpdateTimestamp ? modifiedOn : undefined, tx ) @@ -517,7 +509,7 @@ export async function pushActivityInboxNotifications ( activityMessage: ActivityMessage, shouldUpdateTimestamp: boolean ): Promise | undefined> { - const content = await getNotificationContent(originTx, receiver.socialStrings, sender, object, control) + const content = await getNotificationContent(originTx, receiver.employee, sender, object, control) const data: Partial> = { ...content, attachedTo: activityMessage._id, @@ -582,7 +574,7 @@ async function createNotifyContext ( const contextsCache: ContextsCache = control.cache.get(ContextsCacheKey) ?? { contexts: new Map>() } - const cacheKey = `${objectId}_${receiver._id}` + const cacheKey = `${objectId}_${receiver.account}` const cachedId = contextsCache.contexts.get(cacheKey) if (cachedId !== undefined) { @@ -602,18 +594,15 @@ async function createNotifyContext ( hidden: false, tx: tx?._id, lastUpdateTimestamp: updateTimestamp, - lastViewedTimestamp: sender === receiver._id ? updateTimestamp : undefined + lastViewedTimestamp: receiver.socialIds.some((it) => it === sender) ? updateTimestamp : undefined }) contextsCache.contexts.set(cacheKey, createTx.objectId) control.cache.set(ContextsCacheKey, contextsCache) await ctx.with('apply', {}, () => control.apply(control.ctx, [createTx])) - const personUuid = receiver.person?.personUuid - if (personUuid !== undefined) { - control.ctx.contextData.broadcast.targets['docNotifyContext' + createTx._id] = (it) => { - if (it._id === createTx._id) { - return [personUuid] - } + control.ctx.contextData.broadcast.targets['docNotifyContext' + createTx._id] = (it) => { + if (it._id === createTx._id) { + return [receiver.account] } } return createTx.objectId @@ -631,10 +620,6 @@ export async function getNotificationTxes ( activityMessages: ActivityMessage[], settings: NotificationProviderControl ): Promise { - if (receiver.employee === undefined) { - return [] - } - const res: Tx[] = [] for (const message of activityMessages) { @@ -643,7 +628,8 @@ export async function getNotificationTxes ( control, tx, object, - receiver.socialStrings, + receiver.employee, + receiver.socialIds, params.isOwn, params.isSpace, settings, @@ -699,7 +685,7 @@ export async function getNotificationTxes ( message.attachedToClass, object.space, receiver, - sender._id, + sender.socialId, params.shouldUpdateTimestamp ? tx.modifiedOn : undefined, tx ) @@ -718,19 +704,15 @@ async function updateContextsTimestamp ( ): Promise { if (contexts.length === 0) return const res: Tx[] = [] - const socialIdsByAccounts = await getSocialIdsByAccounts( - control, - contexts.map((it) => it.user) - ) + const modifiedByAccount = await getAccountBySocialId(control, modifiedBy) for (const context of contexts) { const isViewed = context.lastViewedTimestamp !== undefined && (context.lastUpdateTimestamp ?? 0) <= context.lastViewedTimestamp - const ctxUserSocialIds = socialIdsByAccounts[context.user] ?? [] const updateTx = control.txFactory.createTxUpdateDoc(context._class, context.space, context._id, { hidden: false, lastUpdateTimestamp: timestamp, - ...(isViewed && ctxUserSocialIds.includes(modifiedBy) + ...(isViewed && context.user === modifiedByAccount ? { lastViewedTimestamp: timestamp } @@ -867,29 +849,18 @@ export async function createCollabDocInfo ( if (targets.size === 0) { return res } - const targetPrimarySocialStringsByAccounts = await getPrimarySocialIdsByAccounts(control, Array.from(targets)) - - const usersInfo = await ctx.with('get-user-info', {}, (ctx) => - getUsersInfo(ctx, [...Object.values(targetPrimarySocialStringsByAccounts), tx.modifiedBy], control) - ) - const sender: SenderInfo = usersInfo.get(tx.modifiedBy) ?? { - _id: tx.modifiedBy, - socialStrings: [] - } + const receivers = await getReceiversInfo(ctx, Array.from(targets), control) + const sender: SenderInfo = await getSenderInfo(ctx, tx.modifiedBy, control) const settings = await getNotificationProviderControl(ctx, control) - for (const target of targets) { - const targetSocialString = targetPrimarySocialStringsByAccounts[target] - const info: ReceiverInfo | undefined = toReceiverInfo(control.hierarchy, usersInfo.get(targetSocialString)) - - if (info === undefined) continue + for (const receiver of receivers) { const targetRes = await getNotificationTxes( ctx, control, object, tx, - info, + receiver, sender, params, notifyContexts, @@ -897,13 +868,10 @@ export async function createCollabDocInfo ( settings ) const ids = new Set(targetRes.map((it) => it._id)) - const { personUuid } = info.person - if (personUuid !== undefined) { - const id = generateId() as string - control.ctx.contextData.broadcast.targets[id] = (it) => { - if (ids.has(it._id)) { - return [personUuid] - } + const id = generateId() as string + control.ctx.contextData.broadcast.targets[id] = (it) => { + if (ids.has(it._id)) { + return [receiver.account] } } res = res.concat(targetRes) @@ -1141,13 +1109,15 @@ async function updateCollaboratorsMixin ( } const providers = await control.modelDb.findAll(notification.class.NotificationProvider, {}) - const modifiedByAccount = await getAccountBySocialId(control, tx.modifiedBy) - const socialIdsByAccounts = await getSocialIdsByAccounts(control, tx.attributes.collaborators) + const sender: SenderInfo = await getSenderInfo(ctx, tx.modifiedBy, control) + const receivers = await getReceiversInfo(ctx, tx.attributes.collaborators, control) for (const collab of tx.attributes.collaborators) { - if (!prevCollabs.has(collab) && modifiedByAccount !== collab) { + const info = receivers.find((it) => it.account === collab) + if (info === undefined) continue + if (!prevCollabs.has(collab) && sender.person?.personUuid !== collab) { for (const provider of providers) { - if (isAllowed(control, socialIdsByAccounts[collab], type, provider, notificationControl)) { + if (isAllowed(control, info.socialIds, type, provider, notificationControl)) { newCollabs.push(collab) break } @@ -1167,23 +1137,17 @@ async function updateCollaboratorsMixin ( user: { $in: newCollabs }, objectId: tx.objectId }) - const newCollabsPrimarySocialStringsByAccounts = await getPrimarySocialIdsByAccounts(control, newCollabs) - const infos = await ctx.with('get-user-info', {}, (ctx) => - getUsersInfo(ctx, [...Object.values(newCollabsPrimarySocialStringsByAccounts), originTx.modifiedBy], control) - ) - const sender: SenderInfo = infos.get(originTx.modifiedBy) ?? { _id: originTx.modifiedBy, socialStrings: [] } + const infos = receivers.filter((it) => newCollabs.includes(it.account)) - for (const collab of newCollabs) { - const target = toReceiverInfo(hierarchy, infos.get(newCollabsPrimarySocialStringsByAccounts[collab])) - if (target === undefined) continue - const isMember = space.members.includes(collab) + for (const target of infos) { + const isMember = space.members.includes(target.account) if (space.private && !isMember) continue if (!hierarchy.isDerived(space._class, core.class.SystemSpace) && !isMember) { res.push( control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, { - $push: { members: collab } + $push: { members: target.account } }) ) } @@ -1536,12 +1500,7 @@ export async function OnAttributeUpdate (txes: Tx[], control: TriggerControl): P return result } -async function applyUserTxes ( - ctx: MeasureContext, - control: TriggerControl, - txes: Tx[], - cache: Map = new Map() -): Promise { +async function applyUserTxes (ctx: MeasureContext, control: TriggerControl, txes: Tx[]): Promise { const map: Map = new Map() const res: Tx[] = [] @@ -1553,17 +1512,6 @@ async function applyUserTxes ( ) { const notification = TxProcessor.createDoc2Doc(ttx as TxCreateDoc) - if (map.has(notification.user)) { - map.get(notification.user)?.push(tx) - } else { - map.set(notification.user, [tx]) - } - } else if ( - control.hierarchy.isDerived(ttx.objectClass, notification.class.BrowserNotification) && - ttx._class === core.class.TxCreateDoc - ) { - const notification = TxProcessor.createDoc2Doc(ttx as TxCreateDoc) - if (map.has(notification.user)) { map.get(notification.user)?.push(tx) } else { @@ -1575,18 +1523,11 @@ async function applyUserTxes ( } for (const [user, txs] of map.entries()) { - const person = (cache.get(user) as Person) ?? (await getEmployeeByAcc(control, user)) - const personUuid = person?.personUuid - - if (personUuid !== undefined) { - cache.set(user, person) - await control.apply(ctx, txs) - - const m1 = toIdMap(txs) - control.ctx.contextData.broadcast.targets.docNotifyContext = (it) => { - if (m1.has(it._id)) { - return [personUuid] - } + await control.apply(ctx, txs) + const m1 = toIdMap(txs) + control.ctx.contextData.broadcast.targets.docNotifyContext = (it) => { + if (m1.has(it._id)) { + return [user] } } } @@ -1640,12 +1581,9 @@ async function updateCollaborators ( if (hierarchy.classHierarchyMixin(objectClass, activity.mixin.ActivityDoc) === undefined) return res const contexts = await control.findAll(control.ctx, notification.class.DocNotifyContext, { objectId }) - const toAddPrimarySocialStringsByAccounts = await getPrimarySocialIdsByAccounts(control, toAdd) - const addedInfo = await getUsersInfo(ctx, Object.values(toAddPrimarySocialStringsByAccounts), control) + const addedInfo = await getReceiversInfo(ctx, toAdd, control) - for (const addedUser of addedInfo.values()) { - const info = toReceiverInfo(hierarchy, addedUser) - if (info === undefined) continue + for (const info of addedInfo.values()) { const context = getDocNotifyContext(control, contexts, objectId, info.account) if (context !== undefined) { if (context.hidden) { @@ -1884,7 +1822,6 @@ export default async () => ({ PushNotificationsHandler }, function: { - IsUserInFieldValueTypeMatch: isUserInFieldValueTypeMatch, IsUserEmployeeInFieldValueTypeMatch: isUserEmployeeInFieldValueTypeMatch } }) diff --git a/server-plugins/notification-resources/src/utils.ts b/server-plugins/notification-resources/src/utils.ts index 3ac8cf486a..7390514214 100644 --- a/server-plugins/notification-resources/src/utils.ts +++ b/server-plugins/notification-resources/src/utils.ts @@ -15,9 +15,17 @@ import activity, { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity' import { Analytics } from '@hcengineering/analytics' import chunter, { ChatMessage } from '@hcengineering/chunter' -import contact, { Employee, formatName, includesAny, Person } from '@hcengineering/contact' +import contact, { + Employee, + formatName, + includesAny, + Person, + PersonSpace, + SocialIdentity, + SocialIdentityRef +} from '@hcengineering/contact' import core, { - PersonId, + AccountUuid, Class, concatLink, Doc, @@ -26,15 +34,16 @@ import core, { Hierarchy, Markup, matchQuery, + type MeasureContext, MixinUpdate, + notEmpty, + PersonId, Ref, Space, Tx, TxCUD, TxMixin, - TxUpdateDoc, - type MeasureContext, - AccountUuid + TxUpdateDoc } from '@hcengineering/core' import notification, { BaseNotificationType, @@ -43,18 +52,12 @@ import notification, { NotificationContent, notificationId, NotificationProvider, - NotificationType, type NotificationProviderSetting, + NotificationType, type NotificationTypeSetting } from '@hcengineering/notification' import { getMetadata, getResource, IntlString, translate } from '@hcengineering/platform' import serverCore, { TriggerControl } from '@hcengineering/server-core' -import { - getPersonsBySocialIds, - getEmployeesBySocialIds, - getSocialStringsByPersons, - getPerson -} from '@hcengineering/server-contact' import serverNotification, { HTMLPresenter, NotificationPresenter, @@ -74,7 +77,7 @@ import { NotifyResult } from './types' export function isUserEmployeeInFieldValueTypeMatch ( _: Tx, doc: Doc, - person: Person, + person: Ref, socialIds: PersonId[], type: NotificationType, control: TriggerControl @@ -145,13 +148,13 @@ export async function shouldNotifyCommon ( export function isAllowed ( control: TriggerControl, - receiver: PersonId[], + receiverIds: PersonId[], type: BaseNotificationType, provider: NotificationProvider, notificationControl: NotificationProviderControl ): boolean { const providerSettings = (notificationControl.byProvider.get(provider._id) ?? []).filter(({ createdBy }) => - createdBy !== undefined ? receiver.includes(createdBy) : false + createdBy !== undefined ? receiverIds.includes(createdBy) : false ) if (providerSettings.length > 0 && providerSettings.every((s) => !s.enabled)) { @@ -169,7 +172,7 @@ export function isAllowed ( } const setting = (notificationControl.settingsByProvider.get(provider._id) ?? []).find( - (it) => it.type === type._id && it.createdBy !== undefined && receiver.includes(it.createdBy) + (it) => it.type === type._id && it.createdBy !== undefined && receiverIds.includes(it.createdBy) ) if (setting !== undefined) { @@ -189,6 +192,7 @@ export async function isShouldNotifyTx ( control: TriggerControl, tx: TxCUD, object: Doc, + person: Ref, personIds: PersonId[], isOwn: boolean, isSpace: boolean, @@ -213,8 +217,6 @@ export async function isShouldNotifyTx ( const mixin = control.hierarchy.as(type, serverNotification.mixin.TypeMatch) if (mixin.func !== undefined) { const f = await getResource(mixin.func) - const person = await getPerson(control, personIds[0]) - if (person === undefined) continue let res = f(tx, object, person, personIds, type, control) if (res instanceof Promise) { res = await res @@ -324,15 +326,15 @@ export function getTextPresenter (_class: Ref>, hierarchy: Hierarchy) } async function getSenderName (control: TriggerControl, sender: SenderInfo): Promise { - if (sender._id === core.account.System || sender._id === core.account.ConfigUser) { + if (sender.socialId === core.account.System || sender.socialId === core.account.ConfigUser) { return await translate(core.string.System, {}) } const { person } = sender if (person === undefined) { - console.error('Cannot find person', { accountId: sender._id }) - Analytics.handleError(new Error(`Cannot find person ${sender._id}`)) + console.error('Cannot find person', { socialId: sender.socialId }) + Analytics.handleError(new Error(`Cannot find person ${sender.socialId}`)) return '' } @@ -407,7 +409,7 @@ function getNotificationPresenter (_class: Ref>, hierarchy: Hierarchy export async function getNotificationContent ( originTx: TxCUD, - socialIds: PersonId[], + receiver: Ref, sender: SenderInfo, object: Doc, control: TriggerControl @@ -425,7 +427,7 @@ export async function getNotificationContent ( if (notificationPresenter !== undefined) { const getFuillfillmentParams = await getResource(notificationPresenter.presenter) - const updateParams = await getFuillfillmentParams(object, originTx, socialIds[0], control) + const updateParams = await getFuillfillmentParams(object, originTx, receiver, control) title = updateParams.title body = updateParams.body data = updateParams.data @@ -455,74 +457,93 @@ export async function getNotificationContent ( return content } -export async function getUsersInfo ( +export async function getReceiversInfo ( ctx: MeasureContext, - ids: PersonId[], + accounts: AccountUuid[], control: TriggerControl -): Promise> { - if (ids.length === 0) return new Map() - const uniqueIds = Array.from(new Set(ids)) +): Promise { + if (accounts.length === 0) return [] - const employeesBySocialId = await getEmployeesBySocialIds(control, uniqueIds) - const presentEmployeeIds = Object.values(employeesBySocialId) - .map((it) => it?._id) - .filter((it) => it !== undefined) - const missingSocialIds = Object.entries(employeesBySocialId) - .filter(([, employee]) => employee === undefined) - .map(([id]) => id as PersonId) - const personsBySocialId = await getPersonsBySocialIds(control, missingSocialIds) - - const employeesIds = new Set(presentEmployeeIds) - const spaces = (await control.findAll(ctx, contact.class.PersonSpace, {})).filter((it) => - employeesIds.has(it.person as Ref) + const employees: Pick[] = await control.findAll( + ctx, + contact.mixin.Employee, + { personUuid: { $in: accounts }, active: true }, + { projection: { _id: 1, personUuid: 1 } } ) - const spacesByEmployee = groupByArray(spaces, (it) => it.person) + if (employees.length === 0) return [] - const persons = [...presentEmployeeIds, ...Object.values(personsBySocialId).map((it) => it._id)] + const spaces: Pick[] = await control.queryFind( + ctx, + contact.class.PersonSpace, + {}, + { projection: { _id: 1, person: 1 } } + ) + if (spaces.length === 0) return [] - const socialStringsByPersons = await getSocialStringsByPersons(control, persons as Ref[]) + const socialIds: Pick[] = await control.findAll( + ctx, + contact.class.SocialIdentity, + { attachedTo: { $in: employees.map((it) => it._id) } }, + { projection: { _id: 1, attachedTo: 1 } } + ) - return new Map( - uniqueIds.map((_id) => { - const employee = employeesBySocialId[_id] - const space = employee !== undefined ? spacesByEmployee.get(employee._id)?.[0] : undefined - const person = employee ?? personsBySocialId[_id] - const socialStrings = socialStringsByPersons[person?._id] ?? [] + const employeeByAccount = new Map(employees.map((it) => [it.personUuid, it])) + const spaceByPerson = new Map(spaces.map((it) => [it.person, it])) + const socialIdsByEmployee = groupByArray(socialIds, (it) => it.attachedTo) - return [ - _id, - { - _id, - person, - socialStrings, - space: space?._id, - account: employee?.personUuid, - employee - } - ] + return accounts + .map((account) => { + const employee = employeeByAccount.get(account) + if (employee === undefined) return undefined + const space = spaceByPerson.get(employee._id) + if (space === undefined) return undefined + + const info: ReceiverInfo = { + employee: employee._id, + space: space._id, + account, + socialIds: socialIdsByEmployee.get(employee._id)?.map((it) => it._id) ?? [] + } + return info }) - ) + .filter(notEmpty) } -export function toReceiverInfo (hierarchy: Hierarchy, info?: SenderInfo | ReceiverInfo): ReceiverInfo | undefined { - if (info === undefined) return undefined - if (info.person === undefined) return undefined - if (!('space' in info)) return undefined - if (info.space === undefined) return undefined +export async function getSenderInfo ( + ctx: MeasureContext, + socialId: PersonId, + control: TriggerControl +): Promise { + const controlAccount = control.ctx.contextData.account + let account: AccountUuid | undefined = control.ctx.contextData.socialStringsToUsers.get(socialId) - const isEmployee = hierarchy.hasMixin(info.person, contact.mixin.Employee) - if (!isEmployee) return undefined + if (account == null && controlAccount.socialIds.includes(socialId)) { + account = controlAccount.uuid + } - const employee = hierarchy.as(info.person, contact.mixin.Employee) - if (!employee.active || employee.personUuid == null) return undefined + if (account != null) { + return { + socialId, + person: (await control.findAll(ctx, contact.class.Person, { personUuid: account }))[0] + } + } + + const socialIdentity = ( + await control.findAll( + control.ctx, + contact.class.SocialIdentity, + { _id: socialId as SocialIdentityRef }, + { limit: 1, projection: { _id: 1, attachedTo: 1 } } + ) + )[0] + + if (socialIdentity === undefined) { + return { socialId } + } return { - _id: info._id, - person: employee, - space: info.space, - socialStrings: info.socialStrings, - account: employee.personUuid, - employee + socialId, + person: (await control.findAll(ctx, contact.class.Person, { _id: socialIdentity.attachedTo }))[0] } } diff --git a/server-plugins/notification/src/index.ts b/server-plugins/notification/src/index.ts index b63bac063c..08c33ba972 100644 --- a/server-plugins/notification/src/index.ts +++ b/server-plugins/notification/src/index.ts @@ -59,7 +59,7 @@ export type TypeMatchFunc = Resource< ( tx: Tx, doc: Doc, - person: Person, + person: Ref, socialIds: PersonId[], type: NotificationType, control: TriggerControl @@ -79,7 +79,7 @@ export interface TypeMatch extends NotificationType { export type NotificationContentProvider = ( doc: Doc, tx: TxCUD, - target: PersonId, + person: Ref, control: TriggerControl ) => Promise @@ -91,19 +91,15 @@ export interface NotificationPresenter extends Class { } export interface ReceiverInfo { - _id: PersonId - person: Person - socialStrings: PersonId[] - - space: Ref account: AccountUuid - employee: Employee + employee: Ref + socialIds: PersonId[] + space: Ref } export interface SenderInfo { - _id: PersonId + socialId: PersonId person?: Person - socialStrings: PersonId[] } export type NotificationProviderFunc = ( @@ -152,7 +148,6 @@ export default plugin(serverNotificationId, { PushNotificationsHandler: '' as Resource }, function: { - IsUserInFieldValueTypeMatch: '' as TypeMatchFunc, IsUserEmployeeInFieldValueTypeMatch: '' as TypeMatchFunc } }) diff --git a/server-plugins/request-resources/src/index.ts b/server-plugins/request-resources/src/index.ts index c750af0124..50df2bac83 100644 --- a/server-plugins/request-resources/src/index.ts +++ b/server-plugins/request-resources/src/index.ts @@ -16,7 +16,6 @@ import { DocUpdateMessage } from '@hcengineering/activity' import core, { Doc, Tx, TxCUD, TxCreateDoc, TxProcessor, TxUpdateDoc, type MeasureContext } from '@hcengineering/core' import notification from '@hcengineering/notification' -import { getPrimarySocialIdsByAccounts } from '@hcengineering/server-contact' import { getResource, translate } from '@hcengineering/platform' import request, { Request, RequestStatus } from '@hcengineering/request' import { pushDocUpdateMessages } from '@hcengineering/server-activity-resources' @@ -25,9 +24,9 @@ import { getCollaborators, getNotificationProviderControl, getNotificationTxes, - getTextPresenter, - getUsersInfo, - toReceiverInfo + getReceiversInfo, + getSenderInfo, + getTextPresenter } from '@hcengineering/server-notification-resources' /** @@ -146,35 +145,18 @@ async function getRequestNotificationTx ( const notifyContexts = await control.findAll(control.ctx, notification.class.DocNotifyContext, { objectId: doc._id }) - const collaboratorsPrimarySocialStringsByAccounts = await getPrimarySocialIdsByAccounts( - control, - Array.from(collaborators) - ) - const usersInfo = await getUsersInfo( - control.ctx, - [...Object.values(collaboratorsPrimarySocialStringsByAccounts), tx.modifiedBy], - control - ) - const senderInfo = usersInfo.get(tx.modifiedBy) ?? { - _id: tx.modifiedBy, - socialStrings: [] - } + const receiverInfos = await getReceiversInfo(ctx, Array.from(collaborators), control) + const senderInfo = await getSenderInfo(ctx, tx.modifiedBy, control) const notificationControl = await getNotificationProviderControl(ctx, control) - for (const target of collaborators) { - const targetInfo = toReceiverInfo( - control.hierarchy, - usersInfo.get(collaboratorsPrimarySocialStringsByAccounts[target]) - ) - if (targetInfo === undefined) continue - + for (const receiver of receiverInfos) { const txes = await getNotificationTxes( ctx, control, request, tx, - targetInfo, + receiver, senderInfo, { isOwn: true, isSpace: false, shouldUpdateTimestamp: true }, notifyContexts, diff --git a/server-plugins/telegram-resources/src/index.ts b/server-plugins/telegram-resources/src/index.ts index be05508301..271775c91d 100644 --- a/server-plugins/telegram-resources/src/index.ts +++ b/server-plugins/telegram-resources/src/index.ts @@ -100,7 +100,7 @@ export async function OnMessageCreate (txes: Tx[], control: TriggerControl): Pro export function IsIncomingMessageTypeMatch ( tx: Tx, doc: Doc, - person: Person, + person: Ref, user: PersonId[], type: NotificationType, control: TriggerControl diff --git a/server-plugins/time-resources/src/index.ts b/server-plugins/time-resources/src/index.ts index 8308676b69..625783a7b5 100644 --- a/server-plugins/time-resources/src/index.ts +++ b/server-plugins/time-resources/src/index.ts @@ -14,7 +14,7 @@ // import { Analytics } from '@hcengineering/analytics' -import contact, { Employee, Person, pickPrimarySocialId } from '@hcengineering/contact' +import contact, { Employee, Person } from '@hcengineering/contact' import core, { AttachedData, @@ -37,13 +37,14 @@ import core, { import notification, { CommonInboxNotification } from '@hcengineering/notification' import { getResource } from '@hcengineering/platform' import type { TriggerControl } from '@hcengineering/server-core' -import { getSocialStrings, getPerson, getAllSocialStringsByPersonId } from '@hcengineering/server-contact' +import { getSocialStrings } from '@hcengineering/server-contact' import { ReceiverInfo, SenderInfo } from '@hcengineering/server-notification' import { getCommonNotificationTxes, getNotificationContent, getNotificationProviderControl, - isShouldNotifyTx + isShouldNotifyTx, + getSenderInfo } from '@hcengineering/server-notification-resources' import serverTime, { OnToDo, ToDoFactory } from '@hcengineering/server-time' import task, { makeRank } from '@hcengineering/task' @@ -295,44 +296,33 @@ export async function OnToDoCreate (txes: TxCUD[], control: TriggerControl) continue } - const socialStrings = await getSocialStrings(control, employee._id) - const primarySocialString = pickPrimarySocialId(socialStrings) + const socialIds = await getSocialStrings(control, employee._id) const account = employee.personUuid if (account == null) { continue } - // TODO: Select a proper account const receiverInfo: ReceiverInfo = { - _id: primarySocialString, - person: employee, - socialStrings, - - employee, account, - space: personSpace._id + socialIds, + space: personSpace._id, + employee: employee._id } - const senderPerson = await getPerson(control, tx.modifiedBy) - const senderSocialStrings = await getAllSocialStringsByPersonId(control, [tx.modifiedBy]) - - const senderInfo: SenderInfo = { - _id: tx.modifiedBy, - person: senderPerson, - socialStrings: senderSocialStrings - } + const senderInfo: SenderInfo = await getSenderInfo(control.ctx, tx.modifiedBy, control) const notificationControl = await getNotificationProviderControl(control.ctx, control) const notifyResult = await isShouldNotifyTx( control, createTx, todo, - socialStrings, + employee._id, + socialIds, true, false, notificationControl ) - const content = await getNotificationContent(tx, socialStrings, senderInfo, todo, control) + const content = await getNotificationContent(tx, employee._id, senderInfo, todo, control) const data: Partial> = { ...content, header: time.string.ToDo, @@ -361,12 +351,9 @@ export async function OnToDoCreate (txes: TxCUD[], control: TriggerControl) await control.apply(control.ctx, txes) const ids = txes.map((it) => it._id) - const personUuid = receiverInfo.person?.personUuid - if (personUuid !== undefined) { - control.ctx.contextData.broadcast.targets.notifications = (it) => { - if (ids.includes(it._id)) { - return [personUuid] - } + control.ctx.contextData.broadcast.targets.notifications = (it) => { + if (ids.includes(it._id)) { + return [receiverInfo.account] } } } diff --git a/server-plugins/tracker-resources/src/index.ts b/server-plugins/tracker-resources/src/index.ts index da0d603fae..acad4e3132 100644 --- a/server-plugins/tracker-resources/src/index.ts +++ b/server-plugins/tracker-resources/src/index.ts @@ -20,7 +20,6 @@ import core, { concatLink, Doc, DocumentUpdate, - PersonId, Ref, Space, systemAccountUuid, @@ -34,7 +33,6 @@ import core, { } from '@hcengineering/core' import { NotificationContent } from '@hcengineering/notification' import { getMetadata, IntlString } from '@hcengineering/platform' -import { getSocialStrings } from '@hcengineering/server-contact' import serverCore, { TriggerControl } from '@hcengineering/server-core' import { NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification' import { stripTags } from '@hcengineering/text-core' @@ -89,18 +87,13 @@ export async function issueTextPresenter (doc: Doc): Promise { return `${issue.identifier} ${issue.title}` } -async function isSamePerson (control: TriggerControl, assignee: Ref, target: PersonId): Promise { - const socialStrings = await getSocialStrings(control, assignee) - return socialStrings.includes(target) -} - /** * @public */ export async function getIssueNotificationContent ( doc: Doc, tx: TxCUD, - target: PersonId, + target: Ref, control: TriggerControl ): Promise { const issue = doc as Issue @@ -127,7 +120,7 @@ export async function getIssueNotificationContent ( if ( updateTx.operations.assignee !== null && updateTx.operations.assignee !== undefined && - (await isSamePerson(control, updateTx.operations.assignee, target)) + updateTx.operations.assignee === target ) { body = tracker.string.IssueAssignedToYou } else { diff --git a/server-plugins/training-resources/src/functions/TrainingRequestNotificationTypeMatch.ts b/server-plugins/training-resources/src/functions/TrainingRequestNotificationTypeMatch.ts index 143b008c7d..72d5b6b811 100644 --- a/server-plugins/training-resources/src/functions/TrainingRequestNotificationTypeMatch.ts +++ b/server-plugins/training-resources/src/functions/TrainingRequestNotificationTypeMatch.ts @@ -24,13 +24,13 @@ import { isTxUpdateDoc } from '../utils/isTxUpdateDoc' export function TrainingRequestNotificationTypeMatch ( tx: TxCUD, doc: TrainingRequest, - person: Person, + person: Ref, user: PersonId[], type: NotificationType, control: TriggerControl ): boolean { if (isTxCreateDoc(tx)) { - return doc.trainees.includes(person._id as Ref) + return doc.trainees.includes(person as Ref) } if (isTxUpdateDoc(tx)) { @@ -40,7 +40,7 @@ export function TrainingRequestNotificationTypeMatch ( } const newTrainees = typeof pushed === 'object' ? pushed.$each : [pushed] - return newTrainees.includes(person._id as Ref) + return newTrainees.includes(person as Ref) } return false