Fix multiple mention notifications on edit doc (#5460)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina 2024-04-24 20:18:55 +04:00 committed by GitHub
parent e1c9523d4d
commit 109985333f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 105 additions and 17 deletions

View File

@ -259,6 +259,12 @@ export class TReplyProvider extends TDoc implements ReplyProvider {
function!: Resource<(message: ActivityMessage) => Promise<void>> function!: Resource<(message: ActivityMessage) => Promise<void>>
} }
@Model(activity.class.UserMentionInfo, core.class.AttachedDoc, DOMAIN_ACTIVITY)
export class TUserMentionInfo extends TAttachedDoc {
user!: Ref<Person>
content!: string
}
export function createModel (builder: Builder): void { export function createModel (builder: Builder): void {
builder.createModel( builder.createModel(
TTxViewlet, TTxViewlet,
@ -276,7 +282,8 @@ export function createModel (builder: Builder): void {
TIgnoreActivity, TIgnoreActivity,
TActivityReference, TActivityReference,
TActivityMessagePreview, TActivityMessagePreview,
TReplyProvider TReplyProvider,
TUserMentionInfo
) )
builder.mixin(activity.class.DocUpdateMessage, core.class.Class, view.mixin.ObjectPresenter, { builder.mixin(activity.class.DocUpdateMessage, core.class.Class, view.mixin.ObjectPresenter, {

View File

@ -302,6 +302,11 @@ export interface ReplyProvider extends Doc {
function: Resource<(message: ActivityMessage) => Promise<void>> function: Resource<(message: ActivityMessage) => Promise<void>>
} }
export interface UserMentionInfo extends AttachedDoc {
user: Ref<Person>
content: string
}
/** /**
* @public * @public
*/ */
@ -328,7 +333,8 @@ export default plugin(activityId, {
Reaction: '' as Ref<Class<Reaction>>, Reaction: '' as Ref<Class<Reaction>>,
SavedMessage: '' as Ref<Class<SavedMessage>>, SavedMessage: '' as Ref<Class<SavedMessage>>,
ActivityReference: '' as Ref<Class<ActivityReference>>, ActivityReference: '' as Ref<Class<ActivityReference>>,
ReplyProvider: '' as Ref<Class<ReplyProvider>> ReplyProvider: '' as Ref<Class<ReplyProvider>>,
UserMentionInfo: '' as Ref<Class<UserMentionInfo>>
}, },
icon: { icon: {
Activity: '' as Asset, Activity: '' as Asset,

View File

@ -36,17 +36,35 @@ import core, {
Type Type
} from '@hcengineering/core' } from '@hcengineering/core'
import notification, { MentionInboxNotification } from '@hcengineering/notification' import notification, { MentionInboxNotification } from '@hcengineering/notification'
import { extractReferences, markupToPmNode, pmNodeToMarkup, yDocContentToNodes } from '@hcengineering/text' import {
extractReferences,
markupToPmNode,
pmNodeToMarkup,
yDocContentToNodes,
areEqualJson
} from '@hcengineering/text'
import { StorageAdapter, TriggerControl } from '@hcengineering/server-core' import { StorageAdapter, TriggerControl } from '@hcengineering/server-core'
import activity, { ActivityMessage, ActivityReference } from '@hcengineering/activity' import activity, { ActivityMessage, ActivityReference, UserMentionInfo } from '@hcengineering/activity'
import contact, { Person, PersonAccount } from '@hcengineering/contact' import contact, { Person, PersonAccount } from '@hcengineering/contact'
import { import {
getPushCollaboratorTx,
getCommonNotificationTxes, getCommonNotificationTxes,
getPushCollaboratorTx,
isMessageAlreadyNotified, isMessageAlreadyNotified,
shouldNotifyCommon shouldNotifyCommon
} from '@hcengineering/server-notification-resources' } from '@hcengineering/server-notification-resources'
async function getPersonAccount (person: Ref<Person>, control: TriggerControl): Promise<PersonAccount | undefined> {
return (
await control.modelDb.findAll(
contact.class.PersonAccount,
{
person
},
{ limit: 1 }
)
)[0]
}
export function isDocMentioned (doc: Ref<Doc>, content: string | Buffer): boolean { export function isDocMentioned (doc: Ref<Doc>, content: string | Buffer): boolean {
const references = [] const references = []
@ -76,15 +94,8 @@ export async function getPersonNotificationTxes (
space: Ref<Space>, space: Ref<Space>,
originTx: TxCUD<Doc> originTx: TxCUD<Doc>
): Promise<Tx[]> { ): Promise<Tx[]> {
const receiver = ( const receiverPerson = reference.attachedTo as Ref<Person>
await control.modelDb.findAll( const receiver = await getPersonAccount(receiverPerson, control)
contact.class.PersonAccount,
{
person: reference.attachedTo as Ref<Person>
},
{ limit: 1 }
)
)[0]
if (receiver === undefined) { if (receiver === undefined) {
return [] return []
@ -111,6 +122,31 @@ export async function getPersonNotificationTxes (
return res return res
} }
const info = (
await control.findAll<UserMentionInfo>(activity.class.UserMentionInfo, {
user: receiverPerson,
attachedTo: reference.attachedDocId
})
)[0]
if (info === undefined) {
res.push(
control.txFactory.createTxCreateDoc(activity.class.UserMentionInfo, space, {
attachedTo: reference.attachedDocId ?? reference.srcDocId,
attachedToClass: reference.attachedDocClass ?? reference.srcDocClass,
user: receiverPerson,
content: reference.message,
collection: 'mentions'
})
)
} else {
res.push(
control.txFactory.createTxUpdateDoc(info._class, info.space, info._id, {
content: reference.message
})
)
}
const data: Partial<Data<MentionInboxNotification>> = { const data: Partial<Data<MentionInboxNotification>> = {
header: activity.string.MentionedYouIn, header: activity.string.MentionedYouIn,
messageHtml: reference.message, messageHtml: reference.message,
@ -274,7 +310,7 @@ async function getCreateReferencesTxes (
? (srcDocId as Ref<Space>) ? (srcDocId as Ref<Space>)
: srcDocSpace : srcDocSpace
return await getReferencesTxes(control, txFactory, refs, refSpace, [], originTx) return await getReferencesTxes(control, txFactory, refs, refSpace, [], [], originTx)
} }
async function getUpdateReferencesTxes ( async function getUpdateReferencesTxes (
@ -330,12 +366,15 @@ async function getUpdateReferencesTxes (
attachedDocId, attachedDocId,
collection: 'references' collection: 'references'
}) })
const userMentions = await control.findAll(activity.class.UserMentionInfo, {
attachedTo: attachedDocId
})
const refSpace: Ref<Space> = control.hierarchy.isDerived(srcDocClass, core.class.Space) const refSpace: Ref<Space> = control.hierarchy.isDerived(srcDocClass, core.class.Space)
? (srcDocId as Ref<Space>) ? (srcDocId as Ref<Space>)
: srcDocSpace : srcDocSpace
return await getReferencesTxes(control, txFactory, references, refSpace, current, originTx) return await getReferencesTxes(control, txFactory, references, refSpace, current, userMentions, originTx)
} }
return [] return []
@ -402,6 +441,7 @@ async function getReferencesTxes (
references: Data<ActivityReference>[], references: Data<ActivityReference>[],
space: Ref<Space>, space: Ref<Space>,
current: ActivityReference[], current: ActivityReference[],
mentions: UserMentionInfo[],
originTx: TxCUD<Doc> originTx: TxCUD<Doc>
): Promise<Tx[]> { ): Promise<Tx[]> {
const txes: Tx[] = [] const txes: Tx[] = []
@ -428,6 +468,24 @@ async function getReferencesTxes (
} }
} }
for (const mention of mentions) {
const refIndex = references.findIndex(
(r) => mention.user === r.attachedTo && mention.attachedTo === r.attachedDocId
)
const ref = references[refIndex]
if (refIndex !== -1) {
const alreadyProcessed = areEqualJson(JSON.parse(mention.content), JSON.parse(ref.message))
if (alreadyProcessed) {
references.splice(refIndex, 1)
}
} else {
txes.push(txFactory.createTxRemoveDoc(mention._class, mention.space, mention._id))
}
}
// Add missing references // Add missing references
for (const ref of references) { for (const ref of references) {
txes.push(...(await createReferenceTxes(control, txFactory, ref, space, originTx))) txes.push(...(await createReferenceTxes(control, txFactory, ref, space, originTx)))
@ -447,11 +505,28 @@ async function getRemoveActivityReferenceTxes (
collection: 'references' collection: 'references'
}) })
const mentions = await control.findAll(activity.class.UserMentionInfo, {
attachedTo: removedDocId
})
for (const ref of refs) { for (const ref of refs) {
const removeTx = txFactory.createTxRemoveDoc(ref._class, ref.space, ref._id) const removeTx = txFactory.createTxRemoveDoc(ref._class, ref.space, ref._id)
txes.push(txFactory.createTxCollectionCUD(ref.attachedToClass, ref.attachedTo, ref.space, ref.collection, removeTx)) txes.push(txFactory.createTxCollectionCUD(ref.attachedToClass, ref.attachedTo, ref.space, ref.collection, removeTx))
} }
for (const mention of mentions) {
const removeTx = txFactory.createTxRemoveDoc(mention._class, mention.space, mention._id)
txes.push(
txFactory.createTxCollectionCUD(
mention.attachedToClass,
mention.attachedTo,
mention.space,
mention.collection,
removeTx
)
)
}
return txes return txes
} }
@ -559,7 +634,7 @@ async function ActivityReferenceRemove (tx: Tx, control: TriggerControl): Promis
let hasMarkdown = false let hasMarkdown = false
for (const attr of attributes.values()) { for (const attr of attributes.values()) {
if (isMarkupType(attr.type._class)) { if (isMarkupType(attr.type._class) || isCollaborativeType(attr.type._class)) {
hasMarkdown = true hasMarkdown = true
break break
} }