mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-03 00:43:59 +03:00
Rework triggers with arrays (#7168)
This commit is contained in:
parent
3837e68589
commit
e493d35ac6
@ -44,13 +44,11 @@ export function createModel (builder: Builder): void {
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverActivity.trigger.ActivityMessagesHandler,
|
||||
arrays: true,
|
||||
isAsync: true
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverActivity.trigger.OnDocRemoved,
|
||||
arrays: true
|
||||
trigger: serverActivity.trigger.OnDocRemoved
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
|
@ -37,7 +37,6 @@ export function createModel (builder: Builder): void {
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverAiBot.trigger.OnMessageSend,
|
||||
arrays: true,
|
||||
isAsync: true
|
||||
})
|
||||
|
||||
|
@ -23,7 +23,6 @@ export { serverCollaborationId } from '@hcengineering/server-collaboration'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverCollaboration.trigger.OnDelete,
|
||||
arrays: true
|
||||
trigger: serverCollaboration.trigger.OnDelete
|
||||
})
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import serverFulltext from '@hcengineering/server-fulltext'
|
||||
export { serverFulltextId } from '@hcengineering/server-fulltext'
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverFulltext.trigger.OnChange,
|
||||
arrays: true
|
||||
trigger: serverFulltext.trigger.OnChange
|
||||
})
|
||||
}
|
||||
|
@ -89,8 +89,7 @@ export function createModel (builder: Builder): void {
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverNotification.trigger.OnDocRemove,
|
||||
arrays: true
|
||||
trigger: serverNotification.trigger.OnDocRemove
|
||||
})
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
|
@ -25,8 +25,7 @@ export { serverRequestId } from '@hcengineering/server-request'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverRequest.trigger.OnRequest,
|
||||
arrays: true
|
||||
trigger: serverRequest.trigger.OnRequest
|
||||
})
|
||||
|
||||
builder.mixin(request.class.Request, core.class.Class, serverNotification.mixin.TextPresenter, {
|
||||
|
@ -23,8 +23,7 @@ export { serverTagsId } from '@hcengineering/server-tags'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverTags.trigger.onTagReference,
|
||||
arrays: true
|
||||
trigger: serverTags.trigger.onTagReference
|
||||
})
|
||||
|
||||
builder.mixin<Class<Doc>, ObjectDDParticipant>(
|
||||
|
@ -22,7 +22,6 @@ export { serverTaskId } from '@hcengineering/server-task'
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverTask.trigger.OnStateUpdate,
|
||||
arrays: true
|
||||
trigger: serverTask.trigger.OnStateUpdate
|
||||
})
|
||||
}
|
||||
|
@ -38,7 +38,6 @@ export function createModel (builder: Builder): void {
|
||||
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverTime.trigger.OnTask,
|
||||
arrays: true,
|
||||
isAsync: true
|
||||
})
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import { BackupClient, DocChunk } from './backup'
|
||||
import { Account, AttachedDoc, Class, DOMAIN_MODEL, Doc, Domain, Ref, Timestamp } from './classes'
|
||||
import core from './component'
|
||||
@ -249,12 +250,10 @@ export async function createClient (
|
||||
}
|
||||
lastTx = tx.reduce((cur, it) => (it.modifiedOn > cur ? it.modifiedOn : cur), 0)
|
||||
}
|
||||
const conn = await ctx.with('connect', {}, async () => await connect(txHandler))
|
||||
const conn = await ctx.with('connect', {}, () => connect(txHandler))
|
||||
|
||||
await ctx.with(
|
||||
'load-model',
|
||||
{ reload: false },
|
||||
async (ctx) => await loadModel(ctx, conn, modelFilter, hierarchy, model, false, txPersistence)
|
||||
await ctx.with('load-model', { reload: false }, (ctx) =>
|
||||
loadModel(ctx, conn, modelFilter, hierarchy, model, false, txPersistence)
|
||||
)
|
||||
|
||||
txBuffer = txBuffer.filter((tx) => tx.space !== core.space.Model)
|
||||
@ -273,10 +272,8 @@ export async function createClient (
|
||||
return
|
||||
}
|
||||
// Find all new transactions and apply
|
||||
const loadModelResponse = await ctx.with(
|
||||
'connect',
|
||||
{ reload: true },
|
||||
async (ctx) => await loadModel(ctx, conn, modelFilter, hierarchy, model, true, txPersistence)
|
||||
const loadModelResponse = await ctx.with('connect', { reload: true }, (ctx) =>
|
||||
loadModel(ctx, conn, modelFilter, hierarchy, model, true, txPersistence)
|
||||
)
|
||||
|
||||
if (event === ClientConnectEvent.Reconnected && loadModelResponse.full) {
|
||||
@ -284,9 +281,7 @@ export async function createClient (
|
||||
hierarchy = new Hierarchy()
|
||||
model = new ModelDb(hierarchy)
|
||||
|
||||
await ctx.with('build-model', {}, async (ctx) => {
|
||||
await buildModel(ctx, loadModelResponse, modelFilter, hierarchy, model)
|
||||
})
|
||||
await ctx.with('build-model', {}, (ctx) => buildModel(ctx, loadModelResponse, modelFilter, hierarchy, model))
|
||||
await oldOnConnect?.(ClientConnectEvent.Upgraded)
|
||||
|
||||
// No need to fetch more stuff since upgrade was happened.
|
||||
@ -300,11 +295,8 @@ export async function createClient (
|
||||
}
|
||||
|
||||
// We need to look for last {transactionThreshold} transactions and if it is more since lastTx one we receive, we need to perform full refresh.
|
||||
const atxes = await ctx.with(
|
||||
'find-atx',
|
||||
{},
|
||||
async () =>
|
||||
await conn.findAll(
|
||||
const atxes = await ctx.with('find-atx', {}, () =>
|
||||
conn.findAll(
|
||||
core.class.Tx,
|
||||
{ modifiedOn: { $gt: lastTx }, objectSpace: { $ne: core.space.Model } },
|
||||
{ sort: { modifiedOn: SortingOrder.Ascending, _id: SortingOrder.Ascending }, limit: transactionThreshold }
|
||||
@ -364,12 +356,16 @@ async function tryLoadModel (
|
||||
}
|
||||
|
||||
// Save concatenated
|
||||
void (await ctx.with('persistence-store', {}, (ctx) =>
|
||||
void ctx
|
||||
.with('persistence-store', {}, (ctx) =>
|
||||
persistence?.store({
|
||||
...result,
|
||||
transactions: !result.full ? current.transactions.concat(result.transactions) : result.transactions
|
||||
})
|
||||
))
|
||||
)
|
||||
.catch((err) => {
|
||||
Analytics.handleError(err)
|
||||
})
|
||||
|
||||
if (!result.full && !reload) {
|
||||
result.transactions = current.transactions.concat(result.transactions)
|
||||
@ -432,7 +428,7 @@ async function buildModel (
|
||||
|
||||
const atxes = modelResponse.transactions
|
||||
|
||||
await ctx.with('split txes', {}, async () => {
|
||||
ctx.withSync('split txes', {}, () => {
|
||||
atxes.forEach((tx) =>
|
||||
((tx.modifiedBy === core.account.ConfigUser || tx.modifiedBy === core.account.System) && !isPersonAccount(tx)
|
||||
? systemTx
|
||||
@ -448,7 +444,7 @@ async function buildModel (
|
||||
txes = await modelFilter(txes)
|
||||
}
|
||||
|
||||
await ctx.with('build hierarchy', {}, async () => {
|
||||
ctx.withSync('build hierarchy', {}, () => {
|
||||
for (const tx of txes) {
|
||||
try {
|
||||
hierarchy.tx(tx)
|
||||
@ -461,7 +457,7 @@ async function buildModel (
|
||||
}
|
||||
}
|
||||
})
|
||||
await ctx.with('build model', {}, async (ctx) => {
|
||||
ctx.withSync('build model', {}, (ctx) => {
|
||||
model.addTxes(ctx, txes, false)
|
||||
})
|
||||
}
|
||||
|
@ -47,6 +47,8 @@ const consoleLogger = (logParams: Record<string, any>): MeasureLogger => ({
|
||||
|
||||
const noParamsLogger = consoleLogger({})
|
||||
|
||||
const nullPromise = Promise.resolve()
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -148,6 +150,9 @@ export class MeasureMetricsContext implements MeasureContext {
|
||||
c.end()
|
||||
})
|
||||
} else {
|
||||
if (value == null) {
|
||||
return nullPromise as Promise<T>
|
||||
}
|
||||
return Promise.resolve(value)
|
||||
}
|
||||
} finally {
|
||||
|
@ -104,8 +104,8 @@ export function updateMeasure (
|
||||
param.value += value ?? ed - st
|
||||
param.operations++
|
||||
}
|
||||
|
||||
param.topResult = getUpdatedTopResult(param.topResult, ed - st, fParams)
|
||||
// Do not update top results for params.
|
||||
// param.topResult = getUpdatedTopResult(param.topResult, ed - st, fParams)
|
||||
}
|
||||
// Update leaf data
|
||||
if (override === true) {
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Account, Doc, Domain, Ref } from './classes'
|
||||
import type { Account, Doc, DocIndexState, Domain, Ref } from './classes'
|
||||
import { MeasureContext } from './measurements'
|
||||
import { DocumentQuery, FindOptions } from './storage'
|
||||
import type { DocumentUpdate, Tx } from './tx'
|
||||
@ -58,7 +58,7 @@ export interface SessionData {
|
||||
workspace: WorkspaceIdWithUrl
|
||||
branding: Branding | null
|
||||
|
||||
needWarmupFulltext?: boolean
|
||||
fulltextUpdates?: Map<Ref<DocIndexState>, DocIndexState>
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1317,16 +1317,10 @@ export class LiveQuery implements WithTx, Client {
|
||||
return result
|
||||
}
|
||||
|
||||
triggerInProgress = true
|
||||
|
||||
private async checkUpdateEvents (evt: TxWorkspaceEvent, trigger = true): Promise<void> {
|
||||
if (this.triggerInProgress) {
|
||||
this.triggerInProgress = false
|
||||
const h = this.client.getHierarchy()
|
||||
function hasClass (q: Query, classes: Ref<Class<Doc>>[]): boolean {
|
||||
return (
|
||||
classes.includes(q._class) || classes.some((it) => h.isDerived(q._class, it) || h.isDerived(it, q._class))
|
||||
)
|
||||
return classes.includes(q._class) || classes.some((it) => h.isDerived(q._class, it) || h.isDerived(it, q._class))
|
||||
}
|
||||
if (evt.event === WorkspaceEvent.IndexingUpdate) {
|
||||
const indexingParam = evt.params as IndexingUpdateEvent
|
||||
@ -1391,11 +1385,6 @@ export class LiveQuery implements WithTx, Client {
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.triggerInProgress = true
|
||||
void this.checkUpdateEvents(evt, false)
|
||||
}, 20000)
|
||||
}
|
||||
}
|
||||
|
||||
private async changePrivateHandler (evt: TxWorkspaceEvent): Promise<void> {
|
||||
|
@ -62,7 +62,11 @@ export function serveStats (ctx: MeasureContext, onClose?: () => void): void {
|
||||
credentials: true
|
||||
})
|
||||
)
|
||||
app.use(bodyParser())
|
||||
app.use(
|
||||
bodyParser({
|
||||
jsonLimit: '150mb'
|
||||
})
|
||||
)
|
||||
|
||||
router.get('/api/v1/overview', (req, res) => {
|
||||
try {
|
||||
|
@ -53,7 +53,8 @@ import {
|
||||
import { ReferenceTrigger } from './references'
|
||||
import { getAttrName, getCollectionAttribute, getDocUpdateAction, getTxAttributesUpdates } from './utils'
|
||||
|
||||
export async function OnReactionChanged (originTx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnReactionChanged (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const originTx of txes) {
|
||||
const tx = originTx as TxCollectionCUD<ActivityMessage, Reaction>
|
||||
const innerTx = TxProcessor.extractTx(tx) as TxCUD<Reaction>
|
||||
|
||||
@ -61,13 +62,14 @@ export async function OnReactionChanged (originTx: Tx, control: TriggerControl):
|
||||
const txes = await createReactionNotifications(tx, control)
|
||||
|
||||
await control.apply(control.ctx, txes)
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
if (innerTx._class === core.class.TxRemoveDoc) {
|
||||
const txes = await removeReactionNotifications(tx, control)
|
||||
await control.apply(control.ctx, txes)
|
||||
return []
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
@ -304,11 +306,8 @@ export async function generateDocUpdateMessages (
|
||||
switch (tx._class) {
|
||||
case core.class.TxCreateDoc: {
|
||||
const doc = TxProcessor.createDoc2Doc(tx as TxCreateDoc<Doc>)
|
||||
return await ctx.with(
|
||||
'pushDocUpdateMessages',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await pushDocUpdateMessages(ctx, control, res, doc, originTx ?? tx, undefined, objectCache, controlRules)
|
||||
return await ctx.with('pushDocUpdateMessages', {}, (ctx) =>
|
||||
pushDocUpdateMessages(ctx, control, res, doc, originTx ?? tx, undefined, objectCache, controlRules)
|
||||
)
|
||||
}
|
||||
case core.class.TxMixin:
|
||||
@ -322,20 +321,8 @@ export async function generateDocUpdateMessages (
|
||||
doc = (await control.findAll(ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
|
||||
objectCache?.docs?.set(tx.objectId, doc)
|
||||
}
|
||||
return await ctx.with(
|
||||
'pushDocUpdateMessages',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await pushDocUpdateMessages(
|
||||
ctx,
|
||||
control,
|
||||
res,
|
||||
doc ?? undefined,
|
||||
originTx ?? tx,
|
||||
undefined,
|
||||
objectCache,
|
||||
controlRules
|
||||
)
|
||||
return await ctx.with('pushDocUpdateMessages', {}, (ctx) =>
|
||||
pushDocUpdateMessages(ctx, control, res, doc ?? undefined, originTx ?? tx, undefined, objectCache, controlRules)
|
||||
)
|
||||
}
|
||||
case core.class.TxCollectionCUD: {
|
||||
@ -363,11 +350,8 @@ export async function generateDocUpdateMessages (
|
||||
}
|
||||
if (doc !== undefined) {
|
||||
objectCache?.docs?.set(tx.objectId, doc)
|
||||
return await ctx.with(
|
||||
'pushDocUpdateMessages',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await pushDocUpdateMessages(
|
||||
return await ctx.with('pushDocUpdateMessages', {}, (ctx) =>
|
||||
pushDocUpdateMessages(
|
||||
ctx,
|
||||
control,
|
||||
res,
|
||||
|
@ -791,12 +791,17 @@ async function ActivityReferenceRemove (tx: Tx, etx: TxCUD<Doc>, control: Trigge
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function ReferenceTrigger (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function ReferenceTrigger (txes: TxCUD<Doc>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
|
||||
for (const tx of txes) {
|
||||
const etx = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
||||
if (control.hierarchy.isDerived(etx.objectClass, activity.class.ActivityReference)) return []
|
||||
if (control.hierarchy.isDerived(etx.objectClass, notification.class.InboxNotification)) return []
|
||||
if (control.hierarchy.isDerived(etx.objectClass, activity.class.ActivityReference)) {
|
||||
continue
|
||||
}
|
||||
if (control.hierarchy.isDerived(etx.objectClass, notification.class.InboxNotification)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (etx._class === core.class.TxCreateDoc) {
|
||||
result.push(...(await ActivityReferenceCreate(tx, etx, control)))
|
||||
@ -807,5 +812,6 @@ export async function ReferenceTrigger (tx: TxCUD<Doc>, control: TriggerControl)
|
||||
if (etx._class === core.class.TxRemoveDoc) {
|
||||
result.push(...(await ActivityReferenceRemove(tx, etx, control)))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
@ -278,7 +278,7 @@ export async function OnMessageSend (
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnMention (tx: TxCreateDoc<MentionInboxNotification>, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnMention (tx: TxCreateDoc<MentionInboxNotification>[], control: TriggerControl): Promise<Tx[]> {
|
||||
// Note: temporally commented until open ai will be added
|
||||
// if (tx.objectClass !== notification.class.MentionInboxNotification || tx._class !== core.class.TxCreateDoc) {
|
||||
// return []
|
||||
@ -308,7 +308,7 @@ export async function OnMention (tx: TxCreateDoc<MentionInboxNotification>, cont
|
||||
}
|
||||
|
||||
export async function OnMessageNotified (
|
||||
tx: TxCreateDoc<ActivityInboxNotification>,
|
||||
tx: TxCreateDoc<ActivityInboxNotification>[],
|
||||
control: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
// Note: temporally commented until open ai will be added
|
||||
@ -363,21 +363,22 @@ export async function OnMessageNotified (
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnUserStatus (originTx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnUserStatus (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const originTx of txes) {
|
||||
const tx = TxProcessor.extractTx(originTx) as TxCUD<UserStatus>
|
||||
|
||||
if (
|
||||
tx.objectClass !== core.class.UserStatus ||
|
||||
![core.class.TxCreateDoc, core.class.TxUpdateDoc].includes(tx._class)
|
||||
) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
if (tx._class === core.class.TxCreateDoc) {
|
||||
const createTx = tx as TxCreateDoc<UserStatus>
|
||||
const status = TxProcessor.createDoc2Doc(createTx)
|
||||
if (status.user === aiBot.account.AIBot || status.user === core.account.System || !status.online) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@ -385,22 +386,23 @@ export async function OnUserStatus (originTx: Tx, control: TriggerControl): Prom
|
||||
const updateTx = tx as TxUpdateDoc<UserStatus>
|
||||
const val = updateTx.operations.online
|
||||
if (val !== true) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
const status = (await control.findAll(control.ctx, core.class.UserStatus, { _id: updateTx.objectId }))[0]
|
||||
if (status === undefined || status.user === aiBot.account.AIBot || status.user === core.account.System) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const account = control.modelDb.findAllSync(contact.class.PersonAccount, { email: aiBotAccountEmail })[0]
|
||||
|
||||
if (account !== undefined) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
await createAccountRequest(control.workspace, control.ctx)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
@ -23,19 +23,24 @@ import type { TriggerControl } from '@hcengineering/server-core'
|
||||
* @public
|
||||
*/
|
||||
export async function OnAttachmentDelete (
|
||||
tx: Tx,
|
||||
txes: Tx[],
|
||||
{ removedMap, ctx, storageAdapter, workspace }: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
const toDelete: string[] = []
|
||||
for (const tx of txes) {
|
||||
const rmTx = TxProcessor.extractTx(tx) as TxRemoveDoc<Attachment>
|
||||
|
||||
// Obtain document being deleted.
|
||||
const attach = removedMap.get(rmTx.objectId) as Attachment
|
||||
|
||||
if (attach === undefined) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
toDelete.push(attach.file)
|
||||
}
|
||||
if (toDelete.length > 0) {
|
||||
await storageAdapter.remove(ctx, workspace, toDelete)
|
||||
}
|
||||
|
||||
await storageAdapter.remove(ctx, workspace, [attach.file])
|
||||
|
||||
return []
|
||||
}
|
||||
|
@ -83,11 +83,14 @@ export async function ReminderTextPresenter (doc: Doc, control: TriggerControl):
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnPersonAccountCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnPersonAccountCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<PersonAccount>
|
||||
const user = TxProcessor.createDoc2Doc(ctx)
|
||||
|
||||
const res: TxCreateDoc<Calendar> = control.txFactory.createTxCreateDoc(
|
||||
result.push(
|
||||
control.txFactory.createTxCreateDoc(
|
||||
calendar.class.Calendar,
|
||||
calendar.space.Calendar,
|
||||
{
|
||||
@ -99,7 +102,9 @@ export async function OnPersonAccountCreate (tx: Tx, control: TriggerControl): P
|
||||
undefined,
|
||||
user._id
|
||||
)
|
||||
return [res]
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function getCalendar (calendars: Calendar[], person: Ref<PersonAccount>): Ref<Calendar> | undefined {
|
||||
@ -118,17 +123,20 @@ function getEventPerson (current: Event, calendars: Calendar[], control: Trigger
|
||||
return acc.person
|
||||
}
|
||||
|
||||
async function OnEvent (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
async function OnEvent (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCUD<Event>
|
||||
if (ctx._class === core.class.TxCreateDoc) {
|
||||
return await onEventCreate(ctx as TxCreateDoc<Event>, control)
|
||||
result.push(...(await onEventCreate(ctx as TxCreateDoc<Event>, control)))
|
||||
} else if (ctx._class === core.class.TxUpdateDoc) {
|
||||
return await onEventUpdate(ctx as TxUpdateDoc<Event>, control)
|
||||
result.push(...(await onEventUpdate(ctx as TxUpdateDoc<Event>, control)))
|
||||
} else if (ctx._class === core.class.TxRemoveDoc) {
|
||||
return await onRemoveEvent(ctx as TxRemoveDoc<Event>, control)
|
||||
result.push(...(await onRemoveEvent(ctx as TxRemoveDoc<Event>, control)))
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
async function onEventUpdate (ctx: TxUpdateDoc<Event>, control: TriggerControl): Promise<Tx[]> {
|
||||
|
@ -203,16 +203,20 @@ async function OnChatMessageCreated (ctx: MeasureContext, tx: TxCUD<Doc>, contro
|
||||
return res
|
||||
}
|
||||
|
||||
async function ChatNotificationsHandler (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
||||
async function ChatNotificationsHandler (txes: TxCUD<Doc>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<ChatMessage>
|
||||
|
||||
if (actualTx._class !== core.class.TxCreateDoc) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
const chatMessage = TxProcessor.createDoc2Doc(actualTx)
|
||||
|
||||
return await createCollaboratorNotifications(control.ctx, tx, control, [chatMessage])
|
||||
result.push(...(await createCollaboratorNotifications(control.ctx, tx, control, [chatMessage])))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function joinChannel (control: TriggerControl, channel: Channel, user: Ref<Account>): Tx[] {
|
||||
@ -263,8 +267,9 @@ async function OnThreadMessageDeleted (tx: Tx, control: TriggerControl): Promise
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function ChunterTrigger (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function ChunterTrigger (txes: TxCUD<Doc>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<ChatMessage>
|
||||
|
||||
if (
|
||||
@ -285,6 +290,7 @@ export async function ChunterTrigger (tx: TxCUD<Doc>, control: TriggerControl):
|
||||
) {
|
||||
res.push(...(await control.ctx.with('OnChatMessageCreated', {}, (ctx) => OnChatMessageCreated(ctx, tx, control))))
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@ -342,12 +348,13 @@ export async function getChunterNotificationContent (
|
||||
}
|
||||
}
|
||||
|
||||
async function OnChatMessageRemoved (tx: TxCollectionCUD<Doc, ChatMessage>, control: TriggerControl): Promise<Tx[]> {
|
||||
async function OnChatMessageRemoved (txes: TxCollectionCUD<Doc, ChatMessage>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
if (tx.tx._class !== core.class.TxRemoveDoc) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
const res: Tx[] = []
|
||||
const notifications = await control.findAll(control.ctx, notification.class.InboxNotification, {
|
||||
attachedTo: tx.tx.objectId
|
||||
})
|
||||
@ -355,7 +362,7 @@ async function OnChatMessageRemoved (tx: TxCollectionCUD<Doc, ChatMessage>, cont
|
||||
notifications.forEach((notification) => {
|
||||
res.push(control.txFactory.createTxRemoveDoc(notification._class, notification.space, notification._id))
|
||||
})
|
||||
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@ -470,9 +477,12 @@ export async function syncChat (control: TriggerControl, status: UserStatus, dat
|
||||
await control.apply(control.ctx, res, true)
|
||||
}
|
||||
|
||||
async function OnUserStatus (originTx: TxCUD<UserStatus>, control: TriggerControl): Promise<Tx[]> {
|
||||
async function OnUserStatus (txes: TxCUD<UserStatus>[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const originTx of txes) {
|
||||
const tx = TxProcessor.extractTx(originTx) as TxCUD<UserStatus>
|
||||
if (tx.objectClass !== core.class.UserStatus) return []
|
||||
if (tx.objectClass !== core.class.UserStatus) {
|
||||
continue
|
||||
}
|
||||
if (tx._class === core.class.TxCreateDoc) {
|
||||
const createTx = tx as TxCreateDoc<UserStatus>
|
||||
const { online } = createTx.attributes
|
||||
@ -488,6 +498,7 @@ async function OnUserStatus (originTx: TxCUD<UserStatus>, control: TriggerContro
|
||||
await syncChat(control, status, originTx.modifiedOn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
@ -50,9 +50,10 @@ import { getMetadata } from '@hcengineering/platform'
|
||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
|
||||
export async function OnSpaceTypeMembers (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = tx as TxUpdateDoc<SpaceType>
|
||||
export async function OnSpaceTypeMembers (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = tx as TxUpdateDoc<SpaceType>
|
||||
const newMember = ctx.operations.$push?.members as Ref<Account>
|
||||
if (newMember !== undefined) {
|
||||
const spaces = await control.findAll(control.ctx, core.class.Space, { type: ctx.objectId })
|
||||
@ -79,16 +80,22 @@ export async function OnSpaceTypeMembers (tx: Tx, control: TriggerControl): Prom
|
||||
result.push(pullTx)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export async function OnEmployeeCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const mixinTx = tx as TxMixin<Person, Employee>
|
||||
if (mixinTx.attributes.active !== true) return []
|
||||
const acc = control.modelDb.getAccountByPersonId(mixinTx.objectId)
|
||||
if (acc.length === 0) return []
|
||||
const spaces = await control.findAll(control.ctx, core.class.Space, { autoJoin: true })
|
||||
export async function OnEmployeeCreate (_txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of _txes) {
|
||||
const mixinTx = tx as TxMixin<Person, Employee>
|
||||
if (mixinTx.attributes.active !== true) {
|
||||
continue
|
||||
}
|
||||
const acc = control.modelDb.getAccountByPersonId(mixinTx.objectId)
|
||||
if (acc.length === 0) {
|
||||
continue
|
||||
}
|
||||
const spaces = await control.findAll(control.ctx, core.class.Space, { autoJoin: true })
|
||||
|
||||
const txes = await createPersonSpace(
|
||||
acc.map((it) => it._id),
|
||||
@ -109,6 +116,7 @@ export async function OnEmployeeCreate (tx: Tx, control: TriggerControl): Promis
|
||||
result.push(pushTx)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@ -142,7 +150,11 @@ async function createPersonSpace (
|
||||
]
|
||||
}
|
||||
|
||||
export async function OnPersonAccountCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnPersonAccountCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
const spaces = await control.findAll(control.ctx, core.class.Space, { autoJoin: true })
|
||||
|
||||
for (const tx of txes) {
|
||||
const acc = TxProcessor.createDoc2Doc(tx as TxCreateDoc<PersonAccount>)
|
||||
const person = (
|
||||
await control.findAll(
|
||||
@ -152,10 +164,10 @@ export async function OnPersonAccountCreate (tx: Tx, control: TriggerControl): P
|
||||
{ limit: 1 }
|
||||
)
|
||||
)[0]
|
||||
if (person === undefined) return []
|
||||
const spaces = await control.findAll(control.ctx, core.class.Space, { autoJoin: true })
|
||||
if (person === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const result: Tx[] = []
|
||||
const txes = await createPersonSpace([acc._id], person._id, control)
|
||||
|
||||
result.push(...txes)
|
||||
@ -169,6 +181,7 @@ export async function OnPersonAccountCreate (tx: Tx, control: TriggerControl): P
|
||||
})
|
||||
result.push(pushTx)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@ -176,18 +189,18 @@ export async function OnPersonAccountCreate (tx: Tx, control: TriggerControl): P
|
||||
* @public
|
||||
*/
|
||||
export async function OnContactDelete (
|
||||
tx: Tx,
|
||||
txes: Tx[],
|
||||
{ findAll, hierarchy, storageAdapter, workspace, removedMap, txFactory, ctx }: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const rmTx = tx as TxRemoveDoc<Contact>
|
||||
|
||||
const removeContact = removedMap.get(rmTx.objectId) as Contact
|
||||
if (removeContact === undefined) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
const result: Tx[] = []
|
||||
|
||||
const members = await findAll(ctx, contact.class.Member, { contact: removeContact._id })
|
||||
for (const member of members) {
|
||||
const removeTx = txFactory.createTxRemoveDoc(member._class, member.space, member._id)
|
||||
@ -200,6 +213,7 @@ export async function OnContactDelete (
|
||||
)
|
||||
result.push(tx)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@ -207,10 +221,10 @@ export async function OnContactDelete (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnChannelUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const uTx = tx as TxUpdateDoc<Channel>
|
||||
|
||||
export async function OnChannelUpdate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const uTx = tx as TxUpdateDoc<Channel>
|
||||
|
||||
if (uTx.operations.$inc?.items !== undefined) {
|
||||
const doc = (await control.findAll(control.ctx, uTx.objectClass, { _id: uTx.objectId }, { limit: 1 }))[0]
|
||||
@ -240,6 +254,7 @@ export async function OnChannelUpdate (tx: Tx, control: TriggerControl): Promise
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -1,22 +1,7 @@
|
||||
//
|
||||
// Copyright © 2023-2024 Hardcore Engineering Inc.
|
||||
//
|
||||
import core, {
|
||||
DocumentQuery,
|
||||
Ref,
|
||||
SortingOrder,
|
||||
Tx,
|
||||
TxCollectionCUD,
|
||||
TxFactory,
|
||||
TxUpdateDoc,
|
||||
type Timestamp,
|
||||
type Account,
|
||||
type RolesAssignment,
|
||||
AccountRole,
|
||||
TxCreateDoc
|
||||
} from '@hcengineering/core'
|
||||
import contact, { type Employee, type PersonAccount } from '@hcengineering/contact'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
import documents, {
|
||||
ControlledDocument,
|
||||
ControlledDocumentState,
|
||||
@ -24,13 +9,28 @@ import documents, {
|
||||
DocumentApprovalRequest,
|
||||
DocumentState,
|
||||
DocumentTemplate,
|
||||
type DocumentTraining,
|
||||
getEffectiveDocUpdate,
|
||||
getDocumentId,
|
||||
type DocumentRequest
|
||||
getEffectiveDocUpdate,
|
||||
type DocumentRequest,
|
||||
type DocumentTraining
|
||||
} from '@hcengineering/controlled-documents'
|
||||
import training, { type TrainingRequest, TrainingState } from '@hcengineering/training'
|
||||
import core, {
|
||||
AccountRole,
|
||||
DocumentQuery,
|
||||
Ref,
|
||||
SortingOrder,
|
||||
Tx,
|
||||
TxCollectionCUD,
|
||||
TxCreateDoc,
|
||||
TxFactory,
|
||||
TxUpdateDoc,
|
||||
type Account,
|
||||
type RolesAssignment,
|
||||
type Timestamp
|
||||
} from '@hcengineering/core'
|
||||
import { RequestStatus } from '@hcengineering/request'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
import training, { TrainingState, type TrainingRequest } from '@hcengineering/training'
|
||||
|
||||
async function getDocs (
|
||||
control: TriggerControl,
|
||||
@ -272,26 +272,32 @@ function updateTemplate (doc: ControlledDocument, olderEffective: ControlledDocu
|
||||
}
|
||||
|
||||
export async function OnDocHasBecomeEffective (
|
||||
tx: TxUpdateDoc<ControlledDocument>,
|
||||
txes: TxUpdateDoc<ControlledDocument>[],
|
||||
control: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const doc = (await control.findAll(control.ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 })).shift()
|
||||
if (doc === undefined) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
const olderEffective = await getDocsOlderThanDoc(doc, control, [DocumentState.Effective])
|
||||
|
||||
return [
|
||||
result.push(
|
||||
...updateAuthor(doc, control.txFactory),
|
||||
...archiveDocs(olderEffective, control.txFactory),
|
||||
...updateMeta(doc, control.txFactory),
|
||||
...updateTemplate(doc, olderEffective, control),
|
||||
...(await createDocumentTrainingRequest(doc, control))
|
||||
]
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export async function OnDocDeleted (tx: TxUpdateDoc<ControlledDocument>, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnDocDeleted (txes: TxUpdateDoc<ControlledDocument>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const requests = await control.findAll(control.ctx, documents.class.DocumentRequest, {
|
||||
attachedTo: tx.objectId,
|
||||
status: RequestStatus.Active
|
||||
@ -307,21 +313,24 @@ export async function OnDocDeleted (tx: TxUpdateDoc<ControlledDocument>, control
|
||||
controlledState: undefined
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
export async function OnDocPlannedEffectiveDateChanged (
|
||||
tx: TxUpdateDoc<ControlledDocument>,
|
||||
txes: TxUpdateDoc<ControlledDocument>[],
|
||||
control: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
if (!('plannedEffectiveDate' in tx.operations)) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
const doc = (await control.findAll(control.ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 })).shift()
|
||||
if (doc === undefined) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
// make doc effective immediately if required
|
||||
@ -330,31 +339,36 @@ export async function OnDocPlannedEffectiveDateChanged (
|
||||
const factory = new TxFactory(control.txFactory.account)
|
||||
await control.apply(control.ctx, [makeDocEffective(doc, factory)])
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
export async function OnDocApprovalRequestApproved (
|
||||
tx: TxCollectionCUD<ControlledDocument, DocumentApprovalRequest>,
|
||||
txes: TxCollectionCUD<ControlledDocument, DocumentApprovalRequest>[],
|
||||
control: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const doc = (await control.findAll(control.ctx, tx.objectClass, { _id: tx.objectId })).shift()
|
||||
if (doc == null || doc.plannedEffectiveDate !== 0) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
// Create with not derived tx factory in order for notifications to work
|
||||
const factory = new TxFactory(control.txFactory.account)
|
||||
await control.apply(control.ctx, [makeDocEffective(doc, factory)])
|
||||
|
||||
// make doc effective immediately
|
||||
return []
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnWorkspaceOwnerAdded (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
let ownerId: Ref<PersonAccount> | undefined
|
||||
if (control.hierarchy.isDerived(tx._class, core.class.TxCreateDoc)) {
|
||||
const createTx = tx as TxCreateDoc<PersonAccount>
|
||||
@ -371,7 +385,7 @@ export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): P
|
||||
}
|
||||
|
||||
if (ownerId === undefined) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
const targetSpace = (
|
||||
@ -381,7 +395,7 @@ export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): P
|
||||
)[0]
|
||||
|
||||
if (targetSpace === undefined) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
@ -392,10 +406,11 @@ export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): P
|
||||
const updTx = control.txFactory.createTxUpdateDoc(documents.class.OrgSpace, targetSpace.space, targetSpace._id, {
|
||||
owners: [ownerId]
|
||||
})
|
||||
return [updTx]
|
||||
result.push(updTx)
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
export async function documentTextPresenter (doc: ControlledDocument): Promise<string> {
|
||||
|
@ -20,28 +20,35 @@ import {
|
||||
type Ref,
|
||||
type Tx,
|
||||
type TxRemoveDoc,
|
||||
TxProcessor,
|
||||
DocumentQuery,
|
||||
FindOptions,
|
||||
FindResult
|
||||
FindResult,
|
||||
TxProcessor
|
||||
} from '@hcengineering/core'
|
||||
import drive, { type FileVersion, type Folder } from '@hcengineering/drive'
|
||||
import type { TriggerControl } from '@hcengineering/server-core'
|
||||
|
||||
/** @public */
|
||||
export async function OnFileVersionDelete (
|
||||
tx: Tx,
|
||||
txes: Tx[],
|
||||
{ removedMap, ctx, storageAdapter, workspace }: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
const toDelete: string[] = []
|
||||
for (const tx of txes) {
|
||||
const rmTx = TxProcessor.extractTx(tx) as TxRemoveDoc<FileVersion>
|
||||
|
||||
// Obtain document being deleted.
|
||||
const version = removedMap.get(rmTx.objectId) as FileVersion
|
||||
if (version !== undefined) {
|
||||
await storageAdapter.remove(ctx, workspace, [version.file])
|
||||
toDelete.push(version.file)
|
||||
}
|
||||
}
|
||||
if (toDelete.length > 0) {
|
||||
await storageAdapter.remove(ctx, workspace, toDelete)
|
||||
}
|
||||
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
@ -15,18 +15,19 @@
|
||||
|
||||
import core, {
|
||||
DocIndexState,
|
||||
DOMAIN_DOC_INDEX_STATE,
|
||||
getFullTextContext,
|
||||
isClassIndexable,
|
||||
isFullTextAttribute,
|
||||
Tx,
|
||||
TxProcessor,
|
||||
type AnyAttribute,
|
||||
type AttachedDoc,
|
||||
type Class,
|
||||
type Doc,
|
||||
type FullTextSearchContext,
|
||||
type Ref,
|
||||
type TxCollectionCUD,
|
||||
type TxCUD
|
||||
type TxCUD,
|
||||
type TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import { TriggerControl } from '@hcengineering/server-core'
|
||||
|
||||
@ -38,13 +39,17 @@ export async function OnChange (txes: Tx[], control: TriggerControl): Promise<Tx
|
||||
control.contextCache.set(indexingCtx, contexts)
|
||||
}
|
||||
|
||||
const docsToUpsert = new Map<Ref<DocIndexState>, DocIndexState>()
|
||||
if (control.ctx.contextData.fulltextUpdates === undefined) {
|
||||
control.ctx.contextData.fulltextUpdates = new Map()
|
||||
}
|
||||
const docsToUpsert = control.ctx.contextData.fulltextUpdates
|
||||
|
||||
for (let tx of txes) {
|
||||
if (tx._class === core.class.TxCollectionCUD) {
|
||||
const txcol = tx as TxCollectionCUD<Doc, AttachedDoc>
|
||||
tx = txcol.tx
|
||||
}
|
||||
const attrs = new Map<string, AnyAttribute>()
|
||||
if (TxProcessor.isExtendsCUD(tx._class)) {
|
||||
const cud = tx as TxCUD<Doc>
|
||||
|
||||
@ -52,6 +57,35 @@ export async function OnChange (txes: Tx[], control: TriggerControl): Promise<Tx
|
||||
// No need, since no indixable fields or attachments.
|
||||
continue
|
||||
}
|
||||
|
||||
// We need to check if operation has indexable attributes.
|
||||
if (cud._class === core.class.TxUpdateDoc) {
|
||||
let hasFulltext = false
|
||||
// We could skip updates for a lot of transactions if they have non idexable attributes
|
||||
const upd = cud as TxUpdateDoc<Doc>
|
||||
for (const [k] of Object.entries(upd.operations)) {
|
||||
if (k.startsWith('$') || k === 'modifiedOn') {
|
||||
// Is operator
|
||||
// Skip operator changes
|
||||
} else {
|
||||
const key = upd.objectClass + '.' + k
|
||||
const attr = attrs.get(key) ?? control.hierarchy.getAttribute(upd.objectClass, k)
|
||||
if (attr !== undefined) {
|
||||
attrs.set(key, attr ?? null)
|
||||
}
|
||||
if (attr != null) {
|
||||
if (isFullTextAttribute(attr)) {
|
||||
hasFulltext = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasFulltext) {
|
||||
// No full text changes, no indexing is required
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
docsToUpsert.set(cud.objectId as Ref<DocIndexState>, {
|
||||
_id: cud.objectId as Ref<DocIndexState>,
|
||||
_class: core.class.DocIndexState,
|
||||
@ -62,25 +96,8 @@ export async function OnChange (txes: Tx[], control: TriggerControl): Promise<Tx
|
||||
space: cud.space,
|
||||
removed: cud._class === core.class.TxRemoveDoc
|
||||
})
|
||||
|
||||
const propagate = getFullTextContext(control.hierarchy, cud.objectClass, contexts).propagate
|
||||
if (propagate !== undefined && propagate.length > 0) {
|
||||
control.ctx.contextData.needWarmupFulltext = true
|
||||
await control.ctx.with('clean-childs', { _class: cud.objectClass }, () => {
|
||||
// We need to propagate all changes to all child's of following classes.
|
||||
return control.lowLevel.rawUpdate<DocIndexState>(
|
||||
DOMAIN_DOC_INDEX_STATE,
|
||||
{ attachedTo: cud.objectClass, objectClass: { $in: propagate } },
|
||||
{ needIndex: true }
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if (docsToUpsert.size > 0) {
|
||||
control.ctx.contextData.needWarmupFulltext = true
|
||||
await control.lowLevel.upload(control.ctx, DOMAIN_DOC_INDEX_STATE, Array.from(docsToUpsert.values()))
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
|
@ -65,9 +65,9 @@ export async function FindMessages (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
|
||||
export async function OnMessageCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const createTx = tx as TxCreateDoc<Message>
|
||||
|
||||
const message = TxProcessor.createDoc2Doc<Message>(createTx)
|
||||
@ -80,11 +80,12 @@ export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise
|
||||
const tx = control.txFactory.createTxUpdateDoc(channel._class, channel.space, channel._id, {
|
||||
lastMessage: message.sendOn
|
||||
})
|
||||
res.push(tx)
|
||||
result.push(tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,24 +35,27 @@ import view from '@hcengineering/view'
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnPublicLinkCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
|
||||
export async function OnPublicLinkCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const extractedTx = TxProcessor.extractTx(tx)
|
||||
|
||||
const createTx = extractedTx as TxCreateDoc<PublicLink>
|
||||
|
||||
const link = TxProcessor.createDoc2Doc<PublicLink>(createTx)
|
||||
|
||||
if (link.url !== '') return res
|
||||
if (link.url !== '') {
|
||||
continue
|
||||
}
|
||||
|
||||
const resTx = control.txFactory.createTxUpdateDoc(link._class, link.space, link._id, {
|
||||
url: generateUrl(link._id, control.workspace, control.branding?.front)
|
||||
})
|
||||
|
||||
res.push(resTx)
|
||||
result.push(resTx)
|
||||
}
|
||||
|
||||
return res
|
||||
return result
|
||||
}
|
||||
|
||||
export function getPublicLinkUrl (workspace: WorkspaceIdWithUrl, brandedFront?: string): string {
|
||||
|
@ -131,11 +131,15 @@ function getTxes (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnDepartmentStaff (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxMixin<Employee, Staff>
|
||||
|
||||
const targetAccount = control.modelDb.getAccountByPersonId(ctx.objectId) as PersonAccount[]
|
||||
if (targetAccount.length === 0) return []
|
||||
if (targetAccount.length === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (ctx.attributes.department !== undefined) {
|
||||
const lastDepartment = await getOldDepartment(ctx, control)
|
||||
@ -144,50 +148,60 @@ export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promi
|
||||
if (departmentId === null) {
|
||||
if (lastDepartment !== undefined) {
|
||||
const removed = await buildHierarchy(lastDepartment, control)
|
||||
return getTxes(
|
||||
result.push(
|
||||
...getTxes(
|
||||
control.txFactory,
|
||||
targetAccount.map((it) => it._id),
|
||||
[],
|
||||
removed.map((p) => p._id)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
const push = (await buildHierarchy(departmentId, control)).map((p) => p._id)
|
||||
|
||||
if (lastDepartment === undefined) {
|
||||
return getTxes(
|
||||
result.push(
|
||||
...getTxes(
|
||||
control.txFactory,
|
||||
targetAccount.map((it) => it._id),
|
||||
push
|
||||
)
|
||||
}
|
||||
|
||||
)
|
||||
} else {
|
||||
let removed = (await buildHierarchy(lastDepartment, control)).map((p) => p._id)
|
||||
const added = exlude(removed, push)
|
||||
removed = exlude(push, removed)
|
||||
return getTxes(
|
||||
result.push(
|
||||
...getTxes(
|
||||
control.txFactory,
|
||||
targetAccount.map((it) => it._id),
|
||||
added,
|
||||
removed
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnDepartmentRemove (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnDepartmentRemove (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxRemoveDoc<Department>
|
||||
|
||||
const department = control.removedMap.get(ctx.objectId) as Department
|
||||
if (department === undefined) return []
|
||||
const res: Tx[] = []
|
||||
if (department === undefined) {
|
||||
continue
|
||||
}
|
||||
const nested = await control.findAll(control.ctx, hr.class.Department, { parent: department._id })
|
||||
for (const dep of nested) {
|
||||
res.push(control.txFactory.createTxRemoveDoc(dep._class, dep.space, dep._id))
|
||||
result.push(control.txFactory.createTxRemoveDoc(dep._class, dep.space, dep._id))
|
||||
}
|
||||
const targetAccounts = await control.modelDb.findAll(contact.class.PersonAccount, {
|
||||
_id: { $in: department.members }
|
||||
@ -199,9 +213,11 @@ export async function OnDepartmentRemove (tx: Tx, control: TriggerControl): Prom
|
||||
})
|
||||
const removed = await buildHierarchy(department._id, control)
|
||||
employee.forEach((em) => {
|
||||
res.push(control.txFactory.createTxMixin(em._id, em._class, em.space, hr.mixin.Staff, { department: undefined }))
|
||||
result.push(
|
||||
control.txFactory.createTxMixin(em._id, em._class, em.space, hr.mixin.Staff, { department: undefined })
|
||||
)
|
||||
})
|
||||
res.push(
|
||||
result.push(
|
||||
...getTxes(
|
||||
control.txFactory,
|
||||
targetAccounts.map((it) => it._id),
|
||||
@ -209,57 +225,70 @@ export async function OnDepartmentRemove (tx: Tx, control: TriggerControl): Prom
|
||||
removed.map((p) => p._id)
|
||||
)
|
||||
)
|
||||
return res
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnEmployee (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnEmployee (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxMixin<Person, Employee>
|
||||
|
||||
const person = (await control.findAll(control.ctx, contact.class.Person, { _id: ctx.objectId }))[0]
|
||||
if (person === undefined) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
const employee = control.hierarchy.as(person, ctx.mixin)
|
||||
if (control.hierarchy.hasMixin(person, hr.mixin.Staff) || !employee.active) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
return [
|
||||
result.push(
|
||||
control.txFactory.createTxMixin(ctx.objectId, ctx.objectClass, ctx.objectSpace, hr.mixin.Staff, {
|
||||
department: hr.ids.Head
|
||||
})
|
||||
]
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnEmployeeDeactivate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnEmployeeDeactivate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
if (core.class.TxMixin !== actualTx._class) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
const ctx = actualTx as TxMixin<Person, Employee>
|
||||
if (ctx.mixin !== contact.mixin.Employee || ctx.attributes.active !== false) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
const targetAccount = control.modelDb.getAccountByPersonId(ctx.objectId) as PersonAccount[]
|
||||
if (targetAccount.length === 0) return []
|
||||
if (targetAccount.length === 0) {
|
||||
continue
|
||||
}
|
||||
const set = new Set(targetAccount.map((it) => it._id))
|
||||
|
||||
const departments = await control.queryFind(control.ctx, hr.class.Department, {})
|
||||
const removed = departments.filter((dep) => dep.members.some((p) => set.has(p)))
|
||||
return getTxes(
|
||||
result.push(
|
||||
...getTxes(
|
||||
control.txFactory,
|
||||
targetAccount.map((it) => it._id),
|
||||
[],
|
||||
removed.map((p) => p._id)
|
||||
)
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// TODO: why we need specific email notifications instead of using general flow?
|
||||
@ -317,47 +346,63 @@ async function sendEmailNotifications (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnRequestCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnRequestCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<Request>
|
||||
|
||||
const sender = getPersonAccountById(ctx.modifiedBy, control)
|
||||
if (sender === undefined) return []
|
||||
if (sender === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const request = TxProcessor.createDoc2Doc(ctx)
|
||||
|
||||
await sendEmailNotifications(control, sender, request, request.department, hr.ids.CreateRequestNotification)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnRequestUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnRequestUpdate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxUpdateDoc<Request>
|
||||
|
||||
const sender = getPersonAccountById(ctx.modifiedBy, control)
|
||||
if (sender === undefined) return []
|
||||
if (sender === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const request = (await control.findAll(control.ctx, hr.class.Request, { _id: ctx.objectId }))[0] as Request
|
||||
if (request === undefined) return []
|
||||
if (request === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
await sendEmailNotifications(control, sender, request, request.department, hr.ids.UpdateRequestNotification)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnRequestRemove (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnRequestRemove (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<Request>
|
||||
|
||||
const sender = getPersonAccountById(ctx.modifiedBy, control)
|
||||
if (sender === undefined) return []
|
||||
if (sender === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const request = control.removedMap.get(ctx.objectId) as Request
|
||||
if (request === undefined) return []
|
||||
if (request === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
await sendEmailNotifications(control, sender, request, request.department, hr.ids.RemoveRequestNotification)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
@ -400,13 +445,19 @@ export async function RequestTextPresenter (doc: Doc, control: TriggerControl):
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnPublicHolidayCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnPublicHolidayCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<PublicHoliday>
|
||||
|
||||
const sender = getPersonAccountById(ctx.modifiedBy, control)
|
||||
if (sender === undefined) return []
|
||||
if (sender === undefined) {
|
||||
continue
|
||||
}
|
||||
const employee = await getEmployee(sender.person as Ref<Employee>, control)
|
||||
if (employee === undefined) return []
|
||||
if (employee === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const publicHoliday = TxProcessor.createDoc2Doc(ctx)
|
||||
await sendEmailNotifications(
|
||||
@ -416,7 +467,8 @@ export async function OnPublicHolidayCreate (tx: Tx, control: TriggerControl): P
|
||||
publicHoliday.department,
|
||||
hr.ids.CreatePublicHolidayNotification
|
||||
)
|
||||
return []
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,7 +43,9 @@ export async function leadTextPresenter (doc: Doc): Promise<string> {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnWorkspaceOwnerAdded (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
let ownerId: Ref<PersonAccount> | undefined
|
||||
if (control.hierarchy.isDerived(tx._class, core.class.TxCreateDoc)) {
|
||||
const createTx = tx as TxCreateDoc<PersonAccount>
|
||||
@ -60,7 +62,7 @@ export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): P
|
||||
}
|
||||
|
||||
if (ownerId === undefined) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
const targetFunnel = (
|
||||
@ -70,7 +72,7 @@ export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): P
|
||||
)[0]
|
||||
|
||||
if (targetFunnel === undefined) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
@ -81,10 +83,11 @@ export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): P
|
||||
const updTx = control.txFactory.createTxUpdateDoc(lead.class.Funnel, targetFunnel.space, targetFunnel._id, {
|
||||
owners: [ownerId]
|
||||
})
|
||||
return [updTx]
|
||||
result.push(updTx)
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
return result
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
|
@ -44,12 +44,20 @@ import {
|
||||
} from '@hcengineering/server-notification-resources'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
|
||||
export async function OnEmployee (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnEmployee (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxMixin<Person, Employee>
|
||||
if (actualTx._class !== core.class.TxMixin) return []
|
||||
if (actualTx.mixin !== contact.mixin.Employee) return []
|
||||
if (actualTx._class !== core.class.TxMixin) {
|
||||
continue
|
||||
}
|
||||
if (actualTx.mixin !== contact.mixin.Employee) {
|
||||
continue
|
||||
}
|
||||
const val = actualTx.attributes.active
|
||||
if (val === undefined) return []
|
||||
if (val === undefined) {
|
||||
continue
|
||||
}
|
||||
if (val) {
|
||||
const freeRoom = (await control.findAll(control.ctx, love.class.Office, { person: null }))[0]
|
||||
if (freeRoom !== undefined) {
|
||||
@ -62,14 +70,15 @@ export async function OnEmployee (tx: Tx, control: TriggerControl): Promise<Tx[]
|
||||
} else {
|
||||
const room = (await control.findAll(control.ctx, love.class.Office, { person: actualTx.objectId }))[0]
|
||||
if (room !== undefined) {
|
||||
return [
|
||||
result.push(
|
||||
control.txFactory.createTxUpdateDoc(room._class, room.space, room._id, {
|
||||
person: null
|
||||
})
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function createUserInfo (acc: Ref<Account>, control: TriggerControl): Promise<Tx[]> {
|
||||
@ -124,13 +133,17 @@ async function removeUserInfo (acc: Ref<Account>, control: TriggerControl): Prom
|
||||
return res
|
||||
}
|
||||
|
||||
export async function OnUserStatus (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnUserStatus (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<UserStatus>
|
||||
if (actualTx.objectClass !== core.class.UserStatus) return []
|
||||
if (actualTx.objectClass !== core.class.UserStatus) {
|
||||
continue
|
||||
}
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const createTx = actualTx as TxCreateDoc<UserStatus>
|
||||
const status = TxProcessor.createDoc2Doc(createTx)
|
||||
return await createUserInfo(status.user, control)
|
||||
result.push(...(await createUserInfo(status.user, control)))
|
||||
} else if (actualTx._class === core.class.TxUpdateDoc) {
|
||||
const updateTx = actualTx as TxUpdateDoc<UserStatus>
|
||||
const val = updateTx.operations.online
|
||||
@ -138,19 +151,14 @@ export async function OnUserStatus (tx: Tx, control: TriggerControl): Promise<Tx
|
||||
const status = (await control.findAll(control.ctx, core.class.UserStatus, { _id: updateTx.objectId }))[0]
|
||||
if (status !== undefined) {
|
||||
if (val) {
|
||||
return await createUserInfo(status.user, control)
|
||||
result.push(...(await createUserInfo(status.user, control)))
|
||||
} else {
|
||||
return await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
void removeUserInfo(status.user, control).then((res) => {
|
||||
resolve(res)
|
||||
})
|
||||
}, 20000)
|
||||
})
|
||||
result.push(...(await removeUserInfo(status.user, control)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function roomJoinHandler (info: ParticipantInfo, control: TriggerControl): Promise<Tx[]> {
|
||||
@ -228,51 +236,65 @@ async function setDefaultRoomAccess (info: ParticipantInfo, control: TriggerCont
|
||||
return res
|
||||
}
|
||||
|
||||
export async function OnParticipantInfo (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnParticipantInfo (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<ParticipantInfo>
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const info = TxProcessor.createDoc2Doc(actualTx as TxCreateDoc<ParticipantInfo>)
|
||||
return await roomJoinHandler(info, control)
|
||||
result.push(...(await roomJoinHandler(info, control)))
|
||||
}
|
||||
if (actualTx._class === core.class.TxRemoveDoc) {
|
||||
const removedInfo = control.removedMap.get(actualTx.objectId) as ParticipantInfo
|
||||
if (removedInfo === undefined) return []
|
||||
return await setDefaultRoomAccess(removedInfo, control)
|
||||
if (removedInfo === undefined) {
|
||||
continue
|
||||
}
|
||||
result.push(...(await setDefaultRoomAccess(removedInfo, control)))
|
||||
continue
|
||||
}
|
||||
if (actualTx._class === core.class.TxUpdateDoc) {
|
||||
const newRoom = (actualTx as TxUpdateDoc<ParticipantInfo>).operations.room
|
||||
if (newRoom === undefined) return []
|
||||
if (newRoom === undefined) {
|
||||
continue
|
||||
}
|
||||
const info = (
|
||||
await control.findAll(control.ctx, love.class.ParticipantInfo, { _id: actualTx.objectId }, { limit: 1 })
|
||||
)[0]
|
||||
if (info === undefined) return []
|
||||
const res: Tx[] = []
|
||||
res.push(...(await rejectJoinRequests(info, control)))
|
||||
res.push(...(await setDefaultRoomAccess(info, control)))
|
||||
res.push(...(await roomJoinHandler(info, control)))
|
||||
return res
|
||||
if (info === undefined) {
|
||||
continue
|
||||
}
|
||||
return []
|
||||
result.push(...(await rejectJoinRequests(info, control)))
|
||||
result.push(...(await setDefaultRoomAccess(info, control)))
|
||||
result.push(...(await roomJoinHandler(info, control)))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export async function OnKnock (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnKnock (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<JoinRequest>
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const request = TxProcessor.createDoc2Doc(actualTx)
|
||||
if (request.status === RequestStatus.Pending) {
|
||||
const roomInfo = (await control.findAll(control.ctx, love.class.RoomInfo, { room: request.room }))[0]
|
||||
if (roomInfo !== undefined) {
|
||||
const res: Tx[] = []
|
||||
const from = (await control.findAll(control.ctx, contact.class.Person, { _id: request.person }))[0]
|
||||
if (from === undefined) return []
|
||||
if (from === undefined) {
|
||||
continue
|
||||
}
|
||||
const type = await control.modelDb.findOne(notification.class.NotificationType, {
|
||||
_id: love.ids.KnockNotification
|
||||
})
|
||||
if (type === undefined) return []
|
||||
if (type === undefined) {
|
||||
continue
|
||||
}
|
||||
const provider = await control.modelDb.findOne(notification.class.NotificationProvider, {
|
||||
_id: notification.providers.PushNotificationProvider
|
||||
})
|
||||
if (provider === undefined) return []
|
||||
if (provider === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const notificationControl = await getNotificationProviderControl(control.ctx, control)
|
||||
for (const user of roomInfo.persons) {
|
||||
@ -292,31 +314,40 @@ export async function OnKnock (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
await createPushNotification(control, userAcc[0]._id, title, body, request._id, subscriptions, from, path)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnInvite (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnInvite (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<Invite>
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
const invite = TxProcessor.createDoc2Doc(actualTx)
|
||||
if (invite.status === RequestStatus.Pending) {
|
||||
const target = (await control.findAll(control.ctx, contact.class.Person, { _id: invite.target }))[0]
|
||||
if (target === undefined) return []
|
||||
if (target === undefined) {
|
||||
continue
|
||||
}
|
||||
const userAcc = control.modelDb.getAccountByPersonId(target._id) as PersonAccount[]
|
||||
if (userAcc.length === 0) return []
|
||||
if (userAcc.length === 0) {
|
||||
continue
|
||||
}
|
||||
const from = (await control.findAll(control.ctx, contact.class.Person, { _id: invite.from }))[0]
|
||||
const type = await control.modelDb.findOne(notification.class.NotificationType, {
|
||||
_id: love.ids.InviteNotification
|
||||
})
|
||||
if (type === undefined) return []
|
||||
if (type === undefined) {
|
||||
continue
|
||||
}
|
||||
const provider = await control.modelDb.findOne(notification.class.NotificationProvider, {
|
||||
_id: notification.providers.PushNotificationProvider
|
||||
})
|
||||
if (provider === undefined) return []
|
||||
if (provider === undefined) {
|
||||
continue
|
||||
}
|
||||
const notificationControl = await getNotificationProviderControl(control.ctx, control)
|
||||
if (userAcc.some((it) => isAllowed(control, it._id, type, provider, notificationControl))) {
|
||||
const path = [workbenchId, control.workspace.workspaceUrl, loveId]
|
||||
@ -335,6 +366,7 @@ export async function OnInvite (tx: Tx, control: TriggerControl): Promise<Tx[]>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
|
@ -331,7 +331,7 @@ export async function getDocCollaborators (
|
||||
const collaborators = new Set<Ref<Account>>()
|
||||
for (const field of mixin.fields) {
|
||||
const value = (doc as any)[field]
|
||||
const newCollaborators = await ctx.with('getKeyCollaborators', {}, async (ctx) =>
|
||||
const newCollaborators = ctx.withSync('getKeyCollaborators', {}, (ctx) =>
|
||||
getKeyCollaborators(doc._class, value, field, control)
|
||||
)
|
||||
if (newCollaborators !== undefined) {
|
||||
@ -991,10 +991,8 @@ export async function createCollabDocInfo (
|
||||
return res
|
||||
}
|
||||
|
||||
const usersInfo = await ctx.with(
|
||||
'get-user-info',
|
||||
{},
|
||||
async (ctx) => await getUsersInfo(ctx, [...Array.from(targets), originTx.modifiedBy as Ref<PersonAccount>], control)
|
||||
const usersInfo = await ctx.with('get-user-info', {}, (ctx) =>
|
||||
getUsersInfo(ctx, [...Array.from(targets), originTx.modifiedBy as Ref<PersonAccount>], control)
|
||||
)
|
||||
const sender: SenderInfo = usersInfo.get(originTx.modifiedBy) ?? {
|
||||
_id: originTx.modifiedBy
|
||||
@ -1159,36 +1157,22 @@ async function createCollaboratorDoc (
|
||||
}
|
||||
|
||||
const doc = TxProcessor.createDoc2Doc(tx)
|
||||
const collaborators = await ctx.with(
|
||||
'get-collaborators',
|
||||
{},
|
||||
async (ctx) => await getDocCollaborators(ctx, doc, mixin, control)
|
||||
)
|
||||
const collaborators = await ctx.with('get-collaborators', {}, (ctx) => getDocCollaborators(ctx, doc, mixin, control))
|
||||
const mixinTx = getMixinTx(tx, control, collaborators)
|
||||
|
||||
const notificationTxes = await ctx.with(
|
||||
'create-collabdocinfo',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await createCollabDocInfo(
|
||||
ctx,
|
||||
collaborators as Ref<PersonAccount>[],
|
||||
control,
|
||||
tx,
|
||||
originTx,
|
||||
doc,
|
||||
activityMessage,
|
||||
{ isOwn: true, isSpace: false, shouldUpdateTimestamp: true }
|
||||
)
|
||||
const notificationTxes = await ctx.with('create-collabdocinfo', {}, (ctx) =>
|
||||
createCollabDocInfo(ctx, collaborators as Ref<PersonAccount>[], control, tx, originTx, doc, activityMessage, {
|
||||
isOwn: true,
|
||||
isSpace: false,
|
||||
shouldUpdateTimestamp: true
|
||||
})
|
||||
)
|
||||
res.push(mixinTx)
|
||||
res.push(...notificationTxes)
|
||||
|
||||
res.push(
|
||||
...(await ctx.with(
|
||||
'get-space-collabtxes',
|
||||
{},
|
||||
async (ctx) => await getSpaceCollabTxes(ctx, control, doc, tx, originTx, activityMessage, cache)
|
||||
...(await ctx.with('get-space-collabtxes', {}, (ctx) =>
|
||||
getSpaceCollabTxes(ctx, control, doc, tx, originTx, activityMessage, cache)
|
||||
))
|
||||
)
|
||||
|
||||
@ -1268,10 +1252,8 @@ async function updateCollaboratorsMixin (
|
||||
objectId: tx.objectId
|
||||
})
|
||||
|
||||
const infos = await ctx.with(
|
||||
'get-user-info',
|
||||
{},
|
||||
async (ctx) => await getUsersInfo(ctx, [...newCollabs, originTx.modifiedBy] as Ref<PersonAccount>[], control)
|
||||
const infos = await ctx.with('get-user-info', {}, (ctx) =>
|
||||
getUsersInfo(ctx, [...newCollabs, originTx.modifiedBy] as Ref<PersonAccount>[], control)
|
||||
)
|
||||
const sender: SenderInfo = infos.get(originTx.modifiedBy) ?? { _id: originTx.modifiedBy }
|
||||
|
||||
@ -1332,27 +1314,15 @@ async function collectionCollabDoc (
|
||||
|
||||
cache.set(doc._id, doc)
|
||||
|
||||
const collaborators = await ctx.with(
|
||||
'get-collaborators',
|
||||
{},
|
||||
async (ctx) => await getCollaborators(ctx, doc, control, tx, res)
|
||||
)
|
||||
const collaborators = await ctx.with('get-collaborators', {}, (ctx) => getCollaborators(ctx, doc, control, tx, res))
|
||||
|
||||
res = res.concat(
|
||||
await ctx.with(
|
||||
'create-collab-doc-info',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await createCollabDocInfo(
|
||||
ctx,
|
||||
collaborators as Ref<PersonAccount>[],
|
||||
control,
|
||||
actualTx,
|
||||
tx,
|
||||
doc,
|
||||
activityMessages,
|
||||
{ isOwn: false, isSpace: false, shouldUpdateTimestamp: true }
|
||||
)
|
||||
await ctx.with('create-collab-doc-info', {}, (ctx) =>
|
||||
createCollabDocInfo(ctx, collaborators as Ref<PersonAccount>[], control, actualTx, tx, doc, activityMessages, {
|
||||
isOwn: false,
|
||||
isSpace: false,
|
||||
shouldUpdateTimestamp: true
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
@ -1504,11 +1474,7 @@ async function updateCollaboratorDoc (
|
||||
const params: NotifyParams = { isOwn: true, isSpace: false, shouldUpdateTimestamp: true }
|
||||
if (hierarchy.hasMixin(doc, notification.mixin.Collaborators)) {
|
||||
// we should handle change field and subscribe new collaborators
|
||||
const collabsInfo = await ctx.with(
|
||||
'get-tx-collaborators',
|
||||
{},
|
||||
async (ctx) => await getTxCollabs(ctx, tx, control, doc)
|
||||
)
|
||||
const collabsInfo = await ctx.with('get-tx-collaborators', {}, (ctx) => getTxCollabs(ctx, tx, control, doc))
|
||||
|
||||
if (collabsInfo.added.length > 0) {
|
||||
res.push(createPushCollaboratorsTx(control, tx.objectId, tx.objectClass, tx.objectSpace, collabsInfo.added))
|
||||
@ -1519,11 +1485,8 @@ async function updateCollaboratorDoc (
|
||||
}
|
||||
|
||||
res = res.concat(
|
||||
await ctx.with(
|
||||
'create-collab-docinfo',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await createCollabDocInfo(
|
||||
await ctx.with('create-collab-docinfo', {}, (ctx) =>
|
||||
createCollabDocInfo(
|
||||
ctx,
|
||||
collabsInfo.result as Ref<PersonAccount>[],
|
||||
control,
|
||||
@ -1537,10 +1500,8 @@ async function updateCollaboratorDoc (
|
||||
)
|
||||
)
|
||||
} else {
|
||||
const collaborators = await ctx.with(
|
||||
'get-doc-collaborators',
|
||||
{},
|
||||
async (ctx) => await getDocCollaborators(ctx, doc, mixin, control)
|
||||
const collaborators = await ctx.with('get-doc-collaborators', {}, (ctx) =>
|
||||
getDocCollaborators(ctx, doc, mixin, control)
|
||||
)
|
||||
res.push(getMixinTx(tx, control, collaborators))
|
||||
res = res.concat(
|
||||
@ -1558,14 +1519,12 @@ async function updateCollaboratorDoc (
|
||||
}
|
||||
|
||||
res = res.concat(
|
||||
await ctx.with(
|
||||
'get-space-collabtxes',
|
||||
{},
|
||||
async (ctx) => await getSpaceCollabTxes(ctx, control, doc, tx, originTx, activityMessages, cache)
|
||||
await ctx.with('get-space-collabtxes', {}, (ctx) =>
|
||||
getSpaceCollabTxes(ctx, control, doc, tx, originTx, activityMessages, cache)
|
||||
)
|
||||
)
|
||||
res = res.concat(
|
||||
await ctx.with('update-notify-context-space', {}, async (ctx) => await updateNotifyContextsSpace(ctx, control, tx))
|
||||
await ctx.with('update-notify-context-space', {}, (ctx) => updateNotifyContextsSpace(ctx, control, tx))
|
||||
)
|
||||
|
||||
return res
|
||||
@ -1574,12 +1533,16 @@ async function updateCollaboratorDoc (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnAttributeCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnAttributeCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const attribute = TxProcessor.createDoc2Doc(tx as TxCreateDoc<AnyAttribute>)
|
||||
const group = (
|
||||
await control.modelDb.findAll(notification.class.NotificationGroup, { objectClass: attribute.attributeOf })
|
||||
)[0]
|
||||
if (group === undefined) return []
|
||||
if (group === undefined) {
|
||||
continue
|
||||
}
|
||||
const isCollection: boolean = core.class.Collection === attribute.type._class
|
||||
const objectClass = !isCollection ? attribute.attributeOf : (attribute.type as Collection<AttachedDoc>).of
|
||||
const txClasses = !isCollection
|
||||
@ -1606,22 +1569,34 @@ export async function OnAttributeCreate (tx: Tx, control: TriggerControl): Promi
|
||||
}
|
||||
const id =
|
||||
`${notification.class.NotificationType}_${attribute.attributeOf}_${attribute.name}` as Ref<NotificationType>
|
||||
const res = control.txFactory.createTxCreateDoc(notification.class.NotificationType, core.space.Model, data, id)
|
||||
return [res]
|
||||
result.push(control.txFactory.createTxCreateDoc(notification.class.NotificationType, core.space.Model, data, id))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnAttributeUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnAttributeUpdate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = tx as TxUpdateDoc<AnyAttribute>
|
||||
if (ctx.operations.hidden === undefined) return []
|
||||
const type = (await control.findAll(control.ctx, notification.class.NotificationType, { attribute: ctx.objectId }))[0]
|
||||
if (type === undefined) return []
|
||||
const res = control.txFactory.createTxUpdateDoc(type._class, type.space, type._id, {
|
||||
if (ctx.operations.hidden === undefined) {
|
||||
continue
|
||||
}
|
||||
const type = (
|
||||
await control.findAll(control.ctx, notification.class.NotificationType, { attribute: ctx.objectId })
|
||||
)[0]
|
||||
if (type === undefined) {
|
||||
continue
|
||||
}
|
||||
result.push(
|
||||
control.txFactory.createTxUpdateDoc(type._class, type.space, type._id, {
|
||||
hidden: ctx.operations.hidden
|
||||
})
|
||||
return [res]
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function applyUserTxes (
|
||||
@ -1755,10 +1730,8 @@ export async function createCollaboratorNotifications (
|
||||
): Promise<Tx[]> {
|
||||
if (tx.space === core.space.DerivedTx) {
|
||||
// do not forgot update collaborators for derived tx
|
||||
return await ctx.with(
|
||||
'updateDerivedCollaborators',
|
||||
{},
|
||||
async (ctx) => await updateCollaborators(ctx, control, TxProcessor.extractTx(tx) as TxCUD<Doc>, cache)
|
||||
return await ctx.with('updateDerivedCollaborators', {}, (ctx) =>
|
||||
updateCollaborators(ctx, control, TxProcessor.extractTx(tx) as TxCUD<Doc>, cache)
|
||||
)
|
||||
}
|
||||
|
||||
@ -1768,45 +1741,27 @@ export async function createCollaboratorNotifications (
|
||||
|
||||
switch (tx._class) {
|
||||
case core.class.TxCreateDoc: {
|
||||
const res = await ctx.with(
|
||||
'createCollaboratorDoc',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await createCollaboratorDoc(ctx, tx as TxCreateDoc<Doc>, control, activityMessages, originTx ?? tx, cache)
|
||||
const res = await ctx.with('createCollaboratorDoc', {}, (ctx) =>
|
||||
createCollaboratorDoc(ctx, tx as TxCreateDoc<Doc>, control, activityMessages, originTx ?? tx, cache)
|
||||
)
|
||||
|
||||
return await applyUserTxes(ctx, control, res)
|
||||
}
|
||||
case core.class.TxUpdateDoc:
|
||||
case core.class.TxMixin: {
|
||||
let res = await ctx.with(
|
||||
'updateCollaboratorDoc',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await updateCollaboratorDoc(ctx, tx as TxUpdateDoc<Doc>, control, originTx ?? tx, activityMessages, cache)
|
||||
let res = await ctx.with('updateCollaboratorDoc', {}, (ctx) =>
|
||||
updateCollaboratorDoc(ctx, tx as TxUpdateDoc<Doc>, control, originTx ?? tx, activityMessages, cache)
|
||||
)
|
||||
res = res.concat(
|
||||
await ctx.with(
|
||||
'updateCollaboratorMixin',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await updateCollaboratorsMixin(
|
||||
ctx,
|
||||
tx as TxMixin<Doc, Collaborators>,
|
||||
control,
|
||||
activityMessages,
|
||||
originTx ?? tx
|
||||
)
|
||||
await ctx.with('updateCollaboratorMixin', {}, (ctx) =>
|
||||
updateCollaboratorsMixin(ctx, tx as TxMixin<Doc, Collaborators>, control, activityMessages, originTx ?? tx)
|
||||
)
|
||||
)
|
||||
return await applyUserTxes(ctx, control, res)
|
||||
}
|
||||
case core.class.TxCollectionCUD: {
|
||||
const res = await ctx.with(
|
||||
'collectionCollabDoc',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await collectionCollabDoc(ctx, tx as TxCollectionCUD<Doc, AttachedDoc>, control, activityMessages, cache)
|
||||
const res = await ctx.with('collectionCollabDoc', {}, (ctx) =>
|
||||
collectionCollabDoc(ctx, tx as TxCollectionCUD<Doc, AttachedDoc>, control, activityMessages, cache)
|
||||
)
|
||||
return await applyUserTxes(ctx, control, res)
|
||||
}
|
||||
@ -1914,29 +1869,33 @@ async function OnActivityMessageRemove (message: ActivityMessage, control: Trigg
|
||||
return res
|
||||
}
|
||||
|
||||
async function OnEmployeeDeactivate (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
||||
async function OnEmployeeDeactivate (txes: TxCUD<Doc>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
if (core.class.TxMixin !== actualTx._class) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
const ctx = actualTx as TxMixin<Person, Employee>
|
||||
if (ctx.mixin !== contact.mixin.Employee || ctx.attributes.active !== false) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
const targetAccount = control.modelDb.getAccountByPersonId(ctx.objectId) as PersonAccount[]
|
||||
if (targetAccount.length === 0) return []
|
||||
if (targetAccount.length === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
const res: Tx[] = []
|
||||
for (const acc of targetAccount) {
|
||||
const subscriptions = await control.findAll(control.ctx, notification.class.PushSubscription, {
|
||||
user: acc._id
|
||||
})
|
||||
for (const sub of subscriptions) {
|
||||
res.push(control.txFactory.createTxRemoveDoc(sub._class, sub.space, sub._id))
|
||||
result.push(control.txFactory.createTxRemoveDoc(sub._class, sub.space, sub._id))
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function OnDocRemove (txes: TxCUD<Doc>[], control: TriggerControl): Promise<Tx[]> {
|
||||
|
@ -84,22 +84,23 @@ export async function applicationTextPresenter (doc: Doc, control: TriggerContro
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnRecruitUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnRecruitUpdate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
||||
if (!control.hierarchy.isDerived(actualTx.objectClass, recruit.class.Vacancy)) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
const res: Tx[] = []
|
||||
|
||||
if (actualTx._class === core.class.TxCreateDoc) {
|
||||
handleVacancyCreate(control, actualTx, res)
|
||||
handleVacancyCreate(control, actualTx, result)
|
||||
} else if (actualTx._class === core.class.TxUpdateDoc) {
|
||||
await handleVacancyUpdate(control, actualTx, res)
|
||||
await handleVacancyUpdate(control, actualTx, result)
|
||||
} else if (actualTx._class === core.class.TxRemoveDoc) {
|
||||
handleVacancyRemove(control, actualTx, res)
|
||||
handleVacancyRemove(control, actualTx, result)
|
||||
}
|
||||
return res
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
|
@ -92,23 +92,31 @@ export async function getOwnerPosition (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnRoleNameUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnRoleNameUpdate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
const updateTx = actualTx as TxUpdateDoc<Role>
|
||||
|
||||
if (updateTx.operations?.name === undefined) return []
|
||||
if (updateTx.operations?.name === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Update the related mixin attribute
|
||||
const roleAttribute = await control.modelDb.findOne(core.class.Attribute, {
|
||||
name: updateTx.objectId
|
||||
})
|
||||
if (roleAttribute === undefined) return []
|
||||
if (roleAttribute === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const updAttrTx = control.txFactory.createTxUpdateDoc(core.class.Attribute, core.space.Model, roleAttribute._id, {
|
||||
result.push(
|
||||
control.txFactory.createTxUpdateDoc(core.class.Attribute, core.space.Model, roleAttribute._id, {
|
||||
label: getEmbeddedLabel(updateTx.operations.name)
|
||||
})
|
||||
|
||||
return [updAttrTx]
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
|
@ -49,7 +49,7 @@ export async function TagElementRemove (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function onTagReference (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnTagReference (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
@ -89,7 +89,7 @@ export async function onTagReference (txes: Tx[], control: TriggerControl): Prom
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
trigger: {
|
||||
onTagReference
|
||||
onTagReference: OnTagReference
|
||||
},
|
||||
function: {
|
||||
TagElementRemove
|
||||
|
@ -75,9 +75,9 @@ export async function FindMessages (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
|
||||
export async function OnMessageCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const message = TxProcessor.createDoc2Doc<TelegramMessage>(tx as TxCreateDoc<TelegramMessage>)
|
||||
const channel = (
|
||||
await control.findAll(control.ctx, contact.class.Channel, { _id: message.attachedTo }, { limit: 1 })
|
||||
@ -87,11 +87,12 @@ export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise
|
||||
const tx = control.txFactory.createTxUpdateDoc(channel._class, channel.space, channel._id, {
|
||||
lastMessage: message.sendOn
|
||||
})
|
||||
res.push(tx)
|
||||
result.push(tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,33 +72,57 @@ export async function OnTask (txes: Tx[], control: TriggerControl): Promise<Tx[]
|
||||
return result
|
||||
}
|
||||
|
||||
export async function OnWorkSlotUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnWorkSlotUpdate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<WorkSlot>
|
||||
if (!control.hierarchy.isDerived(actualTx.objectClass, time.class.WorkSlot)) return []
|
||||
if (!control.hierarchy.isDerived(actualTx._class, core.class.TxUpdateDoc)) return []
|
||||
if (!control.hierarchy.isDerived(actualTx.objectClass, time.class.WorkSlot)) {
|
||||
continue
|
||||
}
|
||||
if (!control.hierarchy.isDerived(actualTx._class, core.class.TxUpdateDoc)) {
|
||||
continue
|
||||
}
|
||||
const updTx = actualTx as TxUpdateDoc<WorkSlot>
|
||||
const visibility = updTx.operations.visibility
|
||||
if (visibility !== undefined) {
|
||||
const workslot = (await control.findAll(control.ctx, time.class.WorkSlot, { _id: updTx.objectId }, { limit: 1 }))[0]
|
||||
if (workslot === undefined) return []
|
||||
const todo = (await control.findAll(control.ctx, time.class.ToDo, { _id: workslot.attachedTo }))[0]
|
||||
return [control.txFactory.createTxUpdateDoc(todo._class, todo.space, todo._id, { visibility })]
|
||||
const workslot = (
|
||||
await control.findAll(control.ctx, time.class.WorkSlot, { _id: updTx.objectId }, { limit: 1 })
|
||||
)[0]
|
||||
if (workslot === undefined) {
|
||||
continue
|
||||
}
|
||||
return []
|
||||
const todo = (await control.findAll(control.ctx, time.class.ToDo, { _id: workslot.attachedTo }))[0]
|
||||
result.push(control.txFactory.createTxUpdateDoc(todo._class, todo.space, todo._id, { visibility }))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export async function OnWorkSlotCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnWorkSlotCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<WorkSlot>
|
||||
if (!control.hierarchy.isDerived(actualTx.objectClass, time.class.WorkSlot)) return []
|
||||
if (!control.hierarchy.isDerived(actualTx._class, core.class.TxCreateDoc)) return []
|
||||
if (!control.hierarchy.isDerived(actualTx.objectClass, time.class.WorkSlot)) {
|
||||
continue
|
||||
}
|
||||
if (!control.hierarchy.isDerived(actualTx._class, core.class.TxCreateDoc)) {
|
||||
continue
|
||||
}
|
||||
const workslot = TxProcessor.createDoc2Doc(actualTx as TxCreateDoc<WorkSlot>)
|
||||
const workslots = await control.findAll(control.ctx, time.class.WorkSlot, { attachedTo: workslot.attachedTo })
|
||||
if (workslots.length > 1) return []
|
||||
if (workslots.length > 1) {
|
||||
continue
|
||||
}
|
||||
const todo = (await control.findAll(control.ctx, time.class.ToDo, { _id: workslot.attachedTo }))[0]
|
||||
if (todo === undefined) return []
|
||||
if (!control.hierarchy.isDerived(todo.attachedToClass, tracker.class.Issue)) return []
|
||||
if (todo === undefined) {
|
||||
continue
|
||||
}
|
||||
if (!control.hierarchy.isDerived(todo.attachedToClass, tracker.class.Issue)) {
|
||||
continue
|
||||
}
|
||||
const issue = (await control.findAll(control.ctx, tracker.class.Issue, { _id: todo.attachedTo as Ref<Issue> }))[0]
|
||||
if (issue === undefined) return []
|
||||
if (issue === undefined) {
|
||||
continue
|
||||
}
|
||||
const project = (await control.findAll(control.ctx, task.class.Project, { _id: issue.space }))[0]
|
||||
if (project !== undefined) {
|
||||
const type = (await control.modelDb.findAll(task.class.ProjectType, { _id: project.type }))[0]
|
||||
@ -109,9 +133,11 @@ export async function OnWorkSlotCreate (tx: Tx, control: TriggerControl): Promis
|
||||
const statusMap = toIdMap(statuses)
|
||||
const typeStatuses = taskType.statuses.map((p) => statusMap.get(p)).filter((p) => p !== undefined) as Status[]
|
||||
const current = statusMap.get(issue.status)
|
||||
if (current === undefined) return []
|
||||
if (current === undefined) {
|
||||
continue
|
||||
}
|
||||
if (current.category !== task.statusCategory.UnStarted && current.category !== task.statusCategory.ToDo) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
const nextStatus = typeStatuses.find((p) => p.category === task.statusCategory.Active)
|
||||
if (nextStatus !== undefined) {
|
||||
@ -127,7 +153,7 @@ export async function OnWorkSlotCreate (tx: Tx, control: TriggerControl): Promis
|
||||
innerTx
|
||||
)
|
||||
await control.apply(control.ctx, [outerTx])
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -135,18 +161,31 @@ export async function OnWorkSlotCreate (tx: Tx, control: TriggerControl): Promis
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnToDoRemove (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnToDoRemove (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<ToDo>
|
||||
if (!control.hierarchy.isDerived(actualTx.objectClass, time.class.ToDo)) return []
|
||||
if (!control.hierarchy.isDerived(actualTx._class, core.class.TxRemoveDoc)) return []
|
||||
if (!control.hierarchy.isDerived(actualTx.objectClass, time.class.ToDo)) {
|
||||
continue
|
||||
}
|
||||
if (!control.hierarchy.isDerived(actualTx._class, core.class.TxRemoveDoc)) {
|
||||
continue
|
||||
}
|
||||
const todo = control.removedMap.get(actualTx.objectId) as ToDo
|
||||
if (todo === undefined) return []
|
||||
if (todo === undefined) {
|
||||
continue
|
||||
}
|
||||
// it was closed, do nothing
|
||||
if (todo.doneOn != null) return []
|
||||
if (todo.doneOn != null) {
|
||||
continue
|
||||
}
|
||||
const todos = await control.findAll(control.ctx, time.class.ToDo, { attachedTo: todo.attachedTo })
|
||||
if (todos.length > 0) return []
|
||||
if (todos.length > 0) {
|
||||
continue
|
||||
}
|
||||
const issue = (await control.findAll(control.ctx, tracker.class.Issue, { _id: todo.attachedTo as Ref<Issue> }))[0]
|
||||
if (issue === undefined) return []
|
||||
if (issue === undefined) {
|
||||
continue
|
||||
}
|
||||
const project = (await control.findAll(control.ctx, task.class.Project, { _id: issue.space }))[0]
|
||||
if (project !== undefined) {
|
||||
const type = (await control.modelDb.findAll(task.class.ProjectType, { _id: project.type }))[0]
|
||||
@ -158,8 +197,12 @@ export async function OnToDoRemove (tx: Tx, control: TriggerControl): Promise<Tx
|
||||
const statusMap = toIdMap(statuses)
|
||||
const typeStatuses = taskType.statuses.map((p) => statusMap.get(p)).filter((p) => p !== undefined) as Status[]
|
||||
const current = statusMap.get(issue.status)
|
||||
if (current === undefined) return []
|
||||
if (current.category !== task.statusCategory.Active && current.category !== task.statusCategory.ToDo) return []
|
||||
if (current === undefined) {
|
||||
continue
|
||||
}
|
||||
if (current.category !== task.statusCategory.Active && current.category !== task.statusCategory.ToDo) {
|
||||
continue
|
||||
}
|
||||
const nextStatus = typeStatuses.find((p) => p.category === task.statusCategory.UnStarted)
|
||||
if (nextStatus !== undefined) {
|
||||
const innerTx = factory.createTxUpdateDoc(issue._class, issue.space, issue._id, {
|
||||
@ -173,7 +216,7 @@ export async function OnToDoRemove (tx: Tx, control: TriggerControl): Promise<Tx
|
||||
innerTx
|
||||
)
|
||||
await control.apply(control.ctx, [outerTx])
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -181,12 +224,17 @@ export async function OnToDoRemove (tx: Tx, control: TriggerControl): Promise<Tx
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnToDoCreate (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnToDoCreate (txes: TxCUD<Doc>[], control: TriggerControl): Promise<Tx[]> {
|
||||
const hierarchy = control.hierarchy
|
||||
for (const tx of txes) {
|
||||
const createTx = TxProcessor.extractTx(tx) as TxCreateDoc<ToDo>
|
||||
|
||||
if (!hierarchy.isDerived(createTx.objectClass, time.class.ToDo)) return []
|
||||
if (!hierarchy.isDerived(createTx._class, core.class.TxCreateDoc)) return []
|
||||
if (!hierarchy.isDerived(createTx.objectClass, time.class.ToDo)) {
|
||||
continue
|
||||
}
|
||||
if (!hierarchy.isDerived(createTx._class, core.class.TxCreateDoc)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const mixin = hierarchy.classHierarchyMixin(
|
||||
createTx.objectClass as Ref<Class<Doc>>,
|
||||
@ -194,18 +242,20 @@ export async function OnToDoCreate (tx: TxCUD<Doc>, control: TriggerControl): Pr
|
||||
)
|
||||
|
||||
if (mixin === undefined) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
const todo = TxProcessor.createDoc2Doc(createTx)
|
||||
const account = control.modelDb.getAccountByPersonId(todo.user) as PersonAccount[]
|
||||
|
||||
if (account.length === 0) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
const object = (await control.findAll(control.ctx, todo.attachedToClass, { _id: todo.attachedTo }))[0]
|
||||
if (object === undefined) return []
|
||||
if (object === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const person = (
|
||||
await control.findAll(
|
||||
@ -215,12 +265,16 @@ export async function OnToDoCreate (tx: TxCUD<Doc>, control: TriggerControl): Pr
|
||||
{ limit: 1 }
|
||||
)
|
||||
)[0]
|
||||
if (person === undefined) return []
|
||||
if (person === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
const personSpace = (
|
||||
await control.findAll(control.ctx, contact.class.PersonSpace, { person: todo.user }, { limit: 1 })
|
||||
)[0]
|
||||
if (personSpace === undefined) return []
|
||||
if (personSpace === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: Select a proper account
|
||||
const receiverInfo: ReceiverInfo = {
|
||||
@ -279,16 +333,23 @@ export async function OnToDoCreate (tx: TxCUD<Doc>, control: TriggerControl): Pr
|
||||
return [receiverInfo.account.email]
|
||||
}
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnToDoUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnToDoUpdate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<ToDo>
|
||||
if (!control.hierarchy.isDerived(actualTx.objectClass, time.class.ToDo)) return []
|
||||
if (!control.hierarchy.isDerived(actualTx._class, core.class.TxUpdateDoc)) return []
|
||||
if (!control.hierarchy.isDerived(actualTx.objectClass, time.class.ToDo)) {
|
||||
continue
|
||||
}
|
||||
if (!control.hierarchy.isDerived(actualTx._class, core.class.TxUpdateDoc)) {
|
||||
continue
|
||||
}
|
||||
const updTx = actualTx as TxUpdateDoc<ToDo>
|
||||
const doneOn = updTx.operations.doneOn
|
||||
const title = updTx.operations.title
|
||||
@ -296,7 +357,6 @@ export async function OnToDoUpdate (tx: Tx, control: TriggerControl): Promise<Tx
|
||||
const visibility = updTx.operations.visibility
|
||||
if (doneOn != null) {
|
||||
const events = await control.findAll(control.ctx, time.class.WorkSlot, { attachedTo: updTx.objectId })
|
||||
const res: Tx[] = []
|
||||
const resEvents: WorkSlot[] = []
|
||||
for (const event of events) {
|
||||
if (event.date > doneOn) {
|
||||
@ -308,7 +368,7 @@ export async function OnToDoUpdate (tx: Tx, control: TriggerControl): Promise<Tx
|
||||
event.collection,
|
||||
innerTx
|
||||
)
|
||||
res.push(outerTx)
|
||||
result.push(outerTx)
|
||||
} else if (event.dueDate > doneOn) {
|
||||
const upd: DocumentUpdate<WorkSlot> = {
|
||||
dueDate: doneOn
|
||||
@ -327,7 +387,7 @@ export async function OnToDoUpdate (tx: Tx, control: TriggerControl): Promise<Tx
|
||||
event.collection,
|
||||
innerTx
|
||||
)
|
||||
res.push(outerTx)
|
||||
result.push(outerTx)
|
||||
resEvents.push({
|
||||
...event,
|
||||
dueDate: doneOn
|
||||
@ -337,7 +397,9 @@ export async function OnToDoUpdate (tx: Tx, control: TriggerControl): Promise<Tx
|
||||
}
|
||||
}
|
||||
const todo = (await control.findAll(control.ctx, time.class.ToDo, { _id: updTx.objectId }))[0]
|
||||
if (todo === undefined) return res
|
||||
if (todo === undefined) {
|
||||
continue
|
||||
}
|
||||
const funcs = control.hierarchy.classHierarchyMixin<Class<Doc>, OnToDo>(
|
||||
todo.attachedToClass,
|
||||
serverTime.mixin.OnToDo
|
||||
@ -347,11 +409,10 @@ export async function OnToDoUpdate (tx: Tx, control: TriggerControl): Promise<Tx
|
||||
const todoRes = await func(control, resEvents, todo)
|
||||
await control.apply(control.ctx, todoRes)
|
||||
}
|
||||
return res
|
||||
continue
|
||||
}
|
||||
if (title !== undefined || description !== undefined || visibility !== undefined) {
|
||||
const events = await control.findAll(control.ctx, time.class.WorkSlot, { attachedTo: updTx.objectId })
|
||||
const res: Tx[] = []
|
||||
for (const event of events) {
|
||||
const upd: DocumentUpdate<WorkSlot> = {}
|
||||
if (title !== undefined) {
|
||||
@ -374,11 +435,11 @@ export async function OnToDoUpdate (tx: Tx, control: TriggerControl): Promise<Tx
|
||||
event.collection,
|
||||
innerTx
|
||||
)
|
||||
res.push(outerTx)
|
||||
result.push(outerTx)
|
||||
}
|
||||
return res
|
||||
}
|
||||
return []
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,6 +13,8 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import chunter, { ChatMessage } from '@hcengineering/chunter'
|
||||
import { Person, PersonAccount } from '@hcengineering/contact'
|
||||
import core, {
|
||||
Account,
|
||||
AccountRole,
|
||||
@ -31,15 +33,13 @@ import core, {
|
||||
TxUpdateDoc,
|
||||
WithLookup
|
||||
} from '@hcengineering/core'
|
||||
import { getMetadata, IntlString } from '@hcengineering/platform'
|
||||
import { Person, PersonAccount } from '@hcengineering/contact'
|
||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||
import tracker, { Component, Issue, IssueParentInfo, TimeSpendReport, trackerId } from '@hcengineering/tracker'
|
||||
import { NotificationContent } from '@hcengineering/notification'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
import { stripTags } from '@hcengineering/text'
|
||||
import chunter, { ChatMessage } from '@hcengineering/chunter'
|
||||
import { getMetadata, IntlString } from '@hcengineering/platform'
|
||||
import serverCore, { TriggerControl } from '@hcengineering/server-core'
|
||||
import { NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification'
|
||||
import { stripTags } from '@hcengineering/text'
|
||||
import tracker, { Component, Issue, IssueParentInfo, TimeSpendReport, trackerId } from '@hcengineering/tracker'
|
||||
import { workbenchId } from '@hcengineering/workbench'
|
||||
|
||||
async function updateSubIssues (
|
||||
updateTx: TxUpdateDoc<Issue>,
|
||||
@ -160,30 +160,35 @@ export async function getIssueNotificationContent (
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnComponentRemove (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnComponentRemove (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxRemoveDoc<Component>
|
||||
|
||||
const issues = await control.findAll(control.ctx, tracker.class.Issue, {
|
||||
component: ctx.objectId
|
||||
})
|
||||
if (issues === undefined) return []
|
||||
const res: Tx[] = []
|
||||
|
||||
if (issues === undefined) {
|
||||
continue
|
||||
}
|
||||
for (const issue of issues) {
|
||||
const issuePush = {
|
||||
...issue,
|
||||
component: null
|
||||
}
|
||||
const tx = control.txFactory.createTxUpdateDoc(issue._class, issue.space, issue._id, issuePush)
|
||||
res.push(tx)
|
||||
result.push(tx)
|
||||
}
|
||||
return res
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnWorkspaceOwnerAdded (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
let ownerId: Ref<PersonAccount> | undefined
|
||||
if (control.hierarchy.isDerived(tx._class, core.class.TxCreateDoc)) {
|
||||
const createTx = tx as TxCreateDoc<PersonAccount>
|
||||
@ -200,7 +205,7 @@ export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): P
|
||||
}
|
||||
|
||||
if (ownerId === undefined) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
const targetProject = (
|
||||
@ -210,7 +215,7 @@ export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): P
|
||||
)[0]
|
||||
|
||||
if (targetProject === undefined) {
|
||||
return []
|
||||
continue
|
||||
}
|
||||
|
||||
if (
|
||||
@ -218,19 +223,22 @@ export async function OnWorkspaceOwnerAdded (tx: Tx, control: TriggerControl): P
|
||||
targetProject.owners.length === 0 ||
|
||||
targetProject.owners[0] === core.account.System
|
||||
) {
|
||||
const updTx = control.txFactory.createTxUpdateDoc(tracker.class.Project, targetProject.space, targetProject._id, {
|
||||
result.push(
|
||||
control.txFactory.createTxUpdateDoc(tracker.class.Project, targetProject.space, targetProject._id, {
|
||||
owners: [ownerId]
|
||||
})
|
||||
return [updTx]
|
||||
)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnIssueUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnIssueUpdate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const actualTx = TxProcessor.extractTx(tx)
|
||||
|
||||
// Check TimeReport operations
|
||||
@ -241,7 +249,7 @@ export async function OnIssueUpdate (tx: Tx, control: TriggerControl): Promise<T
|
||||
) {
|
||||
const cud = actualTx as TxCUD<TimeSpendReport>
|
||||
if (cud.objectClass === tracker.class.TimeSpendReport) {
|
||||
return await doTimeReportUpdate(cud, tx, control)
|
||||
result.push(...(await doTimeReportUpdate(cud, tx, control)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,17 +257,16 @@ export async function OnIssueUpdate (tx: Tx, control: TriggerControl): Promise<T
|
||||
const createTx = actualTx as TxCreateDoc<Issue>
|
||||
if (control.hierarchy.isDerived(createTx.objectClass, tracker.class.Issue)) {
|
||||
const issue = TxProcessor.createDoc2Doc(createTx)
|
||||
const res: Tx[] = []
|
||||
updateIssueParentEstimations(issue, res, control, [], issue.parents)
|
||||
|
||||
return res
|
||||
updateIssueParentEstimations(issue, result, control, [], issue.parents)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (actualTx._class === core.class.TxUpdateDoc) {
|
||||
const updateTx = actualTx as TxUpdateDoc<Issue>
|
||||
if (control.hierarchy.isDerived(updateTx.objectClass, tracker.class.Issue)) {
|
||||
return await doIssueUpdate(updateTx, control, tx as TxCollectionCUD<Issue, AttachedDoc>)
|
||||
result.push(...(await doIssueUpdate(updateTx, control, tx as TxCollectionCUD<Issue, AttachedDoc>)))
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (actualTx._class === core.class.TxRemoveDoc) {
|
||||
@ -268,7 +275,6 @@ export async function OnIssueUpdate (tx: Tx, control: TriggerControl): Promise<T
|
||||
const parentIssue = await control.findAll(control.ctx, tracker.class.Issue, {
|
||||
'childInfo.childId': removeTx.objectId
|
||||
})
|
||||
const res: Tx[] = []
|
||||
const parents: IssueParentInfo[] = parentIssue.map((it) => ({
|
||||
parentId: it._id,
|
||||
parentTitle: it.title,
|
||||
@ -282,15 +288,15 @@ export async function OnIssueUpdate (tx: Tx, control: TriggerControl): Promise<T
|
||||
reportedTime: 0,
|
||||
space: removeTx.space
|
||||
},
|
||||
res,
|
||||
result,
|
||||
control,
|
||||
parents,
|
||||
[]
|
||||
)
|
||||
return res
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async function doTimeReportUpdate (cud: TxCUD<TimeSpendReport>, tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
|
@ -20,24 +20,30 @@ import view from '@hcengineering/view'
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function OnCustomAttributeRemove (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
export async function OnCustomAttributeRemove (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
const hierarchy = control.hierarchy
|
||||
const ptx = tx as TxRemoveDoc<AnyAttribute>
|
||||
if (!checkTx(ptx, hierarchy)) return []
|
||||
if (!checkTx(ptx, hierarchy)) {
|
||||
continue
|
||||
}
|
||||
const attribute = control.removedMap.get(ptx.objectId) as AnyAttribute
|
||||
if (attribute === undefined) return []
|
||||
if (attribute === undefined) {
|
||||
continue
|
||||
}
|
||||
const preferences = await control.findAll(control.ctx, view.class.ViewletPreference, {
|
||||
config: attribute.name,
|
||||
space: core.space.Workspace
|
||||
})
|
||||
const res: Tx[] = []
|
||||
for (const preference of preferences) {
|
||||
const tx = control.txFactory.createTxUpdateDoc(preference._class, preference.space, preference._id, {
|
||||
$pull: { config: attribute.name }
|
||||
})
|
||||
res.push(tx)
|
||||
result.push(tx)
|
||||
}
|
||||
return res
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function checkTx (ptx: TxRemoveDoc<AnyAttribute>, hierarchy: Hierarchy): boolean {
|
||||
|
@ -228,11 +228,7 @@ export function serveAccount (measureCtx: MeasureContext, brandings: BrandingMap
|
||||
host = new URL(origin).host
|
||||
}
|
||||
const branding = host !== undefined ? brandings[host] : null
|
||||
const result = await measureCtx.with(
|
||||
request.method,
|
||||
{},
|
||||
async (ctx) => await method(ctx, db, branding, request, token)
|
||||
)
|
||||
const result = await measureCtx.with(request.method, {}, (ctx) => method(ctx, db, branding, request, token))
|
||||
|
||||
ctx.body = result
|
||||
})
|
||||
|
@ -1087,10 +1087,8 @@ export async function workerHandshake (
|
||||
return
|
||||
}
|
||||
|
||||
const workspacesCnt = await ctx.with(
|
||||
'count-workspaces-in-region',
|
||||
{},
|
||||
async (ctx) => await countWorkspacesInRegion(db, region, version, Date.now() - 24 * 60 * 60 * 1000)
|
||||
const workspacesCnt = await ctx.with('count-workspaces-in-region', {}, (ctx) =>
|
||||
countWorkspacesInRegion(db, region, version, Date.now() - 24 * 60 * 60 * 1000)
|
||||
)
|
||||
|
||||
await db.upgrade.insertOne({
|
||||
@ -1485,7 +1483,7 @@ export async function getWorkspaceInfo (
|
||||
workspace: workspace.name
|
||||
}
|
||||
if (email !== systemAccountEmail && !guest) {
|
||||
account = await ctx.with('get-account', {}, async () => await getAccount(db, email))
|
||||
account = await ctx.with('get-account', {}, () => getAccount(db, email))
|
||||
if (account === null) {
|
||||
ctx.error('no account', { email, token })
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
@ -1520,9 +1518,7 @@ export async function getWorkspaceInfo (
|
||||
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
|
||||
}
|
||||
if (ws.mode !== 'archived' && _updateLastVisit && (isAccount(account) || email === systemAccountEmail)) {
|
||||
void ctx.with('update-last-visit', {}, async () => {
|
||||
await updateLastVisit(db, ws, account as Account)
|
||||
})
|
||||
void ctx.with('update-last-visit', {}, () => updateLastVisit(db, ws, account as Account))
|
||||
}
|
||||
|
||||
const clientWs: ClientWSInfoWithUpgrade = mapToClientWorkspace(ws)
|
||||
|
@ -452,9 +452,7 @@ export async function cloneWorkspace (
|
||||
ctx.info('clone domain...', { domain: c, workspace: targetWorkspaceId.name })
|
||||
|
||||
// We need to clean target connection before copying something.
|
||||
await ctx.with('clean-domain', { domain: c }, async (ctx) => {
|
||||
await cleanDomain(ctx, targetConnection, c)
|
||||
})
|
||||
await ctx.with('clean-domain', { domain: c }, (ctx) => cleanDomain(ctx, targetConnection, c))
|
||||
|
||||
const changes: Snapshot = {
|
||||
added: new Map(),
|
||||
@ -474,7 +472,7 @@ export async function cloneWorkspace (
|
||||
await ctx.with('retrieve-domain-info', { domain: c }, async (ctx) => {
|
||||
while (true) {
|
||||
try {
|
||||
const it = await ctx.with('load-chunk', {}, async () => await sourceConnection.loadChunk(c, idx))
|
||||
const it = await ctx.with('load-chunk', {}, () => sourceConnection.loadChunk(c, idx))
|
||||
idx = it.idx
|
||||
|
||||
let needRetrieve: Ref<Doc>[] = []
|
||||
@ -510,9 +508,7 @@ export async function cloneWorkspace (
|
||||
} catch (err: any) {
|
||||
ctx.error('failed to clone', { err, workspace: targetWorkspaceId.name })
|
||||
if (idx !== undefined) {
|
||||
await ctx.with('load-chunk', {}, async () => {
|
||||
await sourceConnection.closeChunk(idx as number)
|
||||
})
|
||||
await ctx.with('load-chunk', {}, () => sourceConnection.closeChunk(idx as number))
|
||||
}
|
||||
// Try again
|
||||
idx = undefined
|
||||
@ -527,7 +523,7 @@ export async function cloneWorkspace (
|
||||
ctx.info('Retrieve chunk:', { count: needRetrieve.length })
|
||||
let docs: Doc[] = []
|
||||
try {
|
||||
docs = await ctx.with('load-docs', {}, async (ctx) => await sourceConnection.loadDocs(c, needRetrieve))
|
||||
docs = await ctx.with('load-docs', {}, (ctx) => sourceConnection.loadDocs(c, needRetrieve))
|
||||
if (clearTime) {
|
||||
docs = prepareClonedDocuments(docs, sourceConnection)
|
||||
}
|
||||
@ -561,14 +557,7 @@ export async function cloneWorkspace (
|
||||
}
|
||||
}
|
||||
await executor.waitProcessing()
|
||||
await ctx.with(
|
||||
'upload-docs',
|
||||
{},
|
||||
async (ctx) => {
|
||||
await targetConnection.upload(c, docs)
|
||||
},
|
||||
{ length: docs.length }
|
||||
)
|
||||
await ctx.with('upload-docs', {}, (ctx) => targetConnection.upload(c, docs), { length: docs.length })
|
||||
await progress((100 / domains.length) * i + (100 / domains.length / processed) * domainProgress)
|
||||
} catch (err: any) {
|
||||
console.log(err)
|
||||
|
@ -62,7 +62,7 @@ export class BlobClient {
|
||||
if (i === 4) {
|
||||
ctx.error('Failed to check file', { name, error: err })
|
||||
}
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 500))
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 10))
|
||||
}
|
||||
}
|
||||
return false
|
||||
@ -189,7 +189,7 @@ export class BlobClient {
|
||||
})
|
||||
throw err
|
||||
}
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 1000))
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 10))
|
||||
// retry
|
||||
}
|
||||
}
|
||||
|
@ -42,14 +42,8 @@ async function loadCollaborativeDocVersion (
|
||||
documentId: string,
|
||||
versionId: string
|
||||
): Promise<YDoc | undefined> {
|
||||
const yContent = await ctx.with('yDocFromStorage', { type: 'content' }, async (ctx) => {
|
||||
return await yDocFromStorage(
|
||||
ctx,
|
||||
storageAdapter,
|
||||
workspace,
|
||||
documentId,
|
||||
new YDoc({ guid: generateId(), gc: false })
|
||||
)
|
||||
const yContent = await ctx.with('yDocFromStorage', { type: 'content' }, (ctx) => {
|
||||
return yDocFromStorage(ctx, storageAdapter, workspace, documentId, new YDoc({ guid: generateId(), gc: false }))
|
||||
})
|
||||
|
||||
// the document does not exist
|
||||
@ -62,8 +56,8 @@ async function loadCollaborativeDocVersion (
|
||||
}
|
||||
|
||||
const historyDocumentId = collaborativeHistoryDocId(documentId)
|
||||
const yHistory = await ctx.with('yDocFromStorage', { type: 'history' }, async (ctx) => {
|
||||
return await yDocFromStorage(ctx, storageAdapter, workspace, historyDocumentId, new YDoc({ guid: generateId() }))
|
||||
const yHistory = await ctx.with('yDocFromStorage', { type: 'history' }, (ctx) => {
|
||||
return yDocFromStorage(ctx, storageAdapter, workspace, historyDocumentId, new YDoc({ guid: generateId() }))
|
||||
})
|
||||
|
||||
// the history document does not exist
|
||||
@ -122,9 +116,7 @@ export async function saveCollaborativeDocVersion (
|
||||
): Promise<void> {
|
||||
await ctx.with('saveCollaborativeDoc', {}, async (ctx) => {
|
||||
if (versionId === 'HEAD') {
|
||||
await ctx.with('yDocToStorage', {}, async () => {
|
||||
await yDocToStorage(ctx, storageAdapter, workspace, documentId, ydoc)
|
||||
})
|
||||
await ctx.with('yDocToStorage', {}, () => yDocToStorage(ctx, storageAdapter, workspace, documentId, ydoc))
|
||||
} else {
|
||||
console.warn('Cannot save non HEAD document version', documentId, versionId)
|
||||
}
|
||||
@ -149,9 +141,7 @@ export async function removeCollaborativeDoc (
|
||||
}
|
||||
}
|
||||
if (toRemove.length > 0) {
|
||||
await ctx.with('remove', {}, async () => {
|
||||
await storageAdapter.remove(ctx, workspace, toRemove)
|
||||
})
|
||||
await ctx.with('remove', {}, () => storageAdapter.remove(ctx, workspace, toRemove))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -129,8 +129,8 @@ export class StorageExtension implements Extension {
|
||||
const { ctx, adapter } = this.configuration
|
||||
|
||||
try {
|
||||
return await ctx.with('load-document', {}, async (ctx) => {
|
||||
return await adapter.loadDocument(ctx, documentName as DocumentId, context)
|
||||
return await ctx.with('load-document', {}, (ctx) => {
|
||||
return adapter.loadDocument(ctx, documentName as DocumentId, context)
|
||||
})
|
||||
} catch (err) {
|
||||
ctx.error('failed to load document', { documentName, error: err })
|
||||
@ -145,12 +145,12 @@ export class StorageExtension implements Extension {
|
||||
const prevMarkup = this.markups.get(documentName) ?? {}
|
||||
const currMarkup = this.configuration.transformer.fromYdoc(document)
|
||||
|
||||
await ctx.with('save-document', {}, async (ctx) => {
|
||||
await adapter.saveDocument(ctx, documentName as DocumentId, document, context, {
|
||||
await ctx.with('save-document', {}, (ctx) =>
|
||||
adapter.saveDocument(ctx, documentName as DocumentId, document, context, {
|
||||
prev: prevMarkup,
|
||||
curr: currMarkup
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
this.markups.set(documentName, currMarkup)
|
||||
} catch (err) {
|
||||
|
@ -27,8 +27,8 @@ export async function getContent (
|
||||
): Promise<GetContentResponse> {
|
||||
const { hocuspocus, transformer } = params
|
||||
|
||||
const connection = await ctx.with('connect', {}, async () => {
|
||||
return await hocuspocus.openDirectConnection(documentId, context)
|
||||
const connection = await ctx.with('connect', {}, () => {
|
||||
return hocuspocus.openDirectConnection(documentId, context)
|
||||
})
|
||||
|
||||
try {
|
||||
|
@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { MeasureContext } from '@hcengineering/core'
|
||||
import { type UpdateContentRequest, type UpdateContentResponse } from '@hcengineering/collaborator-client'
|
||||
import { MeasureContext } from '@hcengineering/core'
|
||||
import { applyUpdate, encodeStateAsUpdate } from 'yjs'
|
||||
import { Context } from '../../context'
|
||||
import { RpcMethodParams } from '../rpc'
|
||||
@ -29,7 +29,7 @@ export async function updateContent (
|
||||
const { content } = payload
|
||||
const { hocuspocus, transformer } = params
|
||||
|
||||
const updates = await ctx.with('transform', {}, () => {
|
||||
const updates = ctx.withSync('transform', {}, () => {
|
||||
const updates: Record<string, Uint8Array> = {}
|
||||
|
||||
Object.entries(content).forEach(([field, markup]) => {
|
||||
@ -40,13 +40,13 @@ export async function updateContent (
|
||||
return updates
|
||||
})
|
||||
|
||||
const connection = await ctx.with('connect', {}, async () => {
|
||||
return await hocuspocus.openDirectConnection(documentId, context)
|
||||
const connection = await ctx.with('connect', {}, () => {
|
||||
return hocuspocus.openDirectConnection(documentId, context)
|
||||
})
|
||||
|
||||
try {
|
||||
await ctx.with('update', {}, async () => {
|
||||
await connection.transact((document) => {
|
||||
await ctx.with('update', {}, () =>
|
||||
connection.transact((document) => {
|
||||
document.transact(() => {
|
||||
Object.entries(updates).forEach(([field, update]) => {
|
||||
const fragment = document.getXmlFragment(field)
|
||||
@ -55,7 +55,7 @@ export async function updateContent (
|
||||
})
|
||||
}, connection)
|
||||
})
|
||||
})
|
||||
)
|
||||
} finally {
|
||||
await connection.disconnect()
|
||||
}
|
||||
|
@ -169,8 +169,8 @@ export async function start (ctx: MeasureContext, config: Config, storageAdapter
|
||||
rpcCtx.info('rpc', { method: request.method, connectionId: context.connectionId, mode: token.extra?.mode ?? '' })
|
||||
await rpcCtx.with('/rpc', { method: request.method }, async (ctx) => {
|
||||
try {
|
||||
const response: RpcResponse = await rpcCtx.with(request.method, {}, async (ctx) => {
|
||||
return await method(ctx, context, documentId, request.payload, { hocuspocus, storageAdapter, transformer })
|
||||
const response: RpcResponse = await rpcCtx.with(request.method, {}, (ctx) => {
|
||||
return method(ctx, context, documentId, request.payload, { hocuspocus, storageAdapter, transformer })
|
||||
})
|
||||
res.status(200).send(response)
|
||||
} catch (err: any) {
|
||||
|
@ -33,8 +33,8 @@ import { Doc as YDoc } from 'yjs'
|
||||
|
||||
import { Context } from '../context'
|
||||
|
||||
import { CollabStorageAdapter } from './adapter'
|
||||
import { areEqualMarkups } from '@hcengineering/text'
|
||||
import { CollabStorageAdapter } from './adapter'
|
||||
|
||||
export class PlatformStorageAdapter implements CollabStorageAdapter {
|
||||
constructor (private readonly storage: StorageAdapter) {}
|
||||
@ -89,9 +89,7 @@ export class PlatformStorageAdapter implements CollabStorageAdapter {
|
||||
): Promise<void> {
|
||||
const { clientFactory } = context
|
||||
|
||||
const client = await ctx.with('connect', {}, async () => {
|
||||
return await clientFactory()
|
||||
})
|
||||
const client = await ctx.with('connect', {}, () => clientFactory())
|
||||
|
||||
try {
|
||||
try {
|
||||
@ -107,9 +105,9 @@ export class PlatformStorageAdapter implements CollabStorageAdapter {
|
||||
const { platformDocumentId } = context
|
||||
if (platformDocumentId !== undefined) {
|
||||
ctx.info('save document content to platform', { documentId, platformDocumentId })
|
||||
await ctx.with('save-to-platform', {}, async (ctx) => {
|
||||
await this.saveDocumentToPlatform(ctx, client, documentId, platformDocumentId, markup)
|
||||
})
|
||||
await ctx.with('save-to-platform', {}, (ctx) =>
|
||||
this.saveDocumentToPlatform(ctx, client, documentId, platformDocumentId, markup)
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
await client.close()
|
||||
@ -123,11 +121,11 @@ export class PlatformStorageAdapter implements CollabStorageAdapter {
|
||||
): Promise<YDoc | undefined> {
|
||||
const { collaborativeDoc } = parseDocumentId(documentId)
|
||||
|
||||
return await ctx.with('load-document', {}, async (ctx) => {
|
||||
return await withRetry(ctx, 5, async () => {
|
||||
return await ctx.with('load-document', {}, (ctx) =>
|
||||
withRetry(ctx, 5, async () => {
|
||||
return await loadCollaborativeDoc(ctx, this.storage, context.workspaceId, collaborativeDoc)
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async saveDocumentToStorage (
|
||||
@ -138,11 +136,11 @@ export class PlatformStorageAdapter implements CollabStorageAdapter {
|
||||
): Promise<void> {
|
||||
const { collaborativeDoc } = parseDocumentId(documentId)
|
||||
|
||||
await ctx.with('save-document', {}, async (ctx) => {
|
||||
await withRetry(ctx, 5, async () => {
|
||||
await ctx.with('save-document', {}, (ctx) =>
|
||||
withRetry(ctx, 5, async () => {
|
||||
await saveCollaborativeDoc(ctx, this.storage, context.workspaceId, collaborativeDoc, document)
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async saveDocumentToPlatform (
|
||||
@ -171,8 +169,8 @@ export class PlatformStorageAdapter implements CollabStorageAdapter {
|
||||
return
|
||||
}
|
||||
|
||||
const current = await ctx.with('query', {}, async () => {
|
||||
return await client.findOne(objectClass, { _id: objectId })
|
||||
const current = await ctx.with('query', {}, () => {
|
||||
return client.findOne(objectClass, { _id: objectId })
|
||||
})
|
||||
|
||||
if (current === undefined) {
|
||||
@ -189,11 +187,9 @@ export class PlatformStorageAdapter implements CollabStorageAdapter {
|
||||
const collaborativeDoc = (current as any)[objectAttr] as CollaborativeDoc
|
||||
const newCollaborativeDoc = collaborativeDocWithLastVersion(collaborativeDoc, `${Date.now()}`)
|
||||
|
||||
await ctx.with('update', {}, async () => {
|
||||
await client.diffUpdate(current, { [objectAttr]: newCollaborativeDoc })
|
||||
})
|
||||
await ctx.with('update', {}, () => client.diffUpdate(current, { [objectAttr]: newCollaborativeDoc }))
|
||||
|
||||
await ctx.with('activity', {}, async () => {
|
||||
await ctx.with('activity', {}, () => {
|
||||
const data: AttachedData<DocUpdateMessage> = {
|
||||
objectId,
|
||||
objectClass,
|
||||
@ -208,7 +204,7 @@ export class PlatformStorageAdapter implements CollabStorageAdapter {
|
||||
isMixin: hierarchy.isMixin(objectClass)
|
||||
}
|
||||
}
|
||||
await client.addCollection(
|
||||
return client.addCollection(
|
||||
activity.class.DocUpdateMessage,
|
||||
current.space,
|
||||
current._id,
|
||||
|
@ -90,7 +90,7 @@ class BenchmarkDbAdapter extends DummyDbAdapter {
|
||||
return toFindResult<T>(result as T[])
|
||||
}
|
||||
|
||||
async tx (ctx: MeasureContext, ...tx: Tx[]): Promise<TxResult[]> {
|
||||
tx (ctx: MeasureContext, ...tx: Tx[]): Promise<TxResult[]> {
|
||||
if (benchData === '') {
|
||||
benchData = genData(1024 * 1024)
|
||||
}
|
||||
@ -102,16 +102,16 @@ class BenchmarkDbAdapter extends DummyDbAdapter {
|
||||
if (request?.size != null) {
|
||||
const dataSize =
|
||||
typeof request.size === 'number' ? request.size : request.size.from + Math.random() * request.size.to
|
||||
return [
|
||||
return Promise.resolve([
|
||||
{
|
||||
response: benchData.slice(0, dataSize)
|
||||
}
|
||||
]
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [{}]
|
||||
return Promise.resolve([{}])
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
@ -99,8 +99,8 @@ export class DbAdapterManagerImpl implements DBAdapterManager {
|
||||
}
|
||||
}
|
||||
|
||||
async initAdapters (ctx: MeasureContext): Promise<void> {
|
||||
await ctx.with('init-adapters', {}, async (ctx) => {
|
||||
initAdapters (ctx: MeasureContext): Promise<void> {
|
||||
return ctx.with('init-adapters', {}, async (ctx) => {
|
||||
for (const [key, adapter] of this.adapters) {
|
||||
// already initialized
|
||||
if (key !== this.conf.domains[DOMAIN_TX] && adapter.init !== undefined) {
|
||||
|
@ -133,20 +133,20 @@ class InMemoryAdapter extends DummyDbAdapter implements DbAdapter {
|
||||
this.modeldb = new ModelDb(hierarchy)
|
||||
}
|
||||
|
||||
async findAll<T extends Doc>(
|
||||
findAll<T extends Doc>(
|
||||
ctx: MeasureContext,
|
||||
_class: Ref<Class<T>>,
|
||||
query: DocumentQuery<T>,
|
||||
options?: FindOptions<T>
|
||||
): Promise<FindResult<T>> {
|
||||
return await this.modeldb.findAll(_class, query, options)
|
||||
return ctx.withSync('inmem-find', {}, () => this.modeldb.findAll(_class, query, options))
|
||||
}
|
||||
|
||||
async load (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<Doc[]> {
|
||||
return await this.modeldb.findAll(core.class.Doc, { _id: { $in: docs } })
|
||||
load (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<Doc[]> {
|
||||
return this.modeldb.findAll(core.class.Doc, { _id: { $in: docs } })
|
||||
}
|
||||
|
||||
async tx (ctx: MeasureContext, ...tx: Tx[]): Promise<TxResult[]> {
|
||||
tx (ctx: MeasureContext, ...tx: Tx[]): Promise<TxResult[]> {
|
||||
// Filter transactions with broadcast only flags
|
||||
const ftx = tx.filter((it) => {
|
||||
if (TxProcessor.isExtendsCUD(it._class)) {
|
||||
@ -161,9 +161,9 @@ class InMemoryAdapter extends DummyDbAdapter implements DbAdapter {
|
||||
return true
|
||||
})
|
||||
if (ftx.length === 0) {
|
||||
return []
|
||||
return Promise.resolve([])
|
||||
}
|
||||
return await this.modeldb.tx(...ftx)
|
||||
return this.modeldb.tx(...ftx)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,37 +92,37 @@ class PipelineImpl implements Pipeline {
|
||||
return current
|
||||
}
|
||||
|
||||
async findAll<T extends Doc>(
|
||||
findAll<T extends Doc>(
|
||||
ctx: MeasureContext,
|
||||
_class: Ref<Class<T>>,
|
||||
query: DocumentQuery<T>,
|
||||
options?: FindOptions<T>
|
||||
): Promise<FindResult<T>> {
|
||||
return this.head !== undefined ? await this.head.findAll(ctx, _class, query, options) : toFindResult([])
|
||||
return this.head !== undefined ? this.head.findAll(ctx, _class, query, options) : Promise.resolve(toFindResult([]))
|
||||
}
|
||||
|
||||
async loadModel (ctx: MeasureContext, lastModelTx: Timestamp, hash?: string): Promise<Tx[] | LoadModelResponse> {
|
||||
return this.head !== undefined ? await this.head.loadModel(ctx, lastModelTx, hash) : []
|
||||
loadModel (ctx: MeasureContext, lastModelTx: Timestamp, hash?: string): Promise<Tx[] | LoadModelResponse> {
|
||||
return this.head !== undefined ? this.head.loadModel(ctx, lastModelTx, hash) : Promise.resolve([])
|
||||
}
|
||||
|
||||
async groupBy<T, P extends Doc>(
|
||||
groupBy<T, P extends Doc>(
|
||||
ctx: MeasureContext,
|
||||
domain: Domain,
|
||||
field: string,
|
||||
query?: DocumentQuery<P>
|
||||
): Promise<Map<T, number>> {
|
||||
return this.head !== undefined ? await this.head.groupBy(ctx, domain, field, query) : new Map()
|
||||
return this.head !== undefined ? this.head.groupBy(ctx, domain, field, query) : Promise.resolve(new Map())
|
||||
}
|
||||
|
||||
async searchFulltext (ctx: MeasureContext, query: SearchQuery, options: SearchOptions): Promise<SearchResult> {
|
||||
return this.head !== undefined ? await this.head.searchFulltext(ctx, query, options) : { docs: [] }
|
||||
searchFulltext (ctx: MeasureContext, query: SearchQuery, options: SearchOptions): Promise<SearchResult> {
|
||||
return this.head !== undefined ? this.head.searchFulltext(ctx, query, options) : Promise.resolve({ docs: [] })
|
||||
}
|
||||
|
||||
async tx (ctx: MeasureContext, tx: Tx[]): Promise<TxResult> {
|
||||
tx (ctx: MeasureContext, tx: Tx[]): Promise<TxResult> {
|
||||
if (this.head !== undefined) {
|
||||
return await this.head.tx(ctx, tx)
|
||||
return this.head.tx(ctx, tx)
|
||||
}
|
||||
return {}
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
handleBroadcast (ctx: MeasureContext<SessionData>): Promise<void> {
|
||||
|
@ -129,7 +129,6 @@ export function initStatisticsContext (
|
||||
|
||||
const statData = JSON.stringify(data)
|
||||
|
||||
metricsContext.info('send stats:', { size: statData.length })
|
||||
void fetch(
|
||||
concatLink(statsUrl, '/api/v1/statistics') + `/?token=${encodeURIComponent(token)}&name=${serviceId}`,
|
||||
{
|
||||
|
@ -41,7 +41,7 @@ export class BackupClientOps {
|
||||
idIndex = 0
|
||||
chunkInfo = new Map<number, ChunkInfo>()
|
||||
|
||||
async loadChunk (
|
||||
loadChunk (
|
||||
ctx: MeasureContext,
|
||||
domain: Domain,
|
||||
idx?: number,
|
||||
@ -51,7 +51,7 @@ export class BackupClientOps {
|
||||
docs: DocInfo[]
|
||||
finished: boolean
|
||||
}> {
|
||||
return await ctx.with('load-chunk', { domain }, async (ctx) => {
|
||||
return ctx.with('load-chunk', {}, async (ctx) => {
|
||||
idx = idx ?? this.idIndex++
|
||||
let chunk: ChunkInfo | undefined = this.chunkInfo.get(idx)
|
||||
if (chunk !== undefined) {
|
||||
@ -90,8 +90,8 @@ export class BackupClientOps {
|
||||
})
|
||||
}
|
||||
|
||||
async closeChunk (ctx: MeasureContext, idx: number): Promise<void> {
|
||||
await ctx.with('close-chunk', {}, async () => {
|
||||
closeChunk (ctx: MeasureContext, idx: number): Promise<void> {
|
||||
return ctx.with('close-chunk', {}, async () => {
|
||||
const chunk = this.chunkInfo.get(idx)
|
||||
this.chunkInfo.delete(idx)
|
||||
if (chunk != null) {
|
||||
|
@ -17,6 +17,7 @@
|
||||
import core, {
|
||||
TxFactory,
|
||||
TxProcessor,
|
||||
generateId,
|
||||
groupByArray,
|
||||
matchQuery,
|
||||
type Class,
|
||||
@ -41,8 +42,6 @@ import serverCore from './plugin'
|
||||
interface TriggerRecord {
|
||||
query?: DocumentQuery<Tx>
|
||||
trigger: { op: TriggerFunc | Promise<TriggerFunc>, resource: Resource<TriggerFunc>, isAsync: boolean }
|
||||
|
||||
arrays: boolean
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
@ -67,12 +66,12 @@ export class Triggers {
|
||||
const isAsync = t.isAsync === true
|
||||
this.triggers.push({
|
||||
query: match,
|
||||
trigger: { op: func, resource: trigger, isAsync },
|
||||
arrays: t.arrays === true
|
||||
trigger: { op: func, resource: trigger, isAsync }
|
||||
})
|
||||
}
|
||||
|
||||
async tx (txes: Tx[]): Promise<void> {
|
||||
tresolve = Promise.resolve()
|
||||
tx (txes: Tx[]): Promise<void> {
|
||||
for (const tx of txes) {
|
||||
if (tx._class === core.class.TxCreateDoc) {
|
||||
const createTx = tx as TxCreateDoc<Doc>
|
||||
@ -82,13 +81,14 @@ export class Triggers {
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.tresolve
|
||||
}
|
||||
|
||||
async applyTrigger (
|
||||
ctx: MeasureContext,
|
||||
ctrl: Omit<TriggerControl, 'txFactory'>,
|
||||
matches: Tx[],
|
||||
{ trigger, arrays }: TriggerRecord
|
||||
{ trigger }: TriggerRecord
|
||||
): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
const apply: Tx[] = []
|
||||
@ -98,31 +98,28 @@ export class Triggers {
|
||||
...ctrl,
|
||||
ctx,
|
||||
txFactory: null as any, // Will be set later
|
||||
apply: async (ctx, tx, needResult) => {
|
||||
apply: (ctx, tx, needResult) => {
|
||||
if (needResult !== true) {
|
||||
apply.push(...tx)
|
||||
}
|
||||
ctrl.txes.push(...tx) // We need to put them so other triggers could check if similar operation is already performed.
|
||||
return await ctrl.apply(ctx, tx, needResult)
|
||||
return ctrl.apply(ctx, tx, needResult)
|
||||
}
|
||||
}
|
||||
if (trigger.op instanceof Promise) {
|
||||
trigger.op = await trigger.op
|
||||
}
|
||||
for (const [k, v] of group.entries()) {
|
||||
const m = arrays ? [v] : v
|
||||
tctrl.txFactory = new TxFactory(k, true)
|
||||
for (const tx of m) {
|
||||
try {
|
||||
const tresult = await trigger.op(tx, tctrl)
|
||||
const tresult = await trigger.op(v, tctrl)
|
||||
result.push(...tresult)
|
||||
ctrl.txes.push(...tresult)
|
||||
} catch (err: any) {
|
||||
ctx.error('failed to process trigger', { trigger: trigger.resource, tx, err })
|
||||
ctx.error('failed to process trigger', { trigger: trigger.resource, err })
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.concat(apply)
|
||||
}
|
||||
|
||||
@ -133,7 +130,7 @@ export class Triggers {
|
||||
mode: 'sync' | 'async'
|
||||
): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
for (const { query, trigger, arrays } of this.triggers) {
|
||||
for (const { query, trigger } of this.triggers) {
|
||||
if ((trigger.isAsync ? 'async' : 'sync') !== mode) {
|
||||
continue
|
||||
}
|
||||
@ -148,10 +145,16 @@ export class Triggers {
|
||||
trigger.resource,
|
||||
{},
|
||||
async (ctx) => {
|
||||
const tresult = await this.applyTrigger(ctx, ctrl, matches, { trigger, arrays })
|
||||
if (mode === 'async') {
|
||||
ctx.id = generateId()
|
||||
}
|
||||
const tresult = await this.applyTrigger(ctx, ctrl, matches, { trigger })
|
||||
result.push(...tresult)
|
||||
if (ctx.onEnd !== undefined && mode === 'async') {
|
||||
await ctx.onEnd(ctx)
|
||||
}
|
||||
},
|
||||
{ count: matches.length, arrays }
|
||||
{ count: matches.length }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -260,7 +260,7 @@ export interface TriggerControl {
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type TriggerFunc = (tx: Tx | Tx[], ctrl: TriggerControl) => Promise<Tx[]>
|
||||
export type TriggerFunc = (tx: Tx[], ctrl: TriggerControl) => Promise<Tx[]>
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -273,9 +273,6 @@ export interface Trigger extends Doc {
|
||||
|
||||
// We should match transaction
|
||||
txMatch?: DocumentQuery<Tx>
|
||||
|
||||
// If set trigger will handle Tx[] instead of Tx
|
||||
arrays?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@ -572,6 +569,11 @@ export function disableLogging (): void {
|
||||
LOGGING_ENABLED = false
|
||||
}
|
||||
|
||||
interface TickHandler {
|
||||
ticks: number
|
||||
operation: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -581,6 +583,9 @@ export interface Workspace {
|
||||
token: string // Account workspace update token.
|
||||
pipeline: Promise<Pipeline>
|
||||
tickHash: number
|
||||
|
||||
tickHandlers: Map<string, TickHandler>
|
||||
|
||||
sessions: Map<string, { session: Session, socket: ConnectionSocket, tickHash: number }>
|
||||
upgrade: boolean
|
||||
|
||||
|
@ -161,13 +161,13 @@ export class Client {
|
||||
|
||||
try {
|
||||
if (size === undefined || size < 64 * 1024 * 1024) {
|
||||
await ctx.with('direct-upload', {}, async (ctx) => {
|
||||
await this.uploadWithFormData(ctx, workspace, objectName, stream, metadata)
|
||||
})
|
||||
await ctx.with('direct-upload', {}, (ctx) =>
|
||||
this.uploadWithFormData(ctx, workspace, objectName, stream, metadata)
|
||||
)
|
||||
} else {
|
||||
await ctx.with('signed-url-upload', {}, async (ctx) => {
|
||||
await this.uploadWithSignedURL(ctx, workspace, objectName, stream, metadata)
|
||||
})
|
||||
await ctx.with('signed-url-upload', {}, (ctx) =>
|
||||
this.uploadWithSignedURL(ctx, workspace, objectName, stream, metadata)
|
||||
)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('failed to put object', { workspace, objectName, err })
|
||||
|
@ -126,11 +126,9 @@ export class DatalakeService implements StorageAdapter {
|
||||
size
|
||||
}
|
||||
|
||||
await ctx.with('put', {}, async (ctx) => {
|
||||
await withRetry(ctx, 5, async () => {
|
||||
await this.client.putObject(ctx, workspaceId, objectName, stream, metadata, size)
|
||||
})
|
||||
})
|
||||
await ctx.with('put', {}, (ctx) =>
|
||||
withRetry(ctx, 5, () => this.client.putObject(ctx, workspaceId, objectName, stream, metadata, size))
|
||||
)
|
||||
|
||||
return {
|
||||
etag: '',
|
||||
|
@ -62,7 +62,7 @@ class ElasticAdapter implements FullTextAdapter {
|
||||
async initMapping (ctx: MeasureContext): Promise<boolean> {
|
||||
const indexName = this.indexName
|
||||
try {
|
||||
const existingVersions = await ctx.with('get-indexes', {}, () =>
|
||||
const existingVersions = await ctx.withSync('get-indexes', {}, () =>
|
||||
this.client.indices.get({
|
||||
index: [`${this.indexBaseName}_*`]
|
||||
})
|
||||
|
@ -49,7 +49,7 @@ async function storageUpload (
|
||||
const resp = await ctx.with(
|
||||
'storage upload',
|
||||
{ workspace: workspace.name },
|
||||
async (ctx) => await storageAdapter.put(ctx, workspace, uuid, data, file.mimetype, file.size),
|
||||
(ctx) => storageAdapter.put(ctx, workspace, uuid, data, file.mimetype, file.size),
|
||||
{ file: file.name, contentType: file.mimetype }
|
||||
)
|
||||
|
||||
@ -104,7 +104,7 @@ async function getFileRange (
|
||||
const dataStream = await ctx.with(
|
||||
'partial',
|
||||
{},
|
||||
async (ctx) => await client.partial(ctx, workspace, stat._id, start, end - start + 1),
|
||||
(ctx) => client.partial(ctx, workspace, stat._id, start, end - start + 1),
|
||||
{}
|
||||
)
|
||||
res.writeHead(206, {
|
||||
@ -199,7 +199,7 @@ async function getFile (
|
||||
{ contentType: stat.contentType },
|
||||
async (ctx) => {
|
||||
try {
|
||||
const dataStream = await ctx.with('readable', {}, async (ctx) => await client.get(ctx, workspace, stat._id))
|
||||
const dataStream = await ctx.with('readable', {}, (ctx) => client.get(ctx, workspace, stat._id))
|
||||
res.writeHead(200, {
|
||||
'Content-Type': stat.contentType,
|
||||
Etag: stat.etag,
|
||||
@ -400,10 +400,8 @@ export function start (
|
||||
return
|
||||
}
|
||||
|
||||
let blobInfo = await ctx.with(
|
||||
'stat',
|
||||
{ workspace: payload.workspace.name },
|
||||
async (ctx) => await config.storageAdapter.stat(ctx, payload.workspace, uuid)
|
||||
let blobInfo = await ctx.with('stat', { workspace: payload.workspace.name }, (ctx) =>
|
||||
config.storageAdapter.stat(ctx, payload.workspace, uuid)
|
||||
)
|
||||
|
||||
if (blobInfo === undefined) {
|
||||
@ -431,11 +429,8 @@ export function start (
|
||||
const size = req.query.size !== undefined ? parseInt(req.query.size as string) : undefined
|
||||
const accept = req.headers.accept
|
||||
if (accept !== undefined && isImage && blobInfo.contentType !== 'image/gif' && size !== undefined) {
|
||||
blobInfo = await ctx.with(
|
||||
'resize',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await getGeneratePreview(ctx, blobInfo as PlatformBlob, size, uuid, config, payload, accept, () =>
|
||||
blobInfo = await ctx.with('resize', {}, (ctx) =>
|
||||
getGeneratePreview(ctx, blobInfo as PlatformBlob, size, uuid, config, payload, accept, () =>
|
||||
join(tempFileDir, `${++temoFileIndex}`)
|
||||
)
|
||||
)
|
||||
@ -443,16 +438,14 @@ export function start (
|
||||
|
||||
const range = req.headers.range
|
||||
if (range !== undefined) {
|
||||
await ctx.with('file-range', { workspace: payload.workspace.name }, async (ctx) => {
|
||||
await getFileRange(ctx, blobInfo as PlatformBlob, range, config.storageAdapter, payload.workspace, res)
|
||||
})
|
||||
await ctx.with('file-range', { workspace: payload.workspace.name }, (ctx) =>
|
||||
getFileRange(ctx, blobInfo as PlatformBlob, range, config.storageAdapter, payload.workspace, res)
|
||||
)
|
||||
} else {
|
||||
await ctx.with(
|
||||
'file',
|
||||
{ workspace: payload.workspace.name },
|
||||
async (ctx) => {
|
||||
await getFile(ctx, blobInfo as PlatformBlob, config.storageAdapter, payload.workspace, req, res)
|
||||
},
|
||||
(ctx) => getFile(ctx, blobInfo as PlatformBlob, config.storageAdapter, payload.workspace, req, res),
|
||||
{ uuid }
|
||||
)
|
||||
}
|
||||
@ -867,7 +860,7 @@ async function getGeneratePreview (
|
||||
const outFile = tempFile()
|
||||
files.push(outFile)
|
||||
|
||||
const dataBuff = await ctx.with('resize', { contentType }, async () => await pipeline.toFile(outFile))
|
||||
const dataBuff = await ctx.with('resize', { contentType }, () => pipeline.toFile(outFile))
|
||||
pipeline.destroy()
|
||||
|
||||
// Add support of avif as well.
|
||||
|
@ -309,7 +309,7 @@ export class FullTextIndexPipeline implements FullTextPipeline {
|
||||
const { classUpdate: _classes, processed } = await this.metrics.with(
|
||||
'processIndex',
|
||||
{ workspace: this.workspace.name },
|
||||
async (ctx) => await this.processIndex(ctx, indexing)
|
||||
(ctx) => this.processIndex(ctx, indexing)
|
||||
)
|
||||
|
||||
// Also update doc index state queries.
|
||||
@ -445,8 +445,8 @@ export class FullTextIndexPipeline implements FullTextPipeline {
|
||||
let result: FindResult<DocIndexState> | WithLookup<DocIndexState>[] = await ctx.with(
|
||||
'get-indexable',
|
||||
{},
|
||||
async (ctx) => {
|
||||
return await this.storage.findAll(ctx, core.class.DocIndexState, q, {
|
||||
(ctx) => {
|
||||
return this.storage.findAll(ctx, core.class.DocIndexState, q, {
|
||||
limit: globalIndexer.processingSize,
|
||||
skipClass: true,
|
||||
skipSpace: true,
|
||||
@ -589,9 +589,7 @@ export class FullTextIndexPipeline implements FullTextPipeline {
|
||||
await pushQueue.exec(async () => {
|
||||
try {
|
||||
try {
|
||||
await ctx.with('push-elastic', {}, async () => {
|
||||
await this.fulltextAdapter.updateMany(ctx, this.workspace, docs)
|
||||
})
|
||||
await ctx.with('push-elastic', {}, () => this.fulltextAdapter.updateMany(ctx, this.workspace, docs))
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
// Try to push one by one
|
||||
|
@ -54,9 +54,9 @@ export class ContextNameMiddleware extends BaseMiddleware implements Middleware
|
||||
const result = await ctx.with(
|
||||
measureName !== undefined ? `📶 ${measureName}` : 'client-tx',
|
||||
{ _class: tx?._class },
|
||||
async (ctx) => {
|
||||
(ctx) => {
|
||||
;({ opLogMetrics, op } = registerOperationLog(ctx))
|
||||
return await this.provideTx(ctx as MeasureContext<SessionData>, txes)
|
||||
return this.provideTx(ctx as MeasureContext<SessionData>, txes)
|
||||
}
|
||||
)
|
||||
updateOperationLog(opLogMetrics, op)
|
||||
|
@ -16,6 +16,7 @@
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import core, {
|
||||
docKey,
|
||||
DOMAIN_DOC_INDEX_STATE,
|
||||
isFullTextAttribute,
|
||||
isIndexedAttribute,
|
||||
toFindResult,
|
||||
@ -80,8 +81,11 @@ export class FullTextMiddleware extends BaseMiddleware implements Middleware {
|
||||
}
|
||||
|
||||
async handleBroadcast (ctx: MeasureContext<SessionData>): Promise<void> {
|
||||
if (ctx.contextData.needWarmupFulltext === true) {
|
||||
void this.sendWarmup()
|
||||
if (ctx.contextData.fulltextUpdates !== undefined && ctx.contextData.fulltextUpdates.size > 0) {
|
||||
const toUpdate = Array.from(ctx.contextData.fulltextUpdates.values())
|
||||
ctx.contextData.fulltextUpdates.clear()
|
||||
await this.context.lowLevelStorage?.upload(ctx, DOMAIN_DOC_INDEX_STATE, toUpdate)
|
||||
this.sendWarmup()
|
||||
}
|
||||
await super.handleBroadcast(ctx)
|
||||
}
|
||||
@ -95,15 +99,15 @@ export class FullTextMiddleware extends BaseMiddleware implements Middleware {
|
||||
)
|
||||
|
||||
// Send one warumup so reindex will
|
||||
void this.sendWarmup()
|
||||
this.sendWarmup()
|
||||
}
|
||||
|
||||
async sendWarmup (): Promise<void> {
|
||||
sendWarmup (): void {
|
||||
if (!this.warmupTriggered) {
|
||||
this.warmupTriggered = true
|
||||
setTimeout(() => {
|
||||
this.warmupTriggered = false
|
||||
void this._sendWarmup()
|
||||
void this._sendWarmup().catch(() => {})
|
||||
}, 25)
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ export class LiveQueryMiddleware extends BaseMiddleware implements Middleware {
|
||||
getModel (): ModelDb {
|
||||
return modelDb
|
||||
},
|
||||
close: async () => {},
|
||||
close: () => Promise.resolve(),
|
||||
findAll: async (_class, query, options) => {
|
||||
const _ctx: MeasureContext = (options as ServerFindOptions<Doc>)?.ctx ?? metrics
|
||||
delete (options as ServerFindOptions<Doc>)?.ctx
|
||||
@ -73,8 +73,8 @@ export class LiveQueryMiddleware extends BaseMiddleware implements Middleware {
|
||||
results.total
|
||||
)[0]
|
||||
},
|
||||
tx: async (tx) => {
|
||||
return {}
|
||||
tx: (tx) => {
|
||||
return Promise.resolve({})
|
||||
},
|
||||
searchFulltext: async (query: SearchQuery, options: SearchOptions) => {
|
||||
// Cast client doesn't support fulltext search
|
||||
|
@ -55,8 +55,8 @@ export class LowLevelMiddleware extends BaseMiddleware implements Middleware {
|
||||
return adapterManager.getAdapter(domain, true).upload(ctx, domain, docs)
|
||||
},
|
||||
|
||||
async clean (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<void> {
|
||||
await adapterManager.getAdapter(domain, true).clean(ctx, domain, docs)
|
||||
clean (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<void> {
|
||||
return adapterManager.getAdapter(domain, true).clean(ctx, domain, docs)
|
||||
},
|
||||
groupBy<T, P extends Doc>(
|
||||
ctx: MeasureContext,
|
||||
|
@ -122,7 +122,7 @@ export class PrivateMiddleware extends BaseMiddleware implements Middleware {
|
||||
if (options?.lookup !== undefined) {
|
||||
for (const object of findResult) {
|
||||
if (object.$lookup !== undefined) {
|
||||
await this.filterLookup(ctx, object.$lookup)
|
||||
this.filterLookup(ctx, object.$lookup)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,7 +136,7 @@ export class PrivateMiddleware extends BaseMiddleware implements Middleware {
|
||||
return doc.createdBy === account || account === core.account.System
|
||||
}
|
||||
|
||||
async filterLookup<T extends Doc>(ctx: MeasureContext, lookup: LookupData<T>): Promise<void> {
|
||||
filterLookup<T extends Doc>(ctx: MeasureContext, lookup: LookupData<T>): void {
|
||||
for (const key in lookup) {
|
||||
const val = lookup[key]
|
||||
if (Array.isArray(val)) {
|
||||
|
@ -51,7 +51,6 @@ import type {
|
||||
} from '@hcengineering/server-core'
|
||||
import serverCore, { BaseMiddleware, SessionDataImpl, SessionFindAll, Triggers } from '@hcengineering/server-core'
|
||||
import { filterBroadcastOnly } from './utils'
|
||||
import { QueryJoiner } from './queryJoin'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -107,7 +106,7 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
|
||||
}
|
||||
|
||||
private async processDerived (ctx: MeasureContext<SessionData>, txes: Tx[]): Promise<void> {
|
||||
const _findAll: SessionFindAll = async (ctx, _class, query, options) => {
|
||||
const findAll: SessionFindAll = async (ctx, _class, query, options) => {
|
||||
const _ctx: MeasureContext = (options as ServerFindOptions<Doc>)?.ctx ?? ctx
|
||||
delete (options as ServerFindOptions<Doc>)?.ctx
|
||||
if (_ctx.contextData !== undefined) {
|
||||
@ -123,11 +122,6 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
|
||||
results.total
|
||||
)
|
||||
}
|
||||
const joiner = new QueryJoiner(_findAll)
|
||||
|
||||
const findAll: SessionFindAll = async (ctx, _class, query, options) => {
|
||||
return await joiner.findAll(ctx, _class, query, options)
|
||||
}
|
||||
|
||||
const removed = await ctx.with('process-remove', {}, (ctx) => this.processRemove(ctx, txes, findAll))
|
||||
const collections = await ctx.with('process-collection', {}, (ctx) => this.processCollection(ctx, txes, findAll))
|
||||
@ -267,14 +261,14 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
|
||||
}
|
||||
}
|
||||
|
||||
private async getCollectionUpdateTx<D extends Doc>(
|
||||
private getCollectionUpdateTx<D extends Doc>(
|
||||
_id: Ref<D>,
|
||||
_class: Ref<Class<D>>,
|
||||
modifiedBy: Ref<Account>,
|
||||
modifiedOn: number,
|
||||
attachedTo: Pick<Doc, '_class' | 'space'>,
|
||||
update: DocumentUpdate<D>
|
||||
): Promise<Tx> {
|
||||
): Tx {
|
||||
const txFactory = new TxFactory(modifiedBy, true)
|
||||
const baseClass = this.context.hierarchy.getBaseClass(_class)
|
||||
if (baseClass !== _class) {
|
||||
@ -363,7 +357,7 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
|
||||
const attr = this.context.hierarchy.findAttribute(oldAttachedTo._class, colTx.collection)
|
||||
|
||||
if (attr !== undefined) {
|
||||
oldTx = await this.getCollectionUpdateTx(_id, _class, tx.modifiedBy, colTx.modifiedOn, oldAttachedTo, {
|
||||
oldTx = this.getCollectionUpdateTx(_id, _class, tx.modifiedBy, colTx.modifiedOn, oldAttachedTo, {
|
||||
$inc: { [colTx.collection]: -1 }
|
||||
})
|
||||
}
|
||||
@ -375,7 +369,7 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
|
||||
let newTx: Tx | null = null
|
||||
const newAttr = this.context.hierarchy.findAttribute(newAttachedToClass, newAttachedToCollection)
|
||||
if (newAttachedTo !== undefined && newAttr !== undefined) {
|
||||
newTx = await this.getCollectionUpdateTx(
|
||||
newTx = this.getCollectionUpdateTx(
|
||||
newAttachedTo._id,
|
||||
newAttachedTo._class,
|
||||
tx.modifiedBy,
|
||||
@ -419,7 +413,7 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
|
||||
const attachedTo = (await findAll(ctx, _class, { _id }, { limit: 1 }))[0]
|
||||
if (attachedTo !== undefined) {
|
||||
result.push(
|
||||
await this.getCollectionUpdateTx(
|
||||
this.getCollectionUpdateTx(
|
||||
_id,
|
||||
_class,
|
||||
tx.modifiedBy,
|
||||
@ -496,8 +490,8 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
|
||||
serverCore.mixin.ObjectDDParticipant
|
||||
)
|
||||
const collector = await getResource(removeParticipand.collectDocs)
|
||||
const docs = await collector(object, this.context.hierarchy, async (_class, query, options) => {
|
||||
return await findAll(ctx, _class, query, options)
|
||||
const docs = await collector(object, this.context.hierarchy, (_class, query, options) => {
|
||||
return findAll(ctx, _class, query, options)
|
||||
})
|
||||
for (const d of docs) {
|
||||
result.push(...this.deleteObject(ctx, d, ctx.contextData.removedMap))
|
||||
|
@ -62,7 +62,7 @@ export class TxMiddleware extends BaseMiddleware implements Middleware {
|
||||
txPromise = ctx.with(
|
||||
'domain-tx',
|
||||
{},
|
||||
async (ctx) => await this.adapterManager.getAdapter(DOMAIN_TX, true).tx(ctx, ...txToStore),
|
||||
(ctx) => this.adapterManager.getAdapter(DOMAIN_TX, true).tx(ctx, ...txToStore),
|
||||
{
|
||||
count: txToStore.length,
|
||||
txes: Array.from(new Set(txToStore.map((it) => it._class)))
|
||||
@ -70,7 +70,7 @@ export class TxMiddleware extends BaseMiddleware implements Middleware {
|
||||
)
|
||||
}
|
||||
if (txPromise !== undefined) {
|
||||
return (await Promise.all([txPromise, this.provideTx(ctx, txes)]))[1]
|
||||
await txPromise
|
||||
}
|
||||
return await this.provideTx(ctx, txes)
|
||||
}
|
||||
|
@ -134,7 +134,6 @@ describe('mongo operations', () => {
|
||||
})
|
||||
|
||||
it('check add', async () => {
|
||||
jest.setTimeout(500000)
|
||||
const times: number[] = []
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const t = Date.now()
|
||||
@ -153,7 +152,6 @@ describe('mongo operations', () => {
|
||||
})
|
||||
|
||||
it('check find by criteria', async () => {
|
||||
jest.setTimeout(20000)
|
||||
for (let i = 0; i < 50; i++) {
|
||||
await operations.createDoc(taskPlugin.class.Task, '' as Ref<Space>, {
|
||||
name: `my-task-${i}`,
|
||||
|
@ -201,9 +201,7 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
}
|
||||
return docs
|
||||
},
|
||||
close: async () => {
|
||||
await cursor.close()
|
||||
}
|
||||
close: () => cursor.close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,8 +260,8 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async rawDeleteMany<T extends Doc>(domain: Domain, query: DocumentQuery<T>): Promise<void> {
|
||||
await this.db.collection(domain).deleteMany(this.translateRawQuery(query))
|
||||
rawDeleteMany<T extends Doc>(domain: Domain, query: DocumentQuery<T>): Promise<void> {
|
||||
return this.db.collection(domain).deleteMany(this.translateRawQuery(query)).then()
|
||||
}
|
||||
|
||||
abstract init (): Promise<void>
|
||||
@ -280,8 +278,9 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
return []
|
||||
}
|
||||
|
||||
async close (): Promise<void> {
|
||||
close (): Promise<void> {
|
||||
this.client.close()
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
private translateQuery<T extends Doc>(
|
||||
@ -460,13 +459,13 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
return result
|
||||
}
|
||||
|
||||
private async fillLookup<T extends Doc>(
|
||||
private fillLookup<T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
object: any,
|
||||
key: string,
|
||||
fullKey: string,
|
||||
targetObject: any
|
||||
): Promise<void> {
|
||||
): void {
|
||||
if (targetObject.$lookup === undefined) {
|
||||
targetObject.$lookup = {}
|
||||
}
|
||||
@ -481,11 +480,11 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
targetObject.$lookup[key] = (await this.modelDb.findAll(_class, { _id: targetObject[key] }))[0]
|
||||
targetObject.$lookup[key] = this.modelDb.findAllSync(_class, { _id: targetObject[key] })[0]
|
||||
}
|
||||
}
|
||||
|
||||
private async fillLookupValue<T extends Doc>(
|
||||
private fillLookupValue<T extends Doc>(
|
||||
ctx: MeasureContext,
|
||||
clazz: Ref<Class<T>>,
|
||||
lookup: Lookup<T> | undefined,
|
||||
@ -496,11 +495,11 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
field: string
|
||||
domain: Domain
|
||||
}
|
||||
): Promise<void> {
|
||||
): void {
|
||||
if (lookup === undefined && domainLookup === undefined) return
|
||||
for (const key in lookup) {
|
||||
if (key === '_id') {
|
||||
await this.fillReverseLookup(clazz, lookup, object, parent, parentObject)
|
||||
this.fillReverseLookup(clazz, lookup, object, parent, parentObject)
|
||||
continue
|
||||
}
|
||||
const value = (lookup as any)[key]
|
||||
@ -509,10 +508,10 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
const targetObject = parentObject ?? object
|
||||
if (Array.isArray(value)) {
|
||||
const [_class, nested] = value
|
||||
await this.fillLookup(_class, object, key, fullKey, targetObject)
|
||||
await this.fillLookupValue(ctx, _class, nested, object, fullKey, targetObject.$lookup[key])
|
||||
this.fillLookup(_class, object, key, fullKey, targetObject)
|
||||
this.fillLookupValue(ctx, _class, nested, object, fullKey, targetObject.$lookup[key])
|
||||
} else {
|
||||
await this.fillLookup(value, object, key, fullKey, targetObject)
|
||||
this.fillLookup(value, object, key, fullKey, targetObject)
|
||||
}
|
||||
}
|
||||
if (domainLookup !== undefined) {
|
||||
@ -525,13 +524,13 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
private async fillReverseLookup<T extends Doc>(
|
||||
private fillReverseLookup<T extends Doc>(
|
||||
clazz: Ref<Class<T>>,
|
||||
lookup: ReverseLookups,
|
||||
object: any,
|
||||
parent?: string,
|
||||
parentObject?: any
|
||||
): Promise<void> {
|
||||
): void {
|
||||
const targetObject = parentObject ?? object
|
||||
if (targetObject.$lookup === undefined) {
|
||||
targetObject.$lookup = {}
|
||||
@ -554,17 +553,17 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
const arr = object[fullKey]
|
||||
targetObject.$lookup[key] = arr
|
||||
} else {
|
||||
const arr = await this.modelDb.findAll(_class, { [attr]: targetObject._id })
|
||||
const arr = this.modelDb.findAllSync(_class, { [attr]: targetObject._id })
|
||||
targetObject.$lookup[key] = arr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async fillSortPipeline<T extends Doc>(
|
||||
private fillSortPipeline<T extends Doc>(
|
||||
clazz: Ref<Class<T>>,
|
||||
options: FindOptions<T> | undefined,
|
||||
pipeline: any[]
|
||||
): Promise<void> {
|
||||
): void {
|
||||
if (options?.sort !== undefined) {
|
||||
const sort = {} as any
|
||||
for (const _key in options.sort) {
|
||||
@ -630,7 +629,7 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
}
|
||||
}
|
||||
const totalPipeline: any[] = [...pipeline]
|
||||
await this.fillSortPipeline(clazz, options, pipeline)
|
||||
this.fillSortPipeline(clazz, options, pipeline)
|
||||
if (options?.limit !== undefined || typeof query._id === 'string') {
|
||||
pipeline.push({ $limit: options?.limit ?? 1 })
|
||||
}
|
||||
@ -652,14 +651,11 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
let result: WithLookup<T>[] = []
|
||||
let total = options?.total === true ? 0 : -1
|
||||
try {
|
||||
await ctx.with(
|
||||
result = await ctx.with(
|
||||
'aggregate',
|
||||
{ clazz },
|
||||
async (ctx) => {
|
||||
result = await toArray(cursor)
|
||||
},
|
||||
(ctx) => toArray(cursor),
|
||||
() => ({
|
||||
size: result.length,
|
||||
domain,
|
||||
pipeline,
|
||||
clazz
|
||||
@ -670,8 +666,8 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
throw e
|
||||
}
|
||||
for (const row of result) {
|
||||
await ctx.with('fill-lookup', {}, async (ctx) => {
|
||||
await this.fillLookupValue(ctx, clazz, options?.lookup, row, undefined, undefined, options.domainLookup)
|
||||
ctx.withSync('fill-lookup', {}, (ctx) => {
|
||||
this.fillLookupValue(ctx, clazz, options?.lookup, row, undefined, undefined, options.domainLookup)
|
||||
})
|
||||
if (row.$lookup !== undefined) {
|
||||
for (const [, v] of Object.entries(row.$lookup)) {
|
||||
@ -688,7 +684,7 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
const arr = await ctx.with(
|
||||
'aggregate-total',
|
||||
{},
|
||||
async (ctx) => await toArray(totalCursor),
|
||||
(ctx) => toArray(totalCursor),
|
||||
() => ({
|
||||
domain,
|
||||
pipeline,
|
||||
@ -800,13 +796,13 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
}
|
||||
|
||||
@withContext('groupBy')
|
||||
async groupBy<T, D extends Doc = Doc>(
|
||||
groupBy<T, D extends Doc = Doc>(
|
||||
ctx: MeasureContext,
|
||||
domain: Domain,
|
||||
field: string,
|
||||
query?: DocumentQuery<D>
|
||||
): Promise<Map<T, number>> {
|
||||
const result = await ctx.with('groupBy', { domain }, async (ctx) => {
|
||||
return ctx.with('groupBy', { domain }, async (ctx) => {
|
||||
const coll = this.collection(domain)
|
||||
const grResult = await coll
|
||||
.aggregate([
|
||||
@ -821,10 +817,9 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
.toArray()
|
||||
return new Map(grResult.map((it) => [it._id as unknown as T, it.count]))
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
async findAll<T extends Doc>(
|
||||
findAll<T extends Doc>(
|
||||
ctx: MeasureContext,
|
||||
_class: Ref<Class<T>>,
|
||||
query: DocumentQuery<T>,
|
||||
@ -833,7 +828,7 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
const stTime = Date.now()
|
||||
const mongoQuery = this.translateQuery(_class, query, options)
|
||||
const fQuery = { ...mongoQuery.base, ...mongoQuery.lookup }
|
||||
return await addOperation(ctx, 'find-all', {}, async () => {
|
||||
return addOperation(ctx, 'find-all', {}, async () => {
|
||||
const st = Date.now()
|
||||
let result: FindResult<T>
|
||||
const domain = options?.domain ?? this.hierarchy.getDomain(_class)
|
||||
@ -911,15 +906,11 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
}
|
||||
// Error in case of timeout
|
||||
try {
|
||||
let res: T[] = []
|
||||
await ctx.with(
|
||||
const res: T[] = await ctx.with(
|
||||
'find-all',
|
||||
{},
|
||||
async (ctx) => {
|
||||
res = await toArray(cursor)
|
||||
},
|
||||
(ctx) => toArray(cursor),
|
||||
() => ({
|
||||
size: res.length,
|
||||
queueTime: stTime - st,
|
||||
mongoQuery,
|
||||
options,
|
||||
@ -1031,11 +1022,8 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
const flush = async (flush = false): Promise<void> => {
|
||||
if (bulkUpdate.size > 1000 || flush) {
|
||||
if (bulkUpdate.size > 0) {
|
||||
await ctx.with(
|
||||
'bulk-write-find',
|
||||
{},
|
||||
async () =>
|
||||
await coll.bulkWrite(
|
||||
await ctx.with('bulk-write-find', {}, () =>
|
||||
coll.bulkWrite(
|
||||
Array.from(bulkUpdate.entries()).map((it) => ({
|
||||
updateOne: {
|
||||
filter: { _id: it[0], '%hash%': null },
|
||||
@ -1064,12 +1052,12 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
}
|
||||
)
|
||||
}
|
||||
let d = await ctx.with('next', { mode }, async () => await iterator.next())
|
||||
let d = await ctx.with('next', { mode }, () => iterator.next())
|
||||
if (d == null && mode === 'hashed' && recheck !== true) {
|
||||
mode = 'non-hashed'
|
||||
await iterator.close()
|
||||
iterator = coll.find({ '%hash%': { $in: ['', null] } })
|
||||
d = await ctx.with('next', { mode }, async () => await iterator.next())
|
||||
d = await ctx.with('next', { mode }, () => iterator.next())
|
||||
}
|
||||
const result: DocInfo[] = []
|
||||
if (d != null) {
|
||||
@ -1078,18 +1066,12 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
if (iterator.bufferedCount() > 0) {
|
||||
result.push(...iterator.readBufferedDocuments().map((it) => this.toDocInfo(it, bulkUpdate, recheck)))
|
||||
}
|
||||
await ctx.with('flush', {}, async () => {
|
||||
await flush()
|
||||
})
|
||||
await ctx.with('flush', {}, () => flush())
|
||||
return result
|
||||
},
|
||||
close: async () => {
|
||||
await ctx.with('flush', {}, async () => {
|
||||
await flush(true)
|
||||
})
|
||||
await ctx.with('close', {}, async () => {
|
||||
await iterator.close()
|
||||
})
|
||||
await ctx.with('flush', {}, () => flush(true))
|
||||
await ctx.with('close', {}, () => iterator.close())
|
||||
ctx.end()
|
||||
}
|
||||
}
|
||||
@ -1131,8 +1113,8 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async load (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<Doc[]> {
|
||||
return await ctx.with('load', { domain }, async () => {
|
||||
load (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<Doc[]> {
|
||||
return ctx.with('load', { domain }, async () => {
|
||||
if (docs.length === 0) {
|
||||
return []
|
||||
}
|
||||
@ -1142,16 +1124,16 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
})
|
||||
}
|
||||
|
||||
async upload (ctx: MeasureContext, domain: Domain, docs: Doc[]): Promise<void> {
|
||||
await ctx.with('upload', { domain }, async () => {
|
||||
upload (ctx: MeasureContext, domain: Domain, docs: Doc[]): Promise<void> {
|
||||
return ctx.with('upload', { domain }, () => {
|
||||
const coll = this.collection(domain)
|
||||
|
||||
await uploadDocuments(ctx, docs, coll)
|
||||
return uploadDocuments(ctx, docs, coll)
|
||||
})
|
||||
}
|
||||
|
||||
async update (ctx: MeasureContext, domain: Domain, operations: Map<Ref<Doc>, DocumentUpdate<Doc>>): Promise<void> {
|
||||
await ctx.with('update', { domain }, async () => {
|
||||
update (ctx: MeasureContext, domain: Domain, operations: Map<Ref<Doc>, DocumentUpdate<Doc>>): Promise<void> {
|
||||
return ctx.with('update', { domain }, async () => {
|
||||
const coll = this.collection(domain)
|
||||
|
||||
// remove old and insert new ones
|
||||
@ -1163,8 +1145,8 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
await ctx.with(
|
||||
'bulk-update',
|
||||
{},
|
||||
async () => {
|
||||
await coll.bulkWrite(
|
||||
() => {
|
||||
return coll.bulkWrite(
|
||||
part.map((it) => {
|
||||
const { $unset, ...set } = it[1] as any
|
||||
if ($unset !== undefined) {
|
||||
@ -1205,8 +1187,8 @@ abstract class MongoAdapterBase implements DbAdapter {
|
||||
})
|
||||
}
|
||||
|
||||
async clean (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<void> {
|
||||
await ctx.with('clean', {}, async () => {
|
||||
clean (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<void> {
|
||||
return ctx.with('clean', {}, async () => {
|
||||
if (docs.length > 0) {
|
||||
await this.db.collection<Doc>(domain).deleteMany({ _id: { $in: docs } })
|
||||
}
|
||||
|
@ -402,13 +402,13 @@ abstract class PostgresAdapterBase implements DbAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async findAll<T extends Doc>(
|
||||
findAll<T extends Doc>(
|
||||
ctx: MeasureContext<SessionData>,
|
||||
_class: Ref<Class<T>>,
|
||||
query: DocumentQuery<T>,
|
||||
options?: ServerFindOptions<T>
|
||||
): Promise<FindResult<T>> {
|
||||
return await ctx.with('findAll', { _class }, async () => {
|
||||
return ctx.with('findAll', { _class }, async () => {
|
||||
try {
|
||||
const domain = translateDomain(options?.domain ?? this.hierarchy.getDomain(_class))
|
||||
const sqlChunks: string[] = []
|
||||
@ -1142,9 +1142,9 @@ abstract class PostgresAdapterBase implements DbAdapter {
|
||||
const flush = async (flush = false): Promise<void> => {
|
||||
if (bulkUpdate.size > 1000 || flush) {
|
||||
if (bulkUpdate.size > 0) {
|
||||
await ctx.with('bulk-write-find', {}, async () => {
|
||||
await ctx.with('bulk-write-find', {}, () => {
|
||||
const updates = new Map(Array.from(bulkUpdate.entries()).map((it) => [it[0], { '%hash%': it[1] }]))
|
||||
await this.update(ctx, domain, updates)
|
||||
return this.update(ctx, domain, updates)
|
||||
})
|
||||
}
|
||||
bulkUpdate.clear()
|
||||
@ -1162,12 +1162,12 @@ abstract class PostgresAdapterBase implements DbAdapter {
|
||||
await init('_id, data', "'%hash%' IS NOT NULL AND '%hash%' <> ''")
|
||||
initialized = true
|
||||
}
|
||||
let docs = await ctx.with('next', { mode }, async () => await next(50))
|
||||
let docs = await ctx.with('next', { mode }, () => next(50))
|
||||
if (docs.length === 0 && mode === 'hashed') {
|
||||
await close(cursorName)
|
||||
mode = 'non_hashed'
|
||||
await init('*', "'%hash%' IS NULL OR '%hash%' = ''")
|
||||
docs = await ctx.with('next', { mode }, async () => await next(50))
|
||||
docs = await ctx.with('next', { mode }, () => next(50))
|
||||
}
|
||||
if (docs.length === 0) {
|
||||
return []
|
||||
@ -1190,9 +1190,7 @@ abstract class PostgresAdapterBase implements DbAdapter {
|
||||
|
||||
bulkUpdate.set(d._id, `${digest}|${size.toString(16)}`)
|
||||
|
||||
await ctx.with('flush', {}, async () => {
|
||||
await flush()
|
||||
})
|
||||
await ctx.with('flush', {}, () => flush())
|
||||
result.push({
|
||||
id: d._id,
|
||||
hash: digest,
|
||||
@ -1209,17 +1207,15 @@ abstract class PostgresAdapterBase implements DbAdapter {
|
||||
return result
|
||||
},
|
||||
close: async () => {
|
||||
await ctx.with('flush', {}, async () => {
|
||||
await flush(true)
|
||||
})
|
||||
await ctx.with('flush', {}, () => flush(true))
|
||||
await close(cursorName)
|
||||
ctx.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async load (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<Doc[]> {
|
||||
return await ctx.with('load', { domain }, async () => {
|
||||
load (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<Doc[]> {
|
||||
return ctx.with('load', { domain }, async () => {
|
||||
if (docs.length === 0) {
|
||||
return []
|
||||
}
|
||||
@ -1230,8 +1226,8 @@ abstract class PostgresAdapterBase implements DbAdapter {
|
||||
})
|
||||
}
|
||||
|
||||
async upload (ctx: MeasureContext, domain: Domain, docs: Doc[]): Promise<void> {
|
||||
await ctx.with('upload', { domain }, async (ctx) => {
|
||||
upload (ctx: MeasureContext, domain: Domain, docs: Doc[]): Promise<void> {
|
||||
return ctx.with('upload', { domain }, async (ctx) => {
|
||||
const arr = docs.concat()
|
||||
const fields = getDocFieldsByDomains(domain)
|
||||
const filedsWithData = [...fields, 'data']
|
||||
@ -1282,15 +1278,15 @@ abstract class PostgresAdapterBase implements DbAdapter {
|
||||
await connection`DELETE FROM ${connection(translateDomain(domain))} WHERE _id = ANY(${docs}) AND "workspaceId" = ${this.workspaceId.name}`
|
||||
}
|
||||
|
||||
async groupBy<T, P extends Doc>(
|
||||
groupBy<T, P extends Doc>(
|
||||
ctx: MeasureContext,
|
||||
domain: Domain,
|
||||
field: string,
|
||||
query?: DocumentQuery<P>
|
||||
): Promise<Map<T, number>> {
|
||||
const connection = (await this.getConnection(ctx)) ?? this.client
|
||||
const key = isDataField(domain, field) ? `data ->> '${field}'` : `"${field}"`
|
||||
const result = await ctx.with('groupBy', { domain }, async (ctx) => {
|
||||
return ctx.with('groupBy', { domain }, async (ctx) => {
|
||||
const connection = (await this.getConnection(ctx)) ?? this.client
|
||||
try {
|
||||
const result = await connection.unsafe(
|
||||
`SELECT DISTINCT ${key} as ${field}, Count(*) AS count FROM ${translateDomain(domain)} WHERE ${this.buildRawQuery(domain, query ?? {})} GROUP BY ${key}`
|
||||
@ -1301,13 +1297,12 @@ abstract class PostgresAdapterBase implements DbAdapter {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
async update (ctx: MeasureContext, domain: Domain, operations: Map<Ref<Doc>, DocumentUpdate<Doc>>): Promise<void> {
|
||||
update (ctx: MeasureContext, domain: Domain, operations: Map<Ref<Doc>, DocumentUpdate<Doc>>): Promise<void> {
|
||||
const ids = Array.from(operations.keys())
|
||||
await this.withConnection(ctx, async (client) => {
|
||||
await this.retryTxn(client, async (client) => {
|
||||
return this.withConnection(ctx, (client) => {
|
||||
return this.retryTxn(client, async (client) => {
|
||||
try {
|
||||
const res =
|
||||
await client`SELECT * FROM ${client(translateDomain(domain))} WHERE _id = ANY(${ids}) AND "workspaceId" = ${this.workspaceId.name} FOR UPDATE`
|
||||
@ -1476,17 +1471,17 @@ class PostgresAdapter extends PostgresAdapterBase {
|
||||
|
||||
protected async txCreateDoc (ctx: MeasureContext, tx: TxCreateDoc<Doc>): Promise<TxResult> {
|
||||
const doc = TxProcessor.createDoc2Doc(tx)
|
||||
return await ctx.with('create-doc', { _class: doc._class }, async (_ctx) => {
|
||||
return await this.insert(_ctx, this.hierarchy.getDomain(doc._class), [doc])
|
||||
return await ctx.with('create-doc', { _class: doc._class }, (_ctx) => {
|
||||
return this.insert(_ctx, this.hierarchy.getDomain(doc._class), [doc])
|
||||
})
|
||||
}
|
||||
|
||||
protected async txUpdateDoc (ctx: MeasureContext, tx: TxUpdateDoc<Doc>): Promise<TxResult> {
|
||||
return await ctx.with('tx-update-doc', { _class: tx.objectClass }, async (_ctx) => {
|
||||
protected txUpdateDoc (ctx: MeasureContext, tx: TxUpdateDoc<Doc>): Promise<TxResult> {
|
||||
return ctx.with('tx-update-doc', { _class: tx.objectClass }, (_ctx) => {
|
||||
if (isOperator(tx.operations)) {
|
||||
let doc: Doc | undefined
|
||||
const ops: any = { '%hash%': null, ...tx.operations }
|
||||
return await _ctx.with(
|
||||
return _ctx.with(
|
||||
'update with operations',
|
||||
{ operations: JSON.stringify(Object.keys(tx.operations)) },
|
||||
async (ctx) => {
|
||||
@ -1517,17 +1512,13 @@ class PostgresAdapter extends PostgresAdapterBase {
|
||||
}
|
||||
)
|
||||
} else {
|
||||
return await this.updateDoc(_ctx, tx, tx.retrieve ?? false)
|
||||
return this.updateDoc(_ctx, tx, tx.retrieve ?? false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private async updateDoc<T extends Doc>(
|
||||
ctx: MeasureContext,
|
||||
tx: TxUpdateDoc<T>,
|
||||
retrieve: boolean
|
||||
): Promise<TxResult> {
|
||||
return await ctx.with('update jsonb_set', {}, async (_ctx) => {
|
||||
private updateDoc<T extends Doc>(ctx: MeasureContext, tx: TxUpdateDoc<T>, retrieve: boolean): Promise<TxResult> {
|
||||
return ctx.with('update jsonb_set', {}, async (_ctx) => {
|
||||
const updates: string[] = ['"modifiedBy" = $1', '"modifiedOn" = $2']
|
||||
const params: any[] = [tx.modifiedBy, tx.modifiedOn, tx.objectId, this.workspaceId.name]
|
||||
let paramsIndex = 5
|
||||
@ -1555,12 +1546,12 @@ class PostgresAdapter extends PostgresAdapterBase {
|
||||
}
|
||||
await this.withConnection(ctx, async (connection) => {
|
||||
try {
|
||||
await this.retryTxn(connection, async (client) => {
|
||||
await client.unsafe(
|
||||
await this.retryTxn(connection, (client) =>
|
||||
client.unsafe(
|
||||
`UPDATE ${translateDomain(this.hierarchy.getDomain(tx.objectClass))} SET ${updates.join(', ')} WHERE _id = $3 AND "workspaceId" = $4`,
|
||||
params
|
||||
)
|
||||
})
|
||||
)
|
||||
if (retrieve) {
|
||||
const object = await this.findDoc(_ctx, connection, tx.objectClass, tx.objectId)
|
||||
return { object }
|
||||
@ -1573,14 +1564,14 @@ class PostgresAdapter extends PostgresAdapterBase {
|
||||
})
|
||||
}
|
||||
|
||||
private async findDoc (
|
||||
private findDoc (
|
||||
ctx: MeasureContext,
|
||||
client: postgres.Sql | postgres.ReservedSql,
|
||||
_class: Ref<Class<Doc>>,
|
||||
_id: Ref<Doc>,
|
||||
forUpdate: boolean = false
|
||||
): Promise<Doc | undefined> {
|
||||
return await ctx.with('find-doc', { _class }, async () => {
|
||||
return ctx.with('find-doc', { _class }, async () => {
|
||||
const res =
|
||||
await client`SELECT * FROM ${this.client(translateDomain(this.hierarchy.getDomain(_class)))} WHERE _id = ${_id} AND "workspaceId" = ${this.workspaceId.name} ${
|
||||
forUpdate ? client` FOR UPDATE` : client``
|
||||
@ -1595,9 +1586,11 @@ class PostgresAdapter extends PostgresAdapterBase {
|
||||
await ctx.with('tx-remove-doc', { _class: tx.objectClass }, async (_ctx) => {
|
||||
const domain = translateDomain(this.hierarchy.getDomain(tx.objectClass))
|
||||
await this.withConnection(_ctx, async (connection) => {
|
||||
await this.retryTxn(connection, async (client) => {
|
||||
await client`DELETE FROM ${client(domain)} WHERE _id = ${tx.objectId} AND "workspaceId" = ${this.workspaceId.name}`
|
||||
})
|
||||
await this.retryTxn(
|
||||
connection,
|
||||
(client) =>
|
||||
client`DELETE FROM ${client(domain)} WHERE _id = ${tx.objectId} AND "workspaceId" = ${this.workspaceId.name}`
|
||||
)
|
||||
})
|
||||
})
|
||||
return {}
|
||||
|
@ -341,7 +341,7 @@ export class S3Service implements StorageAdapter {
|
||||
}
|
||||
|
||||
@withContext('put')
|
||||
async put (
|
||||
put (
|
||||
ctx: MeasureContext,
|
||||
workspaceId: WorkspaceId,
|
||||
objectName: string,
|
||||
@ -350,7 +350,7 @@ export class S3Service implements StorageAdapter {
|
||||
size?: number
|
||||
): Promise<UploadedObjectInfo> {
|
||||
if (size !== undefined && size < 1024 * 1024 * 5) {
|
||||
return await ctx.with(
|
||||
return ctx.with(
|
||||
'simple-put',
|
||||
{},
|
||||
async () => {
|
||||
@ -371,7 +371,7 @@ export class S3Service implements StorageAdapter {
|
||||
)
|
||||
// Less 5Mb
|
||||
} else {
|
||||
return await ctx.with(
|
||||
return ctx.with(
|
||||
'multipart-upload',
|
||||
{},
|
||||
async () => {
|
||||
|
@ -90,7 +90,7 @@ class TSessionManager implements SessionManager {
|
||||
checkInterval: any
|
||||
|
||||
sessions = new Map<string, { session: Session, socket: ConnectionSocket }>()
|
||||
reconnectIds = new Map<string, any>()
|
||||
reconnectIds = new Set<string>()
|
||||
|
||||
maintenanceTimer: any
|
||||
timeMinutes = 0
|
||||
@ -180,8 +180,26 @@ class TSessionManager implements SessionManager {
|
||||
const now = Date.now()
|
||||
for (const [wsId, workspace] of this.workspaces.entries()) {
|
||||
if (this.ticks % (60 * ticksPerSecond) === workspace.tickHash) {
|
||||
try {
|
||||
// update account lastVisit every minute per every workspace.∏
|
||||
void this.getWorkspaceInfo(this.ctx, workspace.token)
|
||||
void this.getWorkspaceInfo(this.ctx, workspace.token).catch(() => {
|
||||
// Ignore
|
||||
})
|
||||
} catch (err: any) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
for (const [k, v] of Array.from(workspace.tickHandlers.entries())) {
|
||||
v.ticks--
|
||||
if (v.ticks === 0) {
|
||||
workspace.tickHandlers.delete(k)
|
||||
try {
|
||||
v.operation()
|
||||
} catch (err: any) {
|
||||
Analytics.handleError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const s of workspace.sessions) {
|
||||
@ -465,7 +483,9 @@ class TSessionManager implements SessionManager {
|
||||
|
||||
// We do not need to wait for set-status, just return session to client
|
||||
const _workspace = workspace
|
||||
void ctx.with('set-status', {}, (ctx) => this.trySetStatus(ctx, session, true, _workspace.workspaceId))
|
||||
void ctx
|
||||
.with('set-status', {}, (ctx) => this.trySetStatus(ctx, session, true, _workspace.workspaceId))
|
||||
.catch(() => {})
|
||||
|
||||
if (this.timeMinutes > 0) {
|
||||
ws.send(ctx, { result: this.createMaintenanceWarning() }, session.binaryMode, session.useCompression)
|
||||
@ -638,6 +658,7 @@ class TSessionManager implements SessionManager {
|
||||
branding,
|
||||
workspaceInitCompleted: false,
|
||||
tickHash: this.tickCounter % ticksPerSecond,
|
||||
tickHandlers: new Map(),
|
||||
token: generateToken(systemAccountEmail, token.workspace)
|
||||
}
|
||||
this.workspaces.set(toWorkspaceString(token.workspace), workspace)
|
||||
@ -714,21 +735,25 @@ class TSessionManager implements SessionManager {
|
||||
this.sessions.delete(ws.id)
|
||||
if (workspace !== undefined) {
|
||||
workspace.sessions.delete(sessionRef.session.sessionId)
|
||||
}
|
||||
this.reconnectIds.set(
|
||||
sessionRef.session.sessionId,
|
||||
setTimeout(() => {
|
||||
|
||||
workspace.tickHandlers.set(sessionRef.session.sessionId, {
|
||||
ticks: this.timeouts.reconnectTimeout * ticksPerSecond,
|
||||
operation: () => {
|
||||
this.reconnectIds.delete(sessionRef.session.sessionId)
|
||||
|
||||
const user = sessionRef.session.getUser()
|
||||
if (workspace !== undefined) {
|
||||
const another = Array.from(workspace.sessions.values()).findIndex((p) => p.session.getUser() === user)
|
||||
if (another === -1 && !workspace.upgrade) {
|
||||
void this.trySetStatus(workspace.context, sessionRef.session, false, workspace.workspaceId)
|
||||
}
|
||||
}
|
||||
}, this.timeouts.reconnectTimeout)
|
||||
void this.trySetStatus(workspace.context, sessionRef.session, false, workspace.workspaceId).catch(
|
||||
() => {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
this.reconnectIds.add(sessionRef.session.sessionId)
|
||||
}
|
||||
try {
|
||||
sessionRef.socket.close()
|
||||
} catch (err) {
|
||||
@ -898,8 +923,8 @@ class TSessionManager implements SessionManager {
|
||||
const reqId = generateId()
|
||||
|
||||
const st = Date.now()
|
||||
try {
|
||||
void userCtx.with(`🧭 ${backupMode ? 'handleBackup' : 'handleRequest'}`, {}, async (ctx) => {
|
||||
void userCtx
|
||||
.with(`🧭 ${backupMode ? 'handleBackup' : 'handleRequest'}`, {}, async (ctx) => {
|
||||
if (request.time != null) {
|
||||
const delta = Date.now() - request.time
|
||||
requestCtx.measure('msg-receive-delta', delta)
|
||||
@ -950,8 +975,6 @@ class TSessionManager implements SessionManager {
|
||||
}
|
||||
const reconnect = this.reconnectIds.has(service.sessionId)
|
||||
if (reconnect) {
|
||||
const reconnectTimeout = this.reconnectIds.get(service.sessionId)
|
||||
clearTimeout(reconnectTimeout)
|
||||
this.reconnectIds.delete(service.sessionId)
|
||||
}
|
||||
const helloResponse: HelloResponse = {
|
||||
@ -1020,10 +1043,10 @@ class TSessionManager implements SessionManager {
|
||||
)
|
||||
}
|
||||
})
|
||||
} finally {
|
||||
.finally(() => {
|
||||
userCtx.end()
|
||||
service.requests.delete(reqId)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,9 +264,7 @@ export async function upgradeModel (
|
||||
|
||||
const t = Date.now()
|
||||
try {
|
||||
await ctx.with(op[0], {}, async (ctx) => {
|
||||
await preMigrate(preMigrateClient, logger)
|
||||
})
|
||||
await ctx.with(op[0], {}, (ctx) => preMigrate(preMigrateClient, logger))
|
||||
} catch (err: any) {
|
||||
logger.error(`error during pre-migrate: ${op[0]} ${err.message}`, err)
|
||||
throw err
|
||||
@ -309,9 +307,7 @@ export async function upgradeModel (
|
||||
for (const op of migrateOperations) {
|
||||
try {
|
||||
const t = Date.now()
|
||||
await ctx.with(op[0], {}, async () => {
|
||||
await op[1].migrate(migrateClient, logger)
|
||||
})
|
||||
await ctx.with(op[0], {}, () => op[1].migrate(migrateClient, logger))
|
||||
const tdelta = Date.now() - t
|
||||
if (tdelta > 0) {
|
||||
logger.log('migrate:', { workspaceId: workspaceId.name, operation: op[0], time: Date.now() - t })
|
||||
@ -340,9 +336,7 @@ export async function upgradeModel (
|
||||
let i = 0
|
||||
for (const op of migrateOperations) {
|
||||
const t = Date.now()
|
||||
await ctx.with(op[0], {}, async () => {
|
||||
await op[1].upgrade(migrateState, async () => connection, logger)
|
||||
})
|
||||
await ctx.with(op[0], {}, () => op[1].upgrade(migrateState, async () => connection, logger))
|
||||
const tdelta = Date.now() - t
|
||||
if (tdelta > 0) {
|
||||
logger.log('upgrade:', { operation: op[0], time: tdelta, workspaceId: workspaceId.name })
|
||||
|
@ -141,9 +141,7 @@ export class PlatformWorker {
|
||||
for (const [workspace, users] of workspaces) {
|
||||
const worker = this.clients.get(workspace)
|
||||
if (worker !== undefined) {
|
||||
await this.ctx.with('syncUsers', {}, async (ctx) => {
|
||||
await worker.syncUserData(ctx, users)
|
||||
})
|
||||
await this.ctx.with('syncUsers', {}, (ctx) => worker.syncUserData(ctx, users))
|
||||
}
|
||||
}
|
||||
this.periodicSyncPromise = undefined
|
||||
|
@ -998,8 +998,8 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
|
||||
const response: any = await this.ctx.with(
|
||||
'graphql.listIssue',
|
||||
{ prj: prj.name, repo: repo.name },
|
||||
async () =>
|
||||
await integration.octokit.graphql(
|
||||
() =>
|
||||
integration.octokit.graphql(
|
||||
`query listIssues {
|
||||
nodes(ids: [${idsp}] ) {
|
||||
... on Issue {
|
||||
|
@ -1125,8 +1125,8 @@ export class GithubWorker implements IntegrationManager {
|
||||
const docs = await this.ctx.with(
|
||||
'find-doc-sync-info',
|
||||
{},
|
||||
async (ctx) =>
|
||||
await this._client.findAll<DocSyncInfo>(
|
||||
(ctx) =>
|
||||
this._client.findAll<DocSyncInfo>(
|
||||
github.class.DocSyncInfo,
|
||||
{
|
||||
needSync: { $ne: githubSyncVersion },
|
||||
|
@ -14,7 +14,6 @@ export { serverGithubId } from '@hcengineering/server-github'
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||
trigger: serverGithub.trigger.OnProjectChanges,
|
||||
arrays: true,
|
||||
isAsync: true
|
||||
})
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user