UBERF-6802: Improve create chat message performance (#5530)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina 2024-05-07 10:49:36 +04:00 committed by GitHub
parent 0c4ff22173
commit 9d47b22a17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 198 additions and 213 deletions

View File

@ -20,7 +20,6 @@ import chunter from '@hcengineering/chunter'
import serverNotification from '@hcengineering/server-notification' import serverNotification from '@hcengineering/server-notification'
import serverCore, { type ObjectDDParticipant } from '@hcengineering/server-core' import serverCore, { type ObjectDDParticipant } from '@hcengineering/server-core'
import serverChunter from '@hcengineering/server-chunter' import serverChunter from '@hcengineering/server-chunter'
import notification from '@hcengineering/notification'
export { serverChunterId } from '@hcengineering/server-chunter' export { serverChunterId } from '@hcengineering/server-chunter'
@ -78,25 +77,12 @@ export function createModel (builder: Builder): void {
} }
}) })
builder.mixin(chunter.ids.DMNotification, notification.class.NotificationType, serverNotification.mixin.TypeMatch, { builder.createDoc(serverCore.class.Trigger, core.space.Model, {
func: serverChunter.function.IsDirectMessage trigger: serverChunter.trigger.OnChatMessageCreate,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx._class': core.class.TxCreateDoc,
'tx.objectClass': chunter.class.ChatMessage
}
}) })
builder.mixin(
chunter.ids.ThreadNotification,
notification.class.NotificationType,
serverNotification.mixin.TypeMatch,
{
func: serverChunter.function.IsThreadMessage
}
)
builder.mixin(
chunter.ids.ChannelNotification,
notification.class.NotificationType,
serverNotification.mixin.TypeMatch,
{
func: serverChunter.function.IsChannelMessage
}
)
} }

View File

