UBERF-5811: rework backlinks (#4887)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina
2024-03-07 13:53:07 +07:00
committed by GitHub
parent 049c3125b6
commit 734177f3ea
72 changed files with 1552 additions and 1412 deletions
+115 -175
View File
@@ -14,7 +14,7 @@
// limitations under the License.
//
import chunter, { Backlink, ChatMessage } from '@hcengineering/chunter'
import chunter, { ChatMessage } from '@hcengineering/chunter'
import contact, { Employee, formatName, Person, PersonAccount } from '@hcengineering/contact'
import core, {
Account,
@@ -27,7 +27,6 @@ import core, {
Data,
Doc,
DocumentUpdate,
Hierarchy,
MeasureContext,
MixinUpdate,
Ref,
@@ -45,11 +44,12 @@ import core, {
} from '@hcengineering/core'
import notification, {
ActivityInboxNotification,
BaseNotificationType,
ClassCollaborators,
Collaborators,
CommonInboxNotification,
DocNotifyContext,
InboxNotification,
NotificationProvider,
NotificationType
} from '@hcengineering/notification'
import { getMetadata, getResource } from '@hcengineering/platform'
@@ -57,177 +57,103 @@ import type { TriggerControl } from '@hcengineering/server-core'
import serverNotification, {
getEmployee,
getPersonAccount,
getPersonAccountById,
HTMLPresenter,
TextPresenter
getPersonAccountById
} from '@hcengineering/server-notification'
import activity, { ActivityMessage } from '@hcengineering/activity'
import { Content } from './types'
import { Content, NotifyResult } from './types'
import {
getHTMLPresenter,
getNotificationContent,
getTextPresenter,
isAllowed,
isMixinTx,
isShouldNotify,
isShouldNotifyTx,
isUserEmployeeInFieldValue,
isUserInFieldValue,
replaceAll,
updateNotifyContextsSpace
} from './utils'
export async function OnBacklinkCreate (
originTx: TxCollectionCUD<Doc, AttachedDoc>,
control: TriggerControl
): Promise<Tx[]> {
const hierarchy = control.hierarchy
const isTxCorrect = await isBacklinkCreated(originTx, hierarchy, control)
export function getPushCollaboratorTx (
control: TriggerControl,
user: Ref<Account>,
doc: Doc
): TxMixin<Doc, Doc> | undefined {
const mixin = control.hierarchy.as(doc, notification.mixin.Collaborators)
if (!isTxCorrect) {
return []
}
const tx = originTx as TxCollectionCUD<Doc, Backlink>
const receiver = await getPersonAccount(tx.objectId as Ref<Employee>, control)
if (receiver === undefined) {
return []
}
const sender = await getPersonAccountById(tx.modifiedBy, control)
if (sender === undefined) {
return []
}
if (sender === receiver) {
return []
}
const backlink = TxProcessor.createDoc2Doc(tx.tx as TxCreateDoc<Backlink>)
if (!hierarchy.isDerived(backlink.backlinkClass, activity.class.ActivityMessage)) {
return []
}
const message = (
await control.findAll<ActivityMessage>(
backlink.backlinkClass,
{
_id: backlink.backlinkId as Ref<ActivityMessage>
},
{ limit: 1 }
)
)[0]
if (message === undefined) {
return []
}
const res: Tx[] = []
const collabMixin = hierarchy.as(message as Doc, notification.mixin.Collaborators)
if (collabMixin.collaborators === undefined || !collabMixin.collaborators.includes(receiver._id)) {
const collabTx = control.txFactory.createTxMixin(
message._id,
message._class,
message.space,
notification.mixin.Collaborators,
{
$push: {
collaborators: receiver._id
}
if (mixin.collaborators === undefined || !mixin.collaborators.includes(user)) {
return control.txFactory.createTxMixin(doc._id, doc._class, doc.space, notification.mixin.Collaborators, {
$push: {
collaborators: user
}
)
res.push(collabTx)
})
}
return res
return undefined
}
async function isBacklinkNotified (tx: TxCollectionCUD<Doc, Backlink>, control: TriggerControl): Promise<boolean> {
const receiver = await getPersonAccount(tx.objectId as Ref<Employee>, control)
if (receiver === undefined) {
return false
}
const { hierarchy } = control
const backlink = TxProcessor.createDoc2Doc(tx.tx as TxCreateDoc<Backlink>)
if (!hierarchy.isDerived(backlink.backlinkClass, activity.class.ActivityMessage)) {
return false
}
export async function isMessageAlreadyNotified (
_id: Ref<ActivityMessage>,
user: Ref<Account>,
control: TriggerControl
): Promise<boolean> {
const exists = await control.findAll(
notification.class.ActivityInboxNotification,
{ attachedTo: backlink.backlinkId as Ref<ActivityMessage>, user: receiver._id },
{ limit: 1 }
{ attachedTo: _id, user },
{ limit: 1, projection: { _id: 1 } }
)
return exists.length > 0
}
async function isAlreadyNotified (originTx: TxCUD<Doc>, control: TriggerControl): Promise<boolean> {
if (originTx._class !== core.class.TxCollectionCUD) {
return false
}
const hierarchy = control.hierarchy
const isBacklink = await isBacklinkCreated(originTx as TxCollectionCUD<Doc, AttachedDoc>, hierarchy, control)
if (!isBacklink) {
return false
}
return await isBacklinkNotified(originTx as TxCollectionCUD<Doc, Backlink>, control)
}
async function isBacklinkCreated (
ptx: TxCollectionCUD<Doc, AttachedDoc>,
hierarchy: Hierarchy,
control: TriggerControl
): Promise<boolean> {
if (ptx.tx._class !== core.class.TxCreateDoc || !hierarchy.isDerived(ptx.tx.objectClass, chunter.class.Backlink)) {
return false
}
if (ptx.objectClass === contact.class.Person) {
// We need to check if person is employee.
const [person] = await control.findAll(contact.class.Person, { _id: ptx.objectId as Ref<Person> })
return person !== undefined ? hierarchy.hasMixin(person, contact.mixin.Employee) : false
}
return true
}
/**
* @public
*/
export async function isAllowed (
export async function getCommonNotificationTxes (
control: TriggerControl,
receiver: Ref<PersonAccount>,
typeId: Ref<NotificationType>,
providerId: Ref<NotificationProvider>
): Promise<boolean> {
const setting = (
await control.findAll(
notification.class.NotificationSetting,
{
attachedTo: providerId,
type: typeId,
modifiedBy: receiver
},
{ limit: 1 }
doc: Doc,
data: Partial<Data<CommonInboxNotification>>,
receiver: Ref<Account>,
sender: Ref<Account>,
attachedTo: Ref<Doc>,
attachedToClass: Ref<Class<Doc>>,
space: Ref<Space>,
modifiedOn: Timestamp,
notifyResult: NotifyResult
): Promise<Tx[]> {
const res: Tx[] = []
if (notifyResult.allowed) {
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, { attachedTo })
await pushInboxNotifications(
control,
res,
receiver,
attachedTo,
attachedToClass,
space,
notifyContexts,
data,
notification.class.CommonInboxNotification,
modifiedOn
)
)[0]
if (setting !== undefined) {
return setting.enabled
}
const type = (
await control.modelDb.findAll(notification.class.NotificationType, {
_id: typeId
})
)[0]
if (type === undefined) return false
return type.providers[providerId] ?? false
if (notifyResult.emails.length === 0) {
return res
}
const receiverAccount = await getPersonAccountById(receiver, control)
if (receiverAccount === undefined) {
return res
}
const emp = await getEmployee(receiverAccount.person as Ref<Employee>, control)
if (emp?.active === true) {
for (const type of notifyResult.emails) {
await notifyByEmail(control, type._id, doc, sender as Ref<PersonAccount>, receiverAccount._id)
}
}
return res
}
async function getTextPart (doc: Doc, control: TriggerControl): Promise<string | undefined> {
@@ -243,20 +169,6 @@ async function getHtmlPart (doc: Doc, control: TriggerControl): Promise<string |
return HTMLPresenter != null ? await (await getResource(HTMLPresenter.presenter))(doc, control) : undefined
}
/**
* @public
*/
export function getHTMLPresenter (_class: Ref<Class<Doc>>, hierarchy: Hierarchy): HTMLPresenter | undefined {
return hierarchy.classHierarchyMixin(_class, serverNotification.mixin.HTMLPresenter)
}
/**
* @public
*/
export function getTextPresenter (_class: Ref<Class<Doc>>, hierarchy: Hierarchy): TextPresenter | undefined {
return hierarchy.classHierarchyMixin(_class, serverNotification.mixin.TextPresenter)
}
function fillTemplate (template: string, sender: string, doc: string, data: string): string {
let res = replaceAll(template, '{sender}', sender)
res = replaceAll(res, '{doc}', doc)
@@ -270,7 +182,7 @@ function fillTemplate (template: string, sender: string, doc: string, data: stri
export async function getContent (
doc: Doc | undefined,
sender: string,
type: Ref<NotificationType>,
type: Ref<BaseNotificationType>,
control: TriggerControl,
data: string
): Promise<Content | undefined> {
@@ -293,7 +205,7 @@ export async function getContent (
async function notifyByEmail (
control: TriggerControl,
type: Ref<NotificationType>,
type: Ref<BaseNotificationType>,
doc: Doc | undefined,
senderId: Ref<PersonAccount>,
receiverId: Ref<PersonAccount>,
@@ -553,7 +465,7 @@ export async function getNotificationTxes (
shouldUpdateTimestamp = true
): Promise<Tx[]> {
const res: Tx[] = []
const notifyResult = await isShouldNotify(control, tx, originTx, object, target, isOwn, isSpace)
const notifyResult = await isShouldNotifyTx(control, tx, originTx, object, target, isOwn, isSpace)
if (notifyResult.allowed) {
await pushActivityInboxNotifications(
@@ -834,11 +746,37 @@ async function removeCollaboratorDoc (tx: TxRemoveDoc<Doc>, control: TriggerCont
}
const res: Tx[] = []
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, { attachedTo: tx.objectId })
const notifyContexts = await control.findAll(
notification.class.DocNotifyContext,
{ attachedTo: tx.objectId },
{
projection: {
_id: 1,
_class: 1,
space: 1
}
}
)
if (notifyContexts.length === 0) {
return []
}
const notifyContextRefs = notifyContexts.map(({ _id }) => _id)
const inboxNotifications = await control.findAll(notification.class.InboxNotification, {
docNotifyContext: { $in: notifyContextRefs }
})
const inboxNotifications = await control.findAll(
notification.class.InboxNotification,
{
docNotifyContext: { $in: notifyContextRefs }
},
{
projection: {
_id: 1,
_class: 1,
space: 1
}
}
)
inboxNotifications.forEach((notification) => {
res.push(control.txFactory.createTxRemoveDoc(notification._class, notification.space, notification._id))
@@ -1010,10 +948,6 @@ export async function createCollaboratorNotifications (
return []
}
if (await isAlreadyNotified(originTx ?? tx, control)) {
return []
}
switch (tx._class) {
case core.class.TxCreateDoc:
return await createCollaboratorDoc(tx as TxCreateDoc<Doc>, control, activityMessages, originTx ?? tx)
@@ -1025,8 +959,6 @@ export async function createCollaboratorNotifications (
)
return res
}
case core.class.TxRemoveDoc:
return await removeCollaboratorDoc(tx as TxRemoveDoc<Doc>, control)
case core.class.TxCollectionCUD:
return await collectionCollabDoc(tx as TxCollectionCUD<Doc, AttachedDoc>, control, activityMessages)
}
@@ -1118,6 +1050,14 @@ export async function getCollaborators (
}
}
async function OnDocRemove (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
const etx = TxProcessor.extractTx(tx)
if (etx._class !== core.class.TxRemoveDoc) return []
return await removeCollaboratorDoc(tx as TxRemoveDoc<Doc>, control)
}
export * from './types'
export * from './utils'
@@ -1127,8 +1067,8 @@ export default async () => ({
OnChatMessageCreate,
OnAttributeCreate,
OnAttributeUpdate,
OnBacklinkCreate,
OnActivityNotificationViewed
OnActivityNotificationViewed,
OnDocRemove
},
function: {
IsUserInFieldValue: isUserInFieldValue,
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { NotificationType } from '@hcengineering/notification'
import { BaseNotificationType } from '@hcengineering/notification'
/**
* @public
@@ -28,5 +28,5 @@ export interface Content {
*/
export interface NotifyResult {
allowed: boolean
emails: NotificationType[]
emails: BaseNotificationType[]
}
@@ -12,7 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
import notification, { NotificationContent, NotificationType } from '@hcengineering/notification'
import notification, {
BaseNotificationType,
CommonNotificationType,
NotificationContent,
NotificationProvider,
NotificationType
} from '@hcengineering/notification'
import type { TriggerControl } from '@hcengineering/server-core'
import core, {
Account,
@@ -28,11 +34,16 @@ import core, {
TxMixin,
TxUpdateDoc
} from '@hcengineering/core'
import serverNotification, { getPersonAccountById, NotificationPresenter } from '@hcengineering/server-notification'
import serverNotification, {
getPersonAccountById,
HTMLPresenter,
NotificationPresenter,
TextPresenter
} from '@hcengineering/server-notification'
import { getResource, IntlString } from '@hcengineering/platform'
import contact, { formatName, Person, PersonAccount } from '@hcengineering/contact'
import { getTextPresenter, isAllowed, NotifyResult } from './index'
import { DocUpdateMessage } from '@hcengineering/activity'
import { NotifyResult } from './types'
/**
* @public
@@ -94,7 +105,60 @@ async function findPersonForAccount (control: TriggerControl, personId: Ref<Pers
return undefined
}
export async function isShouldNotify (
export async function shouldNotifyCommon (
control: TriggerControl,
user: Ref<Account>,
typeId: Ref<CommonNotificationType>
): Promise<NotifyResult> {
const type = (await control.modelDb.findAll(notification.class.CommonNotificationType, { _id: typeId }))[0]
const emailTypes: BaseNotificationType[] = []
let allowed = false
if (type === undefined) {
return { allowed, emails: emailTypes }
}
if (await isAllowed(control, user as Ref<PersonAccount>, type._id, notification.providers.PlatformNotification)) {
allowed = true
}
if (await isAllowed(control, user as Ref<PersonAccount>, type._id, notification.providers.EmailNotification)) {
emailTypes.push(type)
}
return { allowed, emails: emailTypes }
}
export async function isAllowed (
control: TriggerControl,
receiver: Ref<PersonAccount>,
typeId: Ref<BaseNotificationType>,
providerId: Ref<NotificationProvider>
): Promise<boolean> {
const setting = (
await control.findAll(
notification.class.NotificationSetting,
{
attachedTo: providerId,
type: typeId,
modifiedBy: receiver
},
{ limit: 1 }
)
)[0]
if (setting !== undefined) {
return setting.enabled
}
const type = (
await control.modelDb.findAll(notification.class.BaseNotificationType, {
_id: typeId
})
)[0]
if (type === undefined) return false
return type.providers[providerId] ?? false
}
export async function isShouldNotifyTx (
control: TriggerControl,
tx: TxCUD<Doc>,
originTx: TxCUD<Doc>,
@@ -233,6 +297,14 @@ export function isMixinTx (tx: TxCUD<Doc>): tx is TxMixin<Doc, Doc> {
return tx._class === core.class.TxMixin
}
export function getHTMLPresenter (_class: Ref<Class<Doc>>, hierarchy: Hierarchy): HTMLPresenter | undefined {
return hierarchy.classHierarchyMixin(_class, serverNotification.mixin.HTMLPresenter)
}
export function getTextPresenter (_class: Ref<Class<Doc>>, hierarchy: Hierarchy): TextPresenter | undefined {
return hierarchy.classHierarchyMixin(_class, serverNotification.mixin.TextPresenter)
}
async function getFallbackNotificationFullfillment (
object: Doc,
originTx: TxCUD<Doc>,