@ -30,7 +30,6 @@ import serverNotification, {
type TypeMatch, type TypeMatch,
type NotificationContentProvider type NotificationContentProvider
} from '@hcengineering/server-notification' } from '@hcengineering/server-notification'
import chunter from '@hcengineering/model-chunter'
export { serverNotificationId } from '@hcengineering/server-notification' export { serverNotificationId } from '@hcengineering/server-notification'
@ -67,15 +66,6 @@ export function createModel (builder: Builder): void {
} }
}) })
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverNotification.trigger.OnChatMessageCreate,
txMatch: {
_class: core.class.TxCollectionCUD,
'tx._class': core.class.TxCreateDoc,
'tx.objectClass': chunter.class.ChatMessage
}
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, { builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverNotification.trigger.OnAttributeCreate, trigger: serverNotification.trigger.OnAttributeCreate,
txMatch: { txMatch: {

View File

@ -125,7 +125,16 @@ export async function createReactionNotifications (
const docUpdateMessage = TxProcessor.createDoc2Doc(messageTx.tx as TxCreateDoc<DocUpdateMessage>) const docUpdateMessage = TxProcessor.createDoc2Doc(messageTx.tx as TxCreateDoc<DocUpdateMessage>)
res = res.concat( res = res.concat(
await createCollabDocInfo([user], control, tx.tx, tx, parentMessage, [docUpdateMessage], true, false, false) await createCollabDocInfo(
[user],
control,
tx.tx,
tx,
parentMessage,
[docUpdateMessage],
{ isOwn: true, isSpace: false, shouldUpdateTimestamp: false },
new Map()
)
) )
return res return res

View File

@ -41,20 +41,20 @@ import core, {
TxRemoveDoc, TxRemoveDoc,
TxUpdateDoc TxUpdateDoc
} from '@hcengineering/core' } from '@hcengineering/core'
import notification, { Collaborators, NotificationContent } from '@hcengineering/notification' import notification, { ClassCollaborators, Collaborators, NotificationContent } from '@hcengineering/notification'
import { getMetadata, IntlString } from '@hcengineering/platform' import { getMetadata, IntlString } from '@hcengineering/platform'
import serverCore, { TriggerControl } from '@hcengineering/server-core' import serverCore, { TriggerControl } from '@hcengineering/server-core'
import { import {
getDocCollaborators, getDocCollaborators,
getMixinTx, getMixinTx,
pushActivityInboxNotifications pushActivityInboxNotifications,
createCollaboratorNotifications
} from '@hcengineering/server-notification-resources' } from '@hcengineering/server-notification-resources'
import { workbenchId } from '@hcengineering/workbench' import { workbenchId } from '@hcengineering/workbench'
import { stripTags } from '@hcengineering/text' import { stripTags } from '@hcengineering/text'
import { Person, PersonAccount } from '@hcengineering/contact' import { Person, PersonAccount } from '@hcengineering/contact'
import activity, { ActivityMessage, ActivityReference } from '@hcengineering/activity' import activity, { ActivityMessage, ActivityReference } from '@hcengineering/activity'
import { IsChannelMessage, IsDirectMessage, IsThreadMessage } from './utils'
import { NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification' import { NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification'
/** /**
@ -143,37 +143,21 @@ async function OnThreadMessageCreated (tx: Tx, control: TriggerControl): Promise
return [lastReplyTx, employeeTx] return [lastReplyTx, employeeTx]
} }
async function OnChatMessageCreated (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> { async function updateCollaborators (
targetDoc: Doc | undefined,
tx: TxCUD<Doc>,
control: TriggerControl,
message: ChatMessage,
mixin?: ClassCollaborators
): Promise<Tx[]> {
if (targetDoc === undefined || mixin === undefined) return []
const hierarchy = control.hierarchy const hierarchy = control.hierarchy
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<ChatMessage>
if (
actualTx._class !== core.class.TxCreateDoc ||
!hierarchy.isDerived(actualTx.objectClass, chunter.class.ChatMessage)
) {
return []
}
const chatMessage = TxProcessor.createDoc2Doc(actualTx)
const mixin = hierarchy.classHierarchyMixin(chatMessage.attachedToClass, notification.mixin.ClassCollaborators)
if (mixin === undefined) {
return []
}
const targetDoc = (
await control.findAll(chatMessage.attachedToClass, { _id: chatMessage.attachedTo }, { limit: 1 })
)[0]
if (targetDoc === undefined) {
return []
}
const res: Tx[] = []
const isChannel = hierarchy.isDerived(targetDoc._class, chunter.class.Channel) const isChannel = hierarchy.isDerived(targetDoc._class, chunter.class.Channel)
const res: Tx[] = []
if (hierarchy.hasMixin(targetDoc, notification.mixin.Collaborators)) { if (hierarchy.hasMixin(targetDoc, notification.mixin.Collaborators)) {
const collaboratorsMixin = hierarchy.as(targetDoc, notification.mixin.Collaborators) const collaboratorsMixin = hierarchy.as(targetDoc, notification.mixin.Collaborators)
if (!collaboratorsMixin.collaborators.includes(chatMessage.modifiedBy)) { if (!collaboratorsMixin.collaborators.includes(message.modifiedBy)) {
res.push( res.push(
control.txFactory.createTxMixin( control.txFactory.createTxMixin(
targetDoc._id, targetDoc._id,
@ -182,7 +166,7 @@ async function OnChatMessageCreated (tx: TxCUD<Doc>, control: TriggerControl): P
notification.mixin.Collaborators, notification.mixin.Collaborators,
{ {
$push: { $push: {
collaborators: chatMessage.modifiedBy collaborators: message.modifiedBy
} }
} }
) )
@ -190,19 +174,50 @@ async function OnChatMessageCreated (tx: TxCUD<Doc>, control: TriggerControl): P
} }
} else { } else {
const collaborators = await getDocCollaborators(targetDoc, mixin, control) const collaborators = await getDocCollaborators(targetDoc, mixin, control)
if (!collaborators.includes(chatMessage.modifiedBy)) { if (!collaborators.includes(message.modifiedBy)) {
collaborators.push(chatMessage.modifiedBy) collaborators.push(message.modifiedBy)
} }
res.push(getMixinTx(tx, control, collaborators)) res.push(getMixinTx(tx, control, collaborators))
} }
if (isChannel && !(targetDoc as Channel).members.includes(chatMessage.modifiedBy)) { if (isChannel && !(targetDoc as Channel).members.includes(message.modifiedBy)) {
res.push(...joinChannel(control, targetDoc as Channel, chatMessage.modifiedBy)) res.push(...joinChannel(control, targetDoc as Channel, message.modifiedBy))
} }
return res return res
} }
async function OnChatMessageCreate (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
const hierarchy = control.hierarchy
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<ChatMessage>
if (actualTx._class !== core.class.TxCreateDoc) {
return []
}
const chatMessage = TxProcessor.createDoc2Doc(actualTx)
const cache = new Map<Ref<Doc>, Doc>()
const mixin = hierarchy.classHierarchyMixin(chatMessage.attachedToClass, notification.mixin.ClassCollaborators)
let targetDoc: Doc | undefined
if (mixin !== undefined) {
targetDoc = (await control.findAll(chatMessage.attachedToClass, { _id: chatMessage.attachedTo }, { limit: 1 }))[0]
}
if (targetDoc !== undefined) {
cache.set(targetDoc._id, targetDoc)
}
const res = await Promise.all([
createCollaboratorNotifications(control.ctx, tx, control, [chatMessage], undefined, cache),
updateCollaborators(targetDoc, tx, control, chatMessage, mixin)
])
return res.flat()
}
function joinChannel (control: TriggerControl, channel: Channel, user: Ref<Account>): Tx[] { function joinChannel (control: TriggerControl, channel: Channel, user: Ref<Account>): Tx[] {
if (channel.members.includes(user)) { if (channel.members.includes(user)) {
return [] return []
@ -260,7 +275,6 @@ export async function ChunterTrigger (tx: Tx, control: TriggerControl): Promise<
const res = await Promise.all([ const res = await Promise.all([
OnThreadMessageCreated(tx, control), OnThreadMessageCreated(tx, control),
OnThreadMessageDeleted(tx, control), OnThreadMessageDeleted(tx, control),
OnChatMessageCreated(tx as TxCUD<Doc>, control),
OnCollaboratorsChanged(tx as TxMixin<Doc, Collaborators>, control) OnCollaboratorsChanged(tx as TxMixin<Doc, Collaborators>, control)
]) ])
return res.flat() return res.flat()
@ -516,15 +530,13 @@ export default async () => ({
ChunterTrigger, ChunterTrigger,
OnDirectMessageSent, OnDirectMessageSent,
OnChatMessageRemoved, OnChatMessageRemoved,
OnChannelMembersChanged OnChannelMembersChanged,
OnChatMessageCreate
}, },
function: { function: {
CommentRemove, CommentRemove,
ChannelHTMLPresenter: channelHTMLPresenter, ChannelHTMLPresenter: channelHTMLPresenter,
ChannelTextPresenter: channelTextPresenter, ChannelTextPresenter: channelTextPresenter,
ChunterNotificationContentProvider: getChunterNotificationContent, ChunterNotificationContentProvider: getChunterNotificationContent
IsDirectMessage,
IsThreadMessage,
IsChannelMessage
} }
}) })

View File

@ -1,60 +0,0 @@
//
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { Account, Doc, Ref, Tx, TxCreateDoc } from '@hcengineering/core'
import { NotificationType } from '@hcengineering/notification'
import { TriggerControl } from '@hcengineering/server-core'
import chunter, { DirectMessage } from '@hcengineering/chunter'
import activity from '@hcengineering/activity'
/**
* @public
*/
export async function IsChannelMessage (
tx: Tx,
doc: Doc,
user: Ref<Account>,
type: NotificationType,
control: TriggerControl
): Promise<boolean> {
const hierarchy = control.hierarchy
const isDirect = await IsDirectMessage(tx, doc, user, type, control)
if (isDirect) {
return false
}
return hierarchy.isDerived(doc._class, chunter.class.Channel) || hierarchy.hasMixin(doc, activity.mixin.ActivityDoc)
}
/**
* @public
*/
export async function IsThreadMessage (tx: TxCreateDoc<Doc>): Promise<boolean> {
return tx.objectClass === chunter.class.ThreadMessage
}
/**
* @public
*/
export async function IsDirectMessage (
tx: Tx,
doc: Doc,
user: Ref<Account>,
type: NotificationType,
control: TriggerControl
): Promise<boolean> {
const dm = (await control.findAll(chunter.class.DirectMessage, { _id: doc._id as Ref<DirectMessage> }))[0]
return dm !== undefined
}

View File

@ -16,7 +16,7 @@
import type { Plugin, Resource } from '@hcengineering/platform' import type { Plugin, Resource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform' import { plugin } from '@hcengineering/platform'
import { ObjectDDParticipantFunc, TriggerFunc } from '@hcengineering/server-core' import { ObjectDDParticipantFunc, TriggerFunc } from '@hcengineering/server-core'
import { NotificationContentProvider, Presenter, TypeMatchFunc } from '@hcengineering/server-notification' import { NotificationContentProvider, Presenter } from '@hcengineering/server-notification'
/** /**
* @public * @public
@ -31,15 +31,13 @@ export default plugin(serverChunterId, {
ChunterTrigger: '' as Resource<TriggerFunc>, ChunterTrigger: '' as Resource<TriggerFunc>,
OnDirectMessageSent: '' as Resource<TriggerFunc>, OnDirectMessageSent: '' as Resource<TriggerFunc>,
OnChatMessageRemoved: '' as Resource<TriggerFunc>, OnChatMessageRemoved: '' as Resource<TriggerFunc>,
OnChannelMembersChanged: '' as Resource<TriggerFunc> OnChannelMembersChanged: '' as Resource<TriggerFunc>,
OnChatMessageCreate: '' as Resource<TriggerFunc>
}, },
function: { function: {
CommentRemove: '' as Resource<ObjectDDParticipantFunc>, CommentRemove: '' as Resource<ObjectDDParticipantFunc>,
ChannelHTMLPresenter: '' as Resource<Presenter>, ChannelHTMLPresenter: '' as Resource<Presenter>,
ChannelTextPresenter: '' as Resource<Presenter>, ChannelTextPresenter: '' as Resource<Presenter>,
IsDirectMessage: '' as TypeMatchFunc,
IsChannelMessage: '' as TypeMatchFunc,
IsThreadMessage: '' as TypeMatchFunc,
ChunterNotificationContentProvider: '' as Resource<NotificationContentProvider> ChunterNotificationContentProvider: '' as Resource<NotificationContentProvider>
} }
}) })

View File

@ -78,7 +78,7 @@ import serverNotification, {
import { stripTags } from '@hcengineering/text' import { stripTags } from '@hcengineering/text'
import { workbenchId } from '@hcengineering/workbench' import { workbenchId } from '@hcengineering/workbench'
import webpush, { WebPushError } from 'web-push' import webpush, { WebPushError } from 'web-push'
import { Content, NotifyResult } from './types' import { Content, NotifyResult, NotifyParams } from './types'
import { import {
getHTMLPresenter, getHTMLPresenter,
getNotificationContent, getNotificationContent,
@ -386,7 +386,8 @@ export async function pushInboxNotifications (
modifiedOn: Timestamp, modifiedOn: Timestamp,
senderId: Ref<PersonAccount>, senderId: Ref<PersonAccount>,
shouldPush: boolean, shouldPush: boolean,
shouldUpdateTimestamp = true shouldUpdateTimestamp = true,
cache: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
): Promise<void> { ): Promise<void> {
const context = getDocNotifyContext(contexts, targetUser, attachedTo, res) const context = getDocNotifyContext(contexts, targetUser, attachedTo, res)
@ -434,7 +435,8 @@ export async function pushInboxNotifications (
notificationData, notificationData,
_class, _class,
senderId, senderId,
notificationTx.objectId notificationTx.objectId,
cache
) )
console.log('Push takes', Date.now() - now, 'ms') console.log('Push takes', Date.now() - now, 'ms')
if (pushTx !== undefined) { if (pushTx !== undefined) {
@ -528,7 +530,8 @@ export async function createPushFromInbox (
data: Data<InboxNotification>, data: Data<InboxNotification>,
_class: Ref<Class<InboxNotification>>, _class: Ref<Class<InboxNotification>>,
senderId: Ref<PersonAccount>, senderId: Ref<PersonAccount>,
_id: Ref<Doc> _id: Ref<Doc>,
cache: Map<Ref<Doc>, Doc>
): Promise<Tx | undefined> { ): Promise<Tx | undefined> {
let title: string = '' let title: string = ''
let body: string = '' let body: string = ''
@ -547,8 +550,14 @@ export async function createPushFromInbox (
let senderPerson: Person | undefined let senderPerson: Person | undefined
if (sender !== undefined) { if (sender !== undefined) {
senderPerson = (await control.findAll(contact.class.Person, { _id: sender.person }))[0] senderPerson =
(cache.get(sender.person) as Person) ?? (await control.findAll(contact.class.Person, { _id: sender.person }))[0]
} }
if (senderPerson !== undefined) {
cache.set(senderPerson._id, senderPerson)
}
const path = [ const path = [
workbenchId, workbenchId,
control.workspace.workspaceUrl, control.workspace.workspaceUrl,
@ -650,19 +659,11 @@ export async function pushActivityInboxNotifications (
docNotifyContexts: DocNotifyContext[], docNotifyContexts: DocNotifyContext[],
activityMessages: ActivityMessage[], activityMessages: ActivityMessage[],
shouldUpdateTimestamp: boolean, shouldUpdateTimestamp: boolean,
shouldPush: boolean shouldPush: boolean,
cache: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
): Promise<void> { ): Promise<void> {
for (const activityMessage of activityMessages) { for (const activityMessage of activityMessages) {
const existNotifications = await control.findAll(notification.class.ActivityInboxNotification, { const content = await getNotificationContent(originTx, targetUser, object, control, cache)
user: targetUser,
attachedTo: activityMessage._id
})
if (existNotifications.length > 0) {
return
}
const content = await getNotificationContent(originTx, targetUser, object, control)
const data: Partial<Data<ActivityInboxNotification>> = { const data: Partial<Data<ActivityInboxNotification>> = {
...content, ...content,
attachedTo: activityMessage._id, attachedTo: activityMessage._id,
@ -682,7 +683,8 @@ export async function pushActivityInboxNotifications (
activityMessage.modifiedOn, activityMessage.modifiedOn,
originTx.modifiedBy as Ref<PersonAccount>, originTx.modifiedBy as Ref<PersonAccount>,
shouldPush, shouldPush,
shouldUpdateTimestamp shouldUpdateTimestamp,
cache
) )
} }
} }
@ -693,14 +695,13 @@ export async function getNotificationTxes (
tx: TxCUD<Doc>, tx: TxCUD<Doc>,
originTx: TxCUD<Doc>, originTx: TxCUD<Doc>,
target: Ref<Account>, target: Ref<Account>,
isOwn: boolean, params: NotifyParams,
isSpace: boolean,
docNotifyContexts: DocNotifyContext[], docNotifyContexts: DocNotifyContext[],
activityMessages: ActivityMessage[], activityMessages: ActivityMessage[],
shouldUpdateTimestamp = true cache: Map<Ref<Doc>, Doc>
): Promise<Tx[]> { ): Promise<Tx[]> {
const res: Tx[] = [] const res: Tx[] = []
const notifyResult = await isShouldNotifyTx(control, tx, originTx, object, target, isOwn, isSpace) const notifyResult = await isShouldNotifyTx(control, tx, originTx, object, target, params.isOwn, params.isSpace)
if (notifyResult.allowed) { if (notifyResult.allowed) {
await pushActivityInboxNotifications( await pushActivityInboxNotifications(
@ -711,8 +712,9 @@ export async function getNotificationTxes (
object, object,
docNotifyContexts, docNotifyContexts,
activityMessages, activityMessages,
shouldUpdateTimestamp, params.shouldUpdateTimestamp,
notifyResult.push notifyResult.push,
cache
) )
} }
@ -745,18 +747,17 @@ export async function createCollabDocInfo (
tx: TxCUD<Doc>, tx: TxCUD<Doc>,
originTx: TxCUD<Doc>, originTx: TxCUD<Doc>,
object: Doc, object: Doc,
activityMessage: ActivityMessage[], activityMessages: ActivityMessage[],
isOwn: boolean, params: NotifyParams,
isSpace: boolean = false, cache: Map<Ref<Doc>, Doc>
shouldUpdateTimestamp = true
): Promise<Tx[]> { ): Promise<Tx[]> {
let res: Tx[] = [] let res: Tx[] = []
if (originTx.space === core.space.DerivedTx) { if (originTx.space === core.space.DerivedTx || collaborators.length === 0) {
return res return res
} }
const docMessages = activityMessage.filter((message) => message.attachedTo === object._id) const docMessages = activityMessages.filter((message) => message.attachedTo === object._id)
if (docMessages.length === 0) { if (docMessages.length === 0) {
return res return res
@ -778,18 +779,7 @@ export async function createCollabDocInfo (
for (const target of targets) { for (const target of targets) {
res = res.concat( res = res.concat(
await getNotificationTxes( await getNotificationTxes(control, object, tx, originTx, target, params, notifyContexts, docMessages, cache)
control,
object,
tx,
originTx,
target,
isOwn,
isSpace,
notifyContexts,
docMessages,
shouldUpdateTimestamp
)
) )
} }
return res return res
@ -819,8 +809,13 @@ async function getSpaceCollabTxes (
doc: Doc, doc: Doc,
tx: TxCUD<Doc>, tx: TxCUD<Doc>,
originTx: TxCUD<Doc>, originTx: TxCUD<Doc>,
activityMessages: ActivityMessage[] activityMessages: ActivityMessage[],
cache: Map<Ref<Doc>, Doc>
): Promise<Tx[]> { ): Promise<Tx[]> {
if (doc.space === core.space.Space) {
return []
}
const space = (await control.findAll(core.class.Space, { _id: doc.space }))[0] const space = (await control.findAll(core.class.Space, { _id: doc.space }))[0]
if (space === undefined) return [] if (space === undefined) return []
const mixin = control.hierarchy.classHierarchyMixin<Doc, ClassCollaborators>( const mixin = control.hierarchy.classHierarchyMixin<Doc, ClassCollaborators>(
@ -830,7 +825,16 @@ async function getSpaceCollabTxes (
if (mixin !== undefined) { if (mixin !== undefined) {
const collabs = control.hierarchy.as<Doc, Collaborators>(space, notification.mixin.Collaborators) const collabs = control.hierarchy.as<Doc, Collaborators>(space, notification.mixin.Collaborators)
if (collabs.collaborators !== undefined) { if (collabs.collaborators !== undefined) {
return await createCollabDocInfo(collabs.collaborators, control, tx, originTx, doc, activityMessages, false, true) return await createCollabDocInfo(
collabs.collaborators,
control,
tx,
originTx,
doc,
activityMessages,
{ isSpace: true, isOwn: false, shouldUpdateTimestamp: true },
cache
)
} }
} }
return [] return []
@ -840,7 +844,8 @@ async function createCollaboratorDoc (
tx: TxCreateDoc<Doc>, tx: TxCreateDoc<Doc>,
control: TriggerControl, control: TriggerControl,
activityMessage: ActivityMessage[], activityMessage: ActivityMessage[],
originTx: TxCUD<Doc> originTx: TxCUD<Doc>,
cache: Map<Ref<Doc>, Doc>
): Promise<Tx[]> { ): Promise<Tx[]> {
const res: Tx[] = [] const res: Tx[] = []
const hierarchy = control.hierarchy const hierarchy = control.hierarchy
@ -854,11 +859,20 @@ async function createCollaboratorDoc (
const collaborators = await getDocCollaborators(doc, mixin, control) const collaborators = await getDocCollaborators(doc, mixin, control)
const mixinTx = getMixinTx(tx, control, collaborators) const mixinTx = getMixinTx(tx, control, collaborators)
const notificationTxes = await createCollabDocInfo(collaborators, control, tx, originTx, doc, activityMessage, true) const notificationTxes = await createCollabDocInfo(
collaborators,
control,
tx,
originTx,
doc,
activityMessage,
{ isOwn: true, isSpace: false, shouldUpdateTimestamp: true },
cache
)
res.push(mixinTx) res.push(mixinTx)
res.push(...notificationTxes) res.push(...notificationTxes)
res.push(...(await getSpaceCollabTxes(control, doc, tx, originTx, activityMessage))) res.push(...(await getSpaceCollabTxes(control, doc, tx, originTx, activityMessage, cache)))
return res return res
} }
@ -867,7 +881,8 @@ async function updateCollaboratorsMixin (
tx: TxMixin<Doc, Collaborators>, tx: TxMixin<Doc, Collaborators>,
control: TriggerControl, control: TriggerControl,
activityMessages: ActivityMessage[], activityMessages: ActivityMessage[],
originTx: TxCUD<Doc> originTx: TxCUD<Doc>,
cache: Map<Ref<Doc>, Doc>
): Promise<Tx[]> { ): Promise<Tx[]> {
const { hierarchy } = control const { hierarchy } = control
@ -936,7 +951,8 @@ async function updateCollaboratorsMixin (
docNotifyContexts, docNotifyContexts,
activityMessages, activityMessages,
true, true,
true true,
cache
) )
} }
} }
@ -947,10 +963,11 @@ async function updateCollaboratorsMixin (
async function collectionCollabDoc ( async function collectionCollabDoc (
tx: TxCollectionCUD<Doc, AttachedDoc>, tx: TxCollectionCUD<Doc, AttachedDoc>,
control: TriggerControl, control: TriggerControl,
activityMessages: ActivityMessage[] activityMessages: ActivityMessage[],
cache: Map<Ref<Doc>, Doc>
): Promise<Tx[]> { ): Promise<Tx[]> {
const actualTx = TxProcessor.extractTx(tx) as TxCUD<Doc> const actualTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
let res = await createCollaboratorNotifications(control.ctx, actualTx, control, activityMessages, tx) let res = await createCollaboratorNotifications(control.ctx, actualTx, control, activityMessages, tx, cache)
if (![core.class.TxCreateDoc, core.class.TxRemoveDoc, core.class.TxUpdateDoc].includes(actualTx._class)) { if (![core.class.TxCreateDoc, core.class.TxRemoveDoc, core.class.TxUpdateDoc].includes(actualTx._class)) {
return res return res
@ -962,15 +979,28 @@ async function collectionCollabDoc (
return res return res
} }
const doc = (await control.findAll(tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0] const doc = cache.get(tx.objectId) ?? (await control.findAll(tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
if (doc === undefined) { if (doc === undefined) {
return res return res
} }
cache.set(doc._id, doc)
const collaborators = await getCollaborators(doc, control, tx, res) const collaborators = await getCollaborators(doc, control, tx, res)
res = res.concat(await createCollabDocInfo(collaborators, control, actualTx, tx, doc, activityMessages, false)) res = res.concat(
await createCollabDocInfo(
collaborators,
control,
actualTx,
tx,
doc,
activityMessages,
{ isOwn: false, isSpace: false, shouldUpdateTimestamp: true },
cache
)
)
return res return res
} }
@ -1068,7 +1098,8 @@ async function updateCollaboratorDoc (
tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>, tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>,
control: TriggerControl, control: TriggerControl,
originTx: TxCUD<Doc>, originTx: TxCUD<Doc>,
activityMessages: ActivityMessage[] activityMessages: ActivityMessage[],
cache: Map<Ref<Doc>, Doc>
): Promise<Tx[]> { ): Promise<Tx[]> {
const hierarchy = control.hierarchy const hierarchy = control.hierarchy
let res: Tx[] = [] let res: Tx[] = []
@ -1076,6 +1107,7 @@ async function updateCollaboratorDoc (
if (mixin === undefined) return [] if (mixin === undefined) return []
const doc = (await control.findAll(tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0] const doc = (await control.findAll(tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
if (doc === undefined) return [] if (doc === undefined) return []
const params: NotifyParams = { isOwn: true, isSpace: false, shouldUpdateTimestamp: true }
if (hierarchy.hasMixin(doc, notification.mixin.Collaborators)) { if (hierarchy.hasMixin(doc, notification.mixin.Collaborators)) {
// we should handle change field and subscribe new collaborators // we should handle change field and subscribe new collaborators
const collabMixin = hierarchy.as(doc, notification.mixin.Collaborators) const collabMixin = hierarchy.as(doc, notification.mixin.Collaborators)
@ -1103,19 +1135,19 @@ async function updateCollaboratorDoc (
originTx, originTx,
doc, doc,
activityMessages, activityMessages,
true, params,
false cache
) )
) )
} else { } else {
const collaborators = await getDocCollaborators(doc, mixin, control) const collaborators = await getDocCollaborators(doc, mixin, control)
res.push(getMixinTx(tx, control, collaborators)) res.push(getMixinTx(tx, control, collaborators))
res = res.concat( res = res.concat(
await createCollabDocInfo(collaborators, control, tx, originTx, doc, activityMessages, true, false) await createCollabDocInfo(collaborators, control, tx, originTx, doc, activityMessages, params, cache)
) )
} }
res = res.concat(await getSpaceCollabTxes(control, doc, tx, originTx, activityMessages)) res = res.concat(await getSpaceCollabTxes(control, doc, tx, originTx, activityMessages, cache))
res = res.concat(await updateNotifyContextsSpace(control, tx)) res = res.concat(await updateNotifyContextsSpace(control, tx))
return res return res
@ -1176,7 +1208,8 @@ export async function createCollaboratorNotifications (
tx: TxCUD<Doc>, tx: TxCUD<Doc>,
control: TriggerControl, control: TriggerControl,
activityMessages: ActivityMessage[], activityMessages: ActivityMessage[],
originTx?: TxCUD<Doc> originTx?: TxCUD<Doc>,
cache: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
): Promise<Tx[]> { ): Promise<Tx[]> {
if (tx.space === core.space.DerivedTx) { if (tx.space === core.space.DerivedTx) {
return [] return []
@ -1188,29 +1221,28 @@ export async function createCollaboratorNotifications (
switch (tx._class) { switch (tx._class) {
case core.class.TxCreateDoc: case core.class.TxCreateDoc:
return await createCollaboratorDoc(tx as TxCreateDoc<Doc>, control, activityMessages, originTx ?? tx) return await createCollaboratorDoc(tx as TxCreateDoc<Doc>, control, activityMessages, originTx ?? tx, cache)
case core.class.TxUpdateDoc: case core.class.TxUpdateDoc:
case core.class.TxMixin: { case core.class.TxMixin: {
let res = await updateCollaboratorDoc(tx as TxUpdateDoc<Doc>, control, originTx ?? tx, activityMessages) let res = await updateCollaboratorDoc(tx as TxUpdateDoc<Doc>, control, originTx ?? tx, activityMessages, cache)
res = res.concat( res = res.concat(
await updateCollaboratorsMixin(tx as TxMixin<Doc, Collaborators>, control, activityMessages, originTx ?? tx) await updateCollaboratorsMixin(
tx as TxMixin<Doc, Collaborators>,
control,
activityMessages,
originTx ?? tx,
cache
)
) )
return res return res
} }
case core.class.TxCollectionCUD: case core.class.TxCollectionCUD:
return await collectionCollabDoc(tx as TxCollectionCUD<Doc, AttachedDoc>, control, activityMessages) return await collectionCollabDoc(tx as TxCollectionCUD<Doc, AttachedDoc>, control, activityMessages, cache)
} }
return [] return []
} }
async function OnChatMessageCreate (tx: TxCollectionCUD<Doc, ChatMessage>, control: TriggerControl): Promise<Tx[]> {
const createTx = TxProcessor.extractTx(tx) as TxCreateDoc<ChatMessage>
const message = (await control.findAll(chunter.class.ChatMessage, { _id: createTx.objectId }))[0]
return await createCollaboratorNotifications(control.ctx, tx, control, [message])
}
/** /**
* @public * @public
*/ */
@ -1314,7 +1346,6 @@ export * from './utils'
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => ({ export default async () => ({
trigger: { trigger: {
OnChatMessageCreate,
OnAttributeCreate, OnAttributeCreate,
OnAttributeUpdate, OnAttributeUpdate,
OnActivityNotificationViewed, OnActivityNotificationViewed,

View File

@ -31,3 +31,9 @@ export interface NotifyResult {
push: boolean push: boolean
emails: BaseNotificationType[] emails: BaseNotificationType[]
} }
export interface NotifyParams {
isOwn: boolean
isSpace: boolean
shouldUpdateTimestamp: boolean
}

View File

@ -309,7 +309,8 @@ export function getTextPresenter (_class: Ref<Class<Doc>>, hierarchy: Hierarchy)
async function getFallbackNotificationFullfillment ( async function getFallbackNotificationFullfillment (
object: Doc, object: Doc,
originTx: TxCUD<Doc>, originTx: TxCUD<Doc>,
control: TriggerControl control: TriggerControl,
cache: Map<Ref<Doc>, Doc>
): Promise<NotificationContent> { ): Promise<NotificationContent> {
const title: IntlString = notification.string.CommonNotificationTitle const title: IntlString = notification.string.CommonNotificationTitle
let body: IntlString = notification.string.CommonNotificationBody let body: IntlString = notification.string.CommonNotificationBody
@ -324,9 +325,10 @@ async function getFallbackNotificationFullfillment (
const account = control.modelDb.getObject(originTx.modifiedBy) as PersonAccount const account = control.modelDb.getObject(originTx.modifiedBy) as PersonAccount
if (account !== undefined) { if (account !== undefined) {
const senderPerson = await findPersonForAccount(control, account.person) const senderPerson = (cache.get(account.person) as Person) ?? (await findPersonForAccount(control, account.person))
if (senderPerson !== undefined) { if (senderPerson !== undefined) {
intlParams.senderName = formatName(senderPerson.name) intlParams.senderName = formatName(senderPerson.name)
cache.set(senderPerson._id, senderPerson)
} }
} }
@ -364,12 +366,14 @@ export async function getNotificationContent (
originTx: TxCUD<Doc>, originTx: TxCUD<Doc>,
targetUser: Ref<Account>, targetUser: Ref<Account>,
object: Doc, object: Doc,
control: TriggerControl control: TriggerControl,
cache: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
): Promise<NotificationContent> { ): Promise<NotificationContent> {
let { title, body, intlParams, intlParamsNotLocalized } = await getFallbackNotificationFullfillment( let { title, body, intlParams, intlParamsNotLocalized } = await getFallbackNotificationFullfillment(
object, object,
originTx, originTx,
control control,
cache
) )
const actualTx = TxProcessor.extractTx(originTx) const actualTx = TxProcessor.extractTx(originTx)

View File

@ -150,7 +150,6 @@ export default plugin(serverNotificationId, {
OnAttributeCreate: '' as Resource<TriggerFunc>, OnAttributeCreate: '' as Resource<TriggerFunc>,
OnAttributeUpdate: '' as Resource<TriggerFunc>, OnAttributeUpdate: '' as Resource<TriggerFunc>,
OnReactionChanged: '' as Resource<TriggerFunc>, OnReactionChanged: '' as Resource<TriggerFunc>,
OnChatMessageCreate: '' as Resource<TriggerFunc>,
OnActivityNotificationViewed: '' as Resource<TriggerFunc>, OnActivityNotificationViewed: '' as Resource<TriggerFunc>,
OnDocRemove: '' as Resource<TriggerFunc> OnDocRemove: '' as Resource<TriggerFunc>
}, },

View File

@ -113,7 +113,17 @@ async function getRequestNotificationTx (tx: TxCollectionCUD<Doc, Request>, cont
}) })
for (const target of collaborators) { for (const target of collaborators) {
const txes = await getNotificationTxes(control, request, tx.tx, tx, target, true, false, notifyContexts, messages) const txes = await getNotificationTxes(
control,
request,
tx.tx,
tx,
target,
{ isOwn: true, isSpace: false, shouldUpdateTimestamp: true },
notifyContexts,
messages,
new Map()
)
res.push(...txes) res.push(...txes)
} }