mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 03:14:40 +03:00
UBERF-8582: Fix triggers (#7155)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
a220fac255
commit
e0ca033405
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
@ -55,7 +55,8 @@
|
|||||||
// "VERSION": "0.6.289",
|
// "VERSION": "0.6.289",
|
||||||
"ELASTIC_INDEX_NAME": "local_storage_index",
|
"ELASTIC_INDEX_NAME": "local_storage_index",
|
||||||
"UPLOAD_URL": "/files",
|
"UPLOAD_URL": "/files",
|
||||||
"AI_BOT_URL": "http://localhost:4010"
|
"AI_BOT_URL": "http://localhost:4010",
|
||||||
|
"STATS_URL": "http://host.docker.internal:4900",
|
||||||
},
|
},
|
||||||
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
|
||||||
"runtimeVersion": "20",
|
"runtimeVersion": "20",
|
||||||
|
@ -221,7 +221,6 @@ services:
|
|||||||
links:
|
links:
|
||||||
- mongodb
|
- mongodb
|
||||||
- minio
|
- minio
|
||||||
- rekoni
|
|
||||||
- account
|
- account
|
||||||
- stats
|
- stats
|
||||||
# - apm-server
|
# - apm-server
|
||||||
@ -261,7 +260,6 @@ services:
|
|||||||
links:
|
links:
|
||||||
- postgres
|
- postgres
|
||||||
- minio
|
- minio
|
||||||
- rekoni
|
|
||||||
- account
|
- account
|
||||||
- stats
|
- stats
|
||||||
# - apm-server
|
# - apm-server
|
||||||
@ -296,10 +294,6 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- 4004:4004
|
- 4004:4004
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 1024M
|
|
||||||
fulltext:
|
fulltext:
|
||||||
image: hardcoreeng/fulltext
|
image: hardcoreeng/fulltext
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
@ -319,10 +313,6 @@ services:
|
|||||||
- STATS_URL=http://host.docker.internal:4900
|
- STATS_URL=http://host.docker.internal:4900
|
||||||
- REKONI_URL=http://host.docker.internal:4004
|
- REKONI_URL=http://host.docker.internal:4004
|
||||||
- ACCOUNTS_URL=http://host.docker.internal:3000
|
- ACCOUNTS_URL=http://host.docker.internal:3000
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 300M
|
|
||||||
fulltext_pg:
|
fulltext_pg:
|
||||||
image: hardcoreeng/fulltext
|
image: hardcoreeng/fulltext
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
@ -343,10 +333,6 @@ services:
|
|||||||
- STATS_URL=http://host.docker.internal:4900
|
- STATS_URL=http://host.docker.internal:4900
|
||||||
- REKONI_URL=http://host.docker.internal:4004
|
- REKONI_URL=http://host.docker.internal:4004
|
||||||
- ACCOUNTS_URL=http://host.docker.internal:3000
|
- ACCOUNTS_URL=http://host.docker.internal:3000
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 300M
|
|
||||||
print:
|
print:
|
||||||
image: hardcoreeng/print
|
image: hardcoreeng/print
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
@ -358,10 +344,6 @@ services:
|
|||||||
- SECRET=secret
|
- SECRET=secret
|
||||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||||
- STATS_URL=http://host.docker.internal:4900
|
- STATS_URL=http://host.docker.internal:4900
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 300M
|
|
||||||
sign:
|
sign:
|
||||||
image: hardcoreeng/sign
|
image: hardcoreeng/sign
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
@ -382,10 +364,6 @@ services:
|
|||||||
- SERVICE_ID=sign-service
|
- SERVICE_ID=sign-service
|
||||||
- BRANDING_PATH=/var/cfg/branding.json
|
- BRANDING_PATH=/var/cfg/branding.json
|
||||||
- STATS_URL=http://host.docker.internal:4900
|
- STATS_URL=http://host.docker.internal:4900
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 300M
|
|
||||||
analytics:
|
analytics:
|
||||||
image: hardcoreeng/analytics-collector
|
image: hardcoreeng/analytics-collector
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
@ -402,10 +380,6 @@ services:
|
|||||||
- ACCOUNTS_URL=http://host.docker.internal:3000
|
- ACCOUNTS_URL=http://host.docker.internal:3000
|
||||||
- SUPPORT_WORKSPACE=support
|
- SUPPORT_WORKSPACE=support
|
||||||
- STATS_URL=http://host.docker.internal:4900
|
- STATS_URL=http://host.docker.internal:4900
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 300M
|
|
||||||
aiBot:
|
aiBot:
|
||||||
image: hardcoreeng/ai-bot
|
image: hardcoreeng/ai-bot
|
||||||
ports:
|
ports:
|
||||||
@ -426,10 +400,6 @@ services:
|
|||||||
- STATS_URL=http://host.docker.internal:4900
|
- STATS_URL=http://host.docker.internal:4900
|
||||||
# - LOVE_ENDPOINT=http://host.docker.internal:8096
|
# - LOVE_ENDPOINT=http://host.docker.internal:8096
|
||||||
# - OPENAI_API_KEY=token
|
# - OPENAI_API_KEY=token
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 300M
|
|
||||||
# telegram-bot:
|
# telegram-bot:
|
||||||
# image: hardcoreeng/telegram-bot
|
# image: hardcoreeng/telegram-bot
|
||||||
# extra_hosts:
|
# extra_hosts:
|
||||||
@ -445,10 +415,6 @@ services:
|
|||||||
# - ACCOUNTS_URL=http://host.docker.internal:3000
|
# - ACCOUNTS_URL=http://host.docker.internal:3000
|
||||||
# - SERVICE_ID=telegram-bot-service
|
# - SERVICE_ID=telegram-bot-service
|
||||||
# - STATS_URL=http://host.docker.internal:4900
|
# - STATS_URL=http://host.docker.internal:4900
|
||||||
# deploy:
|
|
||||||
# resources:
|
|
||||||
# limits:
|
|
||||||
# memory: 300M
|
|
||||||
volumes:
|
volumes:
|
||||||
db:
|
db:
|
||||||
dbpg:
|
dbpg:
|
||||||
|
@ -2,50 +2,51 @@
|
|||||||
// Copyright @ 2022-2023 Hardcore Engineering Inc.
|
// Copyright @ 2022-2023 Hardcore Engineering Inc.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import attachment, { type Attachment } from '@hcengineering/attachment'
|
||||||
import {
|
import {
|
||||||
|
clone,
|
||||||
|
loadCollaborativeDoc,
|
||||||
|
saveCollaborativeDoc,
|
||||||
|
YAbstractType,
|
||||||
|
YXmlElement,
|
||||||
|
YXmlText
|
||||||
|
} from '@hcengineering/collaboration'
|
||||||
|
import {
|
||||||
|
type ChangeControl,
|
||||||
|
type ControlledDocument,
|
||||||
|
createChangeControl,
|
||||||
|
createDocumentTemplate,
|
||||||
|
type DocumentCategory,
|
||||||
|
documentsId,
|
||||||
|
DocumentState
|
||||||
|
} from '@hcengineering/controlled-documents'
|
||||||
|
import {
|
||||||
|
type Class,
|
||||||
type Data,
|
type Data,
|
||||||
type Ref,
|
type Doc,
|
||||||
TxOperations,
|
|
||||||
generateId,
|
|
||||||
DOMAIN_TX,
|
DOMAIN_TX,
|
||||||
|
generateId,
|
||||||
makeCollaborativeDoc,
|
makeCollaborativeDoc,
|
||||||
MeasureMetricsContext,
|
MeasureMetricsContext,
|
||||||
type Class,
|
type Ref,
|
||||||
type Doc,
|
SortingOrder,
|
||||||
SortingOrder
|
toIdMap,
|
||||||
|
TxOperations
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import {
|
import {
|
||||||
createDefaultSpace,
|
createDefaultSpace,
|
||||||
createOrUpdate,
|
createOrUpdate,
|
||||||
type MigrateUpdate,
|
|
||||||
type MigrationDocumentQuery,
|
|
||||||
tryMigrate,
|
|
||||||
tryUpgrade,
|
|
||||||
type MigrateOperation,
|
type MigrateOperation,
|
||||||
|
type MigrateUpdate,
|
||||||
type MigrationClient,
|
type MigrationClient,
|
||||||
type MigrationUpgradeClient
|
type MigrationDocumentQuery,
|
||||||
|
type MigrationUpgradeClient,
|
||||||
|
tryMigrate,
|
||||||
|
tryUpgrade
|
||||||
} from '@hcengineering/model'
|
} from '@hcengineering/model'
|
||||||
|
import { DOMAIN_ATTACHMENT } from '@hcengineering/model-attachment'
|
||||||
import core from '@hcengineering/model-core'
|
import core from '@hcengineering/model-core'
|
||||||
import tags from '@hcengineering/tags'
|
import tags from '@hcengineering/tags'
|
||||||
import {
|
|
||||||
type ChangeControl,
|
|
||||||
type DocumentCategory,
|
|
||||||
DocumentState,
|
|
||||||
documentsId,
|
|
||||||
createDocumentTemplate,
|
|
||||||
type ControlledDocument,
|
|
||||||
createChangeControl
|
|
||||||
} from '@hcengineering/controlled-documents'
|
|
||||||
import {
|
|
||||||
loadCollaborativeDoc,
|
|
||||||
saveCollaborativeDoc,
|
|
||||||
YXmlElement,
|
|
||||||
YXmlText,
|
|
||||||
YAbstractType,
|
|
||||||
clone
|
|
||||||
} from '@hcengineering/collaboration'
|
|
||||||
import attachment, { type Attachment } from '@hcengineering/attachment'
|
|
||||||
import { DOMAIN_ATTACHMENT } from '@hcengineering/model-attachment'
|
|
||||||
|
|
||||||
import documents, { DOMAIN_DOCUMENTS } from './index'
|
import documents, { DOMAIN_DOCUMENTS } from './index'
|
||||||
|
|
||||||
@ -210,6 +211,7 @@ async function createDocumentCategories (tx: TxOperations): Promise<void> {
|
|||||||
{ code: 'CM', title: 'Client Management' }
|
{ code: 'CM', title: 'Client Management' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const catsCache = toIdMap(await tx.findAll(documents.class.DocumentCategory, {}))
|
||||||
const ops = tx.apply()
|
const ops = tx.apply()
|
||||||
for (const c of categories) {
|
for (const c of categories) {
|
||||||
await createOrUpdate(
|
await createOrUpdate(
|
||||||
@ -217,7 +219,8 @@ async function createDocumentCategories (tx: TxOperations): Promise<void> {
|
|||||||
documents.class.DocumentCategory,
|
documents.class.DocumentCategory,
|
||||||
documents.space.QualityDocuments,
|
documents.space.QualityDocuments,
|
||||||
{ ...c, attachments: 0 },
|
{ ...c, attachments: 0 },
|
||||||
((documents.category.DOC as string) + ' - ' + c.code) as Ref<DocumentCategory>
|
((documents.category.DOC as string) + ' - ' + c.code) as Ref<DocumentCategory>,
|
||||||
|
catsCache
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
await ops.commit()
|
await ops.commit()
|
||||||
|
@ -14,7 +14,15 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import { getCategories } from '@anticrm/skillset'
|
import { getCategories } from '@anticrm/skillset'
|
||||||
import core, { DOMAIN_TX, TxOperations, type Ref, type Space, type Status } from '@hcengineering/core'
|
import core, {
|
||||||
|
DOMAIN_TX,
|
||||||
|
toIdMap,
|
||||||
|
TxOperations,
|
||||||
|
type Doc,
|
||||||
|
type Ref,
|
||||||
|
type Space,
|
||||||
|
type Status
|
||||||
|
} from '@hcengineering/core'
|
||||||
import {
|
import {
|
||||||
createOrUpdate,
|
createOrUpdate,
|
||||||
migrateSpace,
|
migrateSpace,
|
||||||
@ -26,7 +34,7 @@ import {
|
|||||||
type ModelLogger
|
type ModelLogger
|
||||||
} from '@hcengineering/model'
|
} from '@hcengineering/model'
|
||||||
import tags, { type TagCategory } from '@hcengineering/model-tags'
|
import tags, { type TagCategory } from '@hcengineering/model-tags'
|
||||||
import task, { DOMAIN_TASK, createSequence, migrateDefaultStatusesBase } from '@hcengineering/model-task'
|
import task, { createSequence, DOMAIN_TASK, migrateDefaultStatusesBase } from '@hcengineering/model-task'
|
||||||
import { recruitId, type Applicant } from '@hcengineering/recruit'
|
import { recruitId, type Applicant } from '@hcengineering/recruit'
|
||||||
|
|
||||||
import { DOMAIN_CALENDAR } from '@hcengineering/model-calendar'
|
import { DOMAIN_CALENDAR } from '@hcengineering/model-calendar'
|
||||||
@ -194,10 +202,16 @@ async function createDefaults (client: MigrationUpgradeClient, tx: TxOperations)
|
|||||||
},
|
},
|
||||||
recruit.category.Other
|
recruit.category.Other
|
||||||
)
|
)
|
||||||
|
const ops = tx.apply()
|
||||||
|
|
||||||
for (const c of getCategories()) {
|
const cats = getCategories().filter((it, idx, arr) => arr.findIndex((qt) => qt.id === it.id) === idx)
|
||||||
|
|
||||||
|
const existingCategories = toIdMap(
|
||||||
|
await client.findAll<Doc>(tags.class.TagCategory, { targetClass: recruit.mixin.Candidate })
|
||||||
|
)
|
||||||
|
for (const c of cats) {
|
||||||
await createOrUpdate(
|
await createOrUpdate(
|
||||||
tx,
|
ops,
|
||||||
tags.class.TagCategory,
|
tags.class.TagCategory,
|
||||||
core.space.Workspace,
|
core.space.Workspace,
|
||||||
{
|
{
|
||||||
@ -207,9 +221,11 @@ async function createDefaults (client: MigrationUpgradeClient, tx: TxOperations)
|
|||||||
tags: c.skills,
|
tags: c.skills,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
(recruit.category.Category + '.' + c.id) as Ref<TagCategory>
|
(recruit.category.Category + '.' + c.id) as Ref<TagCategory>,
|
||||||
|
existingCategories
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
await ops.commit()
|
||||||
|
|
||||||
await createSequence(tx, recruit.class.Review)
|
await createSequence(tx, recruit.class.Review)
|
||||||
await createSequence(tx, recruit.class.Opinion)
|
await createSequence(tx, recruit.class.Opinion)
|
||||||
|
@ -44,14 +44,13 @@ export function createModel (builder: Builder): void {
|
|||||||
|
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverActivity.trigger.ActivityMessagesHandler,
|
trigger: serverActivity.trigger.ActivityMessagesHandler,
|
||||||
txMatch: {
|
arrays: true,
|
||||||
'tx.objectClass': { $nin: [activity.class.ActivityMessage, notification.class.DocNotifyContext] }
|
|
||||||
},
|
|
||||||
isAsync: true
|
isAsync: true
|
||||||
})
|
})
|
||||||
|
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverActivity.trigger.OnDocRemoved
|
trigger: serverActivity.trigger.OnDocRemoved,
|
||||||
|
arrays: true
|
||||||
})
|
})
|
||||||
|
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
|
@ -37,6 +37,7 @@ export function createModel (builder: Builder): void {
|
|||||||
|
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverAiBot.trigger.OnMessageSend,
|
trigger: serverAiBot.trigger.OnMessageSend,
|
||||||
|
arrays: true,
|
||||||
isAsync: true
|
isAsync: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ export { serverCollaborationId } from '@hcengineering/server-collaboration'
|
|||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverCollaboration.trigger.OnDelete
|
trigger: serverCollaboration.trigger.OnDelete,
|
||||||
|
arrays: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import { type Builder, Mixin, Model } from '@hcengineering/model'
|
import { type Builder, Mixin, Model } from '@hcengineering/model'
|
||||||
|
|
||||||
|
import contact from '@hcengineering/contact'
|
||||||
import core, { type Ref } from '@hcengineering/core'
|
import core, { type Ref } from '@hcengineering/core'
|
||||||
import { TClass, TDoc } from '@hcengineering/model-core'
|
import { TClass, TDoc } from '@hcengineering/model-core'
|
||||||
import { TNotificationType } from '@hcengineering/model-notification'
|
import { TNotificationType } from '@hcengineering/model-notification'
|
||||||
@ -33,7 +34,6 @@ import serverNotification, {
|
|||||||
type TypeMatch,
|
type TypeMatch,
|
||||||
type TypeMatchFunc
|
type TypeMatchFunc
|
||||||
} from '@hcengineering/server-notification'
|
} from '@hcengineering/server-notification'
|
||||||
import contact from '@hcengineering/contact'
|
|
||||||
|
|
||||||
export { serverNotificationId } from '@hcengineering/server-notification'
|
export { serverNotificationId } from '@hcengineering/server-notification'
|
||||||
|
|
||||||
@ -89,7 +89,8 @@ export function createModel (builder: Builder): void {
|
|||||||
})
|
})
|
||||||
|
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverNotification.trigger.OnDocRemove
|
trigger: serverNotification.trigger.OnDocRemove,
|
||||||
|
arrays: true
|
||||||
})
|
})
|
||||||
|
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
|
@ -25,7 +25,8 @@ export { serverRequestId } from '@hcengineering/server-request'
|
|||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverRequest.trigger.OnRequest
|
trigger: serverRequest.trigger.OnRequest,
|
||||||
|
arrays: true
|
||||||
})
|
})
|
||||||
|
|
||||||
builder.mixin(request.class.Request, core.class.Class, serverNotification.mixin.TextPresenter, {
|
builder.mixin(request.class.Request, core.class.Class, serverNotification.mixin.TextPresenter, {
|
||||||
|
@ -23,7 +23,8 @@ export { serverTagsId } from '@hcengineering/server-tags'
|
|||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverTags.trigger.onTagReference
|
trigger: serverTags.trigger.onTagReference,
|
||||||
|
arrays: true
|
||||||
})
|
})
|
||||||
|
|
||||||
builder.mixin<Class<Doc>, ObjectDDParticipant>(
|
builder.mixin<Class<Doc>, ObjectDDParticipant>(
|
||||||
|
@ -22,6 +22,7 @@ export { serverTaskId } from '@hcengineering/server-task'
|
|||||||
|
|
||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverTask.trigger.OnStateUpdate
|
trigger: serverTask.trigger.OnStateUpdate,
|
||||||
|
arrays: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ export function createModel (builder: Builder): void {
|
|||||||
|
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverTime.trigger.OnTask,
|
trigger: serverTime.trigger.OnTask,
|
||||||
|
arrays: true,
|
||||||
isAsync: true
|
isAsync: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -15,10 +15,10 @@
|
|||||||
|
|
||||||
import core, { type Doc } from '@hcengineering/core'
|
import core, { type Doc } from '@hcengineering/core'
|
||||||
import { type Builder, Mixin } from '@hcengineering/model'
|
import { type Builder, Mixin } from '@hcengineering/model'
|
||||||
import serverCore, { type TriggerControl } from '@hcengineering/server-core'
|
|
||||||
import serverView, { type ServerLinkIdProvider } from '@hcengineering/server-view'
|
|
||||||
import { TClass } from '@hcengineering/model-core'
|
import { TClass } from '@hcengineering/model-core'
|
||||||
import { type Resource } from '@hcengineering/platform'
|
import { type Resource } from '@hcengineering/platform'
|
||||||
|
import serverCore, { type TriggerControl } from '@hcengineering/server-core'
|
||||||
|
import serverView, { type ServerLinkIdProvider } from '@hcengineering/server-view'
|
||||||
|
|
||||||
export { serverViewId } from '@hcengineering/server-view'
|
export { serverViewId } from '@hcengineering/server-view'
|
||||||
|
|
||||||
@ -31,6 +31,10 @@ export function createModel (builder: Builder): void {
|
|||||||
builder.createModel(TServerLinkIdProvider)
|
builder.createModel(TServerLinkIdProvider)
|
||||||
|
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverView.trigger.OnCustomAttributeRemove
|
trigger: serverView.trigger.OnCustomAttributeRemove,
|
||||||
|
txMatch: {
|
||||||
|
_class: core.class.TxRemoveDoc,
|
||||||
|
objectClass: core.class.Attribute
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Class, Data, Doc, DocumentUpdate, Ref, Space, TxOperations } from '@hcengineering/core'
|
import { Class, Data, Doc, DocumentUpdate, Ref, Space, TxOperations, type IdMap } from '@hcengineering/core'
|
||||||
import { deepEqual } from 'fast-equals'
|
import { deepEqual } from 'fast-equals'
|
||||||
|
|
||||||
function toUndef (value: any): any {
|
function toUndef (value: any): any {
|
||||||
@ -40,9 +40,10 @@ export async function createOrUpdate<T extends Doc> (
|
|||||||
_class: Ref<Class<T>>,
|
_class: Ref<Class<T>>,
|
||||||
space: Ref<Space>,
|
space: Ref<Space>,
|
||||||
data: Data<T>,
|
data: Data<T>,
|
||||||
_id: Ref<T>
|
_id: Ref<T>,
|
||||||
|
cache?: IdMap<Doc>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const existingDoc = await client.findOne<Doc>(_class, { _id })
|
const existingDoc = cache !== undefined ? cache.get(_id) : await client.findOne<Doc>(_class, { _id })
|
||||||
if (existingDoc !== undefined) {
|
if (existingDoc !== undefined) {
|
||||||
const { _class: _oldClass, _id, space: _oldSpace, modifiedBy, modifiedOn, ...oldData } = existingDoc
|
const { _class: _oldClass, _id, space: _oldSpace, modifiedBy, modifiedOn, ...oldData } = existingDoc
|
||||||
if (modifiedBy === client.txFactory.account) {
|
if (modifiedBy === client.txFactory.account) {
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import { Analytics } from '@hcengineering/analytics'
|
import { Analytics } from '@hcengineering/analytics'
|
||||||
|
import { configureAnalytics, SplitLogger } from '@hcengineering/analytics-service'
|
||||||
import { MeasureMetricsContext, newMetrics } from '@hcengineering/core'
|
import { MeasureMetricsContext, newMetrics } from '@hcengineering/core'
|
||||||
import { startFront } from '@hcengineering/front/src/starter'
|
import { startFront } from '@hcengineering/front/src/starter'
|
||||||
import { configureAnalytics, SplitLogger } from '@hcengineering/analytics-service'
|
|
||||||
import { join } from 'path'
|
|
||||||
import { initStatisticsContext } from '@hcengineering/server-core'
|
import { initStatisticsContext } from '@hcengineering/server-core'
|
||||||
|
import { join } from 'path'
|
||||||
|
|
||||||
configureAnalytics(process.env.SENTRY_DSN, {})
|
configureAnalytics(process.env.SENTRY_DSN, {})
|
||||||
Analytics.setTag('application', 'front')
|
Analytics.setTag('application', 'front')
|
||||||
@ -45,5 +45,5 @@ startFront(metricsContext, {
|
|||||||
ANALYTICS_COLLECTOR_URL: process.env.ANALYTICS_COLLECTOR_URL,
|
ANALYTICS_COLLECTOR_URL: process.env.ANALYTICS_COLLECTOR_URL,
|
||||||
AI_URL: process.env.AI_URL,
|
AI_URL: process.env.AI_URL,
|
||||||
TELEGRAM_BOT_URL: process.env.TELEGRAM_BOT_URL,
|
TELEGRAM_BOT_URL: process.env.TELEGRAM_BOT_URL,
|
||||||
STATS_URL: process.env.STATS_URL
|
STATS_URL: process.env.STATS_API ?? process.env.STATS_URL
|
||||||
})
|
})
|
||||||
|
@ -20,7 +20,7 @@ import { initStatisticsContext, type StorageConfiguration } from '@hcengineering
|
|||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
|
||||||
import { createElasticAdapter } from '@hcengineering/elastic'
|
import { createElasticAdapter } from '@hcengineering/elastic'
|
||||||
import { createRekoniAdapter, createYDocAdapter, type FulltextDBConfiguration } from '@hcengineering/server-indexer'
|
import { createRekoniAdapter, type FulltextDBConfiguration } from '@hcengineering/server-indexer'
|
||||||
import { buildStorageFromConfig, storageConfigFromEnv } from '@hcengineering/server-storage'
|
import { buildStorageFromConfig, storageConfigFromEnv } from '@hcengineering/server-storage'
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
import { startIndexer } from './server'
|
import { startIndexer } from './server'
|
||||||
@ -80,11 +80,6 @@ const config: FulltextDBConfiguration = {
|
|||||||
factory: createRekoniAdapter,
|
factory: createRekoniAdapter,
|
||||||
contentType: '*',
|
contentType: '*',
|
||||||
url: rekoniUrl
|
url: rekoniUrl
|
||||||
},
|
|
||||||
YDoc: {
|
|
||||||
factory: createYDocAdapter,
|
|
||||||
contentType: 'application/ydoc',
|
|
||||||
url: ''
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
defaultContentAdapter: 'Rekoni'
|
defaultContentAdapter: 'Rekoni'
|
||||||
|
@ -8,8 +8,7 @@
|
|||||||
"template": "@hcengineering/node-package",
|
"template": "@hcengineering/node-package",
|
||||||
"license": "EPL-2.0",
|
"license": "EPL-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "rush bundle --to @hcengineering/pod-server && cross-env NODE_ENV=production MODEL_VERSION=$(node ../../common/scripts/show_version.js) ACCOUNTS_URL=http://localhost:3000 REKONI_URL=http://localhost:4004 MONGO_URL=mongodb://localhost:27017 DB_URL=mongodb://localhost:27017 FRONT_URL=http://localhost:8087 UPLOAD_URL=/upload MINIO_ENDPOINT=localhost MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin METRICS_CONSOLE=true SERVER_SECRET=secret OPERATION_PROFILING=false MODEL_JSON=../../models/all/bundle/model.json node --inspect bundle/bundle.js",
|
"start": "rush bundle --to @hcengineering/pod-server && cross-env NODE_ENV=production MODEL_VERSION=$(node ../../common/scripts/show_version.js) ACCOUNTS_URL=http://localhost:3000 REKONI_URL=http://localhost:4004 MONGO_URL=mongodb://localhost:27017 DB_URL=mongodb://localhost:27017 FRONT_URL=http://localhost:8087 UPLOAD_URL=/upload MINIO_ENDPOINT=localhost MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin METRICS_CONSOLE=true SERVER_SECRET=secret OPERATION_PROFILING=false MODEL_JSON=../../models/all/bundle/model.json STATS_URL=http://host.docker.internal:4900 node --inspect bundle/bundle.js",
|
||||||
"start-u": "rush bundle --to @hcengineering/pod-server && ./bundle/ && cross-env NODE_ENV=production MODEL_VERSION=$(node ../../common/scripts/show_version.js) ACCOUNTS_URL=http://localhost:3000 REKONI_URL=http://localhost:4004 MONGO_URL=mongodb://localhost:27017 FRONT_URL=http://localhost:8087 UPLOAD_URL=/upload MINIO_ENDPOINT=localhost MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin METRICS_CONSOLE=true SERVER_SECRET=secret MODEL_JSON=../../models/all/bundle/model.json node bundle/bundle.js",
|
|
||||||
"start-flame": "rush bundle --to @hcengineering/pod-server && cross-env NODE_ENV=production MODEL_VERSION=$(node ../../common/scripts/show_version.js) ACCOUNTS_URL=http://localhost:3000 REKONI_URL=http://localhost:4004 MONGO_URL=mongodb://localhost:27017 FRONT_URL=http://localhost:8087 UPLOAD_URL=/upload MINIO_ENDPOINT=localhost MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin METRICS_CONSOLE=true SERVER_SECRET=secret MODEL_JSON=../../models/all/bundle/model.json clinic flame --dest ./out -- node --nolazy -r ts-node/register --enable-source-maps src/__start.ts",
|
"start-flame": "rush bundle --to @hcengineering/pod-server && cross-env NODE_ENV=production MODEL_VERSION=$(node ../../common/scripts/show_version.js) ACCOUNTS_URL=http://localhost:3000 REKONI_URL=http://localhost:4004 MONGO_URL=mongodb://localhost:27017 FRONT_URL=http://localhost:8087 UPLOAD_URL=/upload MINIO_ENDPOINT=localhost MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin METRICS_CONSOLE=true SERVER_SECRET=secret MODEL_JSON=../../models/all/bundle/model.json clinic flame --dest ./out -- node --nolazy -r ts-node/register --enable-source-maps src/__start.ts",
|
||||||
"build": "compile",
|
"build": "compile",
|
||||||
"_phase:bundle": "rushx bundle",
|
"_phase:bundle": "rushx bundle",
|
||||||
|
@ -387,59 +387,58 @@ export async function generateDocUpdateMessages (
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ActivityMessagesHandler (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
async function ActivityMessagesHandler (_txes: TxCUD<Doc>[], control: TriggerControl): Promise<Tx[]> {
|
||||||
if (
|
const ltxes = _txes.filter(
|
||||||
control.hierarchy.isDerived(tx.objectClass, activity.class.ActivityMessage) ||
|
(it) =>
|
||||||
control.hierarchy.isDerived(tx.objectClass, notification.class.DocNotifyContext) ||
|
!(
|
||||||
control.hierarchy.isDerived(tx.objectClass, notification.class.ActivityInboxNotification) ||
|
control.hierarchy.isDerived(it.objectClass, activity.class.ActivityMessage) ||
|
||||||
control.hierarchy.isDerived(tx.objectClass, notification.class.BrowserNotification)
|
control.hierarchy.isDerived(it.objectClass, notification.class.DocNotifyContext) ||
|
||||||
) {
|
control.hierarchy.isDerived(it.objectClass, notification.class.ActivityInboxNotification) ||
|
||||||
return []
|
control.hierarchy.isDerived(it.objectClass, notification.class.BrowserNotification)
|
||||||
}
|
)
|
||||||
|
)
|
||||||
|
|
||||||
const cache: DocObjectCache = control.contextCache.get('ActivityMessagesHandler') ?? {
|
const cache: DocObjectCache = control.contextCache.get('ActivityMessagesHandler') ?? {
|
||||||
docs: new Map(),
|
docs: new Map(),
|
||||||
transactions: new Map()
|
transactions: new Map()
|
||||||
}
|
}
|
||||||
control.contextCache.set('ActivityMessagesHandler', cache)
|
control.contextCache.set('ActivityMessagesHandler', cache)
|
||||||
|
const result: Tx[] = []
|
||||||
|
for (const tx of ltxes) {
|
||||||
const txes =
|
const txes =
|
||||||
tx.space === core.space.DerivedTx
|
tx.space === core.space.DerivedTx
|
||||||
? []
|
? []
|
||||||
: await control.ctx.with(
|
: await control.ctx.with('generateDocUpdateMessages', {}, (ctx) =>
|
||||||
'generateDocUpdateMessages',
|
generateDocUpdateMessages(ctx, tx, control, [], undefined, cache)
|
||||||
{},
|
|
||||||
async (ctx) => await generateDocUpdateMessages(ctx, tx, control, [], undefined, cache)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const messages = txes.map((messageTx) => TxProcessor.createDoc2Doc(messageTx.tx as TxCreateDoc<DocUpdateMessage>))
|
const messages = txes.map((messageTx) => TxProcessor.createDoc2Doc(messageTx.tx as TxCreateDoc<DocUpdateMessage>))
|
||||||
|
|
||||||
const notificationTxes = await control.ctx.with(
|
const notificationTxes = await control.ctx.with('createCollaboratorNotifications', {}, (ctx) =>
|
||||||
'createCollaboratorNotifications',
|
createCollaboratorNotifications(ctx, tx, control, messages, undefined, cache.docs as Map<Ref<Doc>, Doc>)
|
||||||
{},
|
|
||||||
async (ctx) =>
|
|
||||||
await createCollaboratorNotifications(ctx, tx, control, messages, undefined, cache.docs as Map<Ref<Doc>, Doc>)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const result = [...txes, ...notificationTxes]
|
result.push(...txes, ...notificationTxes)
|
||||||
|
}
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
await control.apply(control.ctx, result)
|
await control.apply(control.ctx, result)
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
async function OnDocRemoved (originTx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
async function OnDocRemoved (txes: TxCUD<Doc>[], control: TriggerControl): Promise<Tx[]> {
|
||||||
|
const result: Tx[] = []
|
||||||
|
for (const originTx of txes) {
|
||||||
const tx = TxProcessor.extractTx(originTx) as TxCUD<Doc>
|
const tx = TxProcessor.extractTx(originTx) as TxCUD<Doc>
|
||||||
|
|
||||||
if (tx._class !== core.class.TxRemoveDoc) {
|
if (tx._class !== core.class.TxRemoveDoc) {
|
||||||
return []
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const activityDocMixin = control.hierarchy.classHierarchyMixin(tx.objectClass, activity.mixin.ActivityDoc)
|
const activityDocMixin = control.hierarchy.classHierarchyMixin(tx.objectClass, activity.mixin.ActivityDoc)
|
||||||
|
|
||||||
if (activityDocMixin === undefined) {
|
if (activityDocMixin === undefined) {
|
||||||
return []
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = await control.findAll(
|
const messages = await control.findAll(
|
||||||
@ -449,7 +448,11 @@ async function OnDocRemoved (originTx: TxCUD<Doc>, control: TriggerControl): Pro
|
|||||||
{ projection: { _id: 1, _class: 1, space: 1 } }
|
{ projection: { _id: 1, _class: 1, space: 1 } }
|
||||||
)
|
)
|
||||||
|
|
||||||
return messages.map((message) => control.txFactory.createTxRemoveDoc(message._class, message.space, message._id))
|
result.push(
|
||||||
|
...messages.map((message) => control.txFactory.createTxRemoveDoc(message._class, message.space, message._id))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ReactionNotificationContentProvider (
|
async function ReactionNotificationContentProvider (
|
||||||
|
@ -13,6 +13,15 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import aiBot, {
|
||||||
|
aiBotAccountEmail,
|
||||||
|
AIEventType,
|
||||||
|
AIMessageEventRequest,
|
||||||
|
AITransferEventRequest
|
||||||
|
} from '@hcengineering/ai-bot'
|
||||||
|
import analyticsCollector, { OnboardingChannel } from '@hcengineering/analytics-collector'
|
||||||
|
import chunter, { ChatMessage, DirectMessage, ThreadMessage } from '@hcengineering/chunter'
|
||||||
|
import contact, { PersonAccount } from '@hcengineering/contact'
|
||||||
import core, {
|
import core, {
|
||||||
AccountRole,
|
AccountRole,
|
||||||
AttachedDoc,
|
AttachedDoc,
|
||||||
@ -27,17 +36,8 @@ import core, {
|
|||||||
TxUpdateDoc,
|
TxUpdateDoc,
|
||||||
UserStatus
|
UserStatus
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { TriggerControl } from '@hcengineering/server-core'
|
|
||||||
import chunter, { ChatMessage, DirectMessage, ThreadMessage } from '@hcengineering/chunter'
|
|
||||||
import aiBot, {
|
|
||||||
aiBotAccountEmail,
|
|
||||||
AIEventType,
|
|
||||||
AIMessageEventRequest,
|
|
||||||
AITransferEventRequest
|
|
||||||
} from '@hcengineering/ai-bot'
|
|
||||||
import contact, { PersonAccount } from '@hcengineering/contact'
|
|
||||||
import { ActivityInboxNotification, MentionInboxNotification } from '@hcengineering/notification'
|
import { ActivityInboxNotification, MentionInboxNotification } from '@hcengineering/notification'
|
||||||
import analyticsCollector, { OnboardingChannel } from '@hcengineering/analytics-collector'
|
import { TriggerControl } from '@hcengineering/server-core'
|
||||||
|
|
||||||
import { createAccountRequest, getSupportWorkspaceId, sendAIEvents } from './utils'
|
import { createAccountRequest, getSupportWorkspaceId, sendAIEvents } from './utils'
|
||||||
|
|
||||||
@ -241,26 +241,29 @@ async function onSupportWorkspaceMessage (control: TriggerControl, message: Chat
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function OnMessageSend (
|
export async function OnMessageSend (
|
||||||
originTx: TxCollectionCUD<Doc, AttachedDoc>,
|
originTxs: TxCollectionCUD<Doc, AttachedDoc>[],
|
||||||
control: TriggerControl
|
control: TriggerControl
|
||||||
): Promise<Tx[]> {
|
): Promise<Tx[]> {
|
||||||
const { hierarchy } = control
|
const { hierarchy } = control
|
||||||
const tx = TxProcessor.extractTx(originTx) as TxCreateDoc<ChatMessage>
|
const txes = originTxs
|
||||||
if (tx._class !== core.class.TxCreateDoc || !hierarchy.isDerived(tx.objectClass, chunter.class.ChatMessage)) {
|
.map((it) => TxProcessor.extractTx(it) as TxCreateDoc<ChatMessage>)
|
||||||
|
.filter(
|
||||||
|
(it) =>
|
||||||
|
it._class === core.class.TxCreateDoc &&
|
||||||
|
hierarchy.isDerived(it.objectClass, chunter.class.ChatMessage) &&
|
||||||
|
!(it.modifiedBy === aiBot.account.AIBot || it.modifiedBy === core.account.System)
|
||||||
|
)
|
||||||
|
if (txes.length === 0) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
for (const tx of txes) {
|
||||||
if (tx.modifiedBy === aiBot.account.AIBot || tx.modifiedBy === core.account.System) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const isThread = hierarchy.isDerived(tx.objectClass, chunter.class.ThreadMessage)
|
const isThread = hierarchy.isDerived(tx.objectClass, chunter.class.ThreadMessage)
|
||||||
const message = TxProcessor.createDoc2Doc(tx)
|
const message = TxProcessor.createDoc2Doc(tx)
|
||||||
|
|
||||||
const docClass = isThread ? (message as ThreadMessage).objectClass : message.attachedToClass
|
const docClass = isThread ? (message as ThreadMessage).objectClass : message.attachedToClass
|
||||||
|
|
||||||
if (!hierarchy.isDerived(docClass, chunter.class.ChunterSpace)) {
|
if (!hierarchy.isDerived(docClass, chunter.class.ChunterSpace)) {
|
||||||
return []
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (docClass === chunter.class.DirectMessage) {
|
if (docClass === chunter.class.DirectMessage) {
|
||||||
@ -270,6 +273,7 @@ export async function OnMessageSend (
|
|||||||
if (docClass === analyticsCollector.class.OnboardingChannel) {
|
if (docClass === analyticsCollector.class.OnboardingChannel) {
|
||||||
await onSupportWorkspaceMessage(control, message)
|
await onSupportWorkspaceMessage(control, message)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,8 @@ import core, {
|
|||||||
TxProcessor,
|
TxProcessor,
|
||||||
TxRemoveDoc,
|
TxRemoveDoc,
|
||||||
TxUpdateDoc,
|
TxUpdateDoc,
|
||||||
UserStatus
|
UserStatus,
|
||||||
|
type MeasureContext
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import notification, { DocNotifyContext, NotificationContent } from '@hcengineering/notification'
|
import notification, { DocNotifyContext, NotificationContent } from '@hcengineering/notification'
|
||||||
import { getMetadata, IntlString, translate } from '@hcengineering/platform'
|
import { getMetadata, IntlString, translate } from '@hcengineering/platform'
|
||||||
@ -151,7 +152,7 @@ async function OnThreadMessageCreated (originTx: TxCUD<Doc>, control: TriggerCon
|
|||||||
return [lastReplyTx, repliedPersonTx]
|
return [lastReplyTx, repliedPersonTx]
|
||||||
}
|
}
|
||||||
|
|
||||||
async function OnChatMessageCreated (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
async function OnChatMessageCreated (ctx: MeasureContext, tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
||||||
const hierarchy = control.hierarchy
|
const hierarchy = control.hierarchy
|
||||||
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<ChatMessage>
|
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<ChatMessage>
|
||||||
|
|
||||||
@ -163,9 +164,7 @@ async function OnChatMessageCreated (tx: TxCUD<Doc>, control: TriggerControl): P
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetDoc = (
|
const targetDoc = (await control.findAll(ctx, message.attachedToClass, { _id: message.attachedTo }, { limit: 1 }))[0]
|
||||||
await control.findAll(control.ctx, message.attachedToClass, { _id: message.attachedTo }, { limit: 1 })
|
|
||||||
)[0]
|
|
||||||
if (targetDoc === undefined) {
|
if (targetDoc === undefined) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@ -190,7 +189,7 @@ async function OnChatMessageCreated (tx: TxCUD<Doc>, control: TriggerControl): P
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const collaborators = await getDocCollaborators(control.ctx, targetDoc, mixin, control)
|
const collaborators = await getDocCollaborators(ctx, targetDoc, mixin, control)
|
||||||
if (!collaborators.includes(message.modifiedBy)) {
|
if (!collaborators.includes(message.modifiedBy)) {
|
||||||
collaborators.push(message.modifiedBy)
|
collaborators.push(message.modifiedBy)
|
||||||
}
|
}
|
||||||
@ -272,33 +271,19 @@ export async function ChunterTrigger (tx: TxCUD<Doc>, control: TriggerControl):
|
|||||||
actualTx._class === core.class.TxCreateDoc &&
|
actualTx._class === core.class.TxCreateDoc &&
|
||||||
control.hierarchy.isDerived(actualTx.objectClass, chunter.class.ThreadMessage)
|
control.hierarchy.isDerived(actualTx.objectClass, chunter.class.ThreadMessage)
|
||||||
) {
|
) {
|
||||||
res.push(
|
res.push(...(await control.ctx.with('OnThreadMessageCreated', {}, (ctx) => OnThreadMessageCreated(tx, control))))
|
||||||
...(await control.ctx.with(
|
|
||||||
'OnThreadMessageCreated',
|
|
||||||
{},
|
|
||||||
async (ctx) => await OnThreadMessageCreated(tx, control)
|
|
||||||
))
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
actualTx._class === core.class.TxRemoveDoc &&
|
actualTx._class === core.class.TxRemoveDoc &&
|
||||||
control.hierarchy.isDerived(actualTx.objectClass, chunter.class.ThreadMessage)
|
control.hierarchy.isDerived(actualTx.objectClass, chunter.class.ThreadMessage)
|
||||||
) {
|
) {
|
||||||
res.push(
|
res.push(...(await control.ctx.with('OnThreadMessageDeleted', {}, (ctx) => OnThreadMessageDeleted(tx, control))))
|
||||||
...(await control.ctx.with(
|
|
||||||
'OnThreadMessageDeleted',
|
|
||||||
{},
|
|
||||||
async (ctx) => await OnThreadMessageDeleted(tx, control)
|
|
||||||
))
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
actualTx._class === core.class.TxCreateDoc &&
|
actualTx._class === core.class.TxCreateDoc &&
|
||||||
control.hierarchy.isDerived(actualTx.objectClass, chunter.class.ChatMessage)
|
control.hierarchy.isDerived(actualTx.objectClass, chunter.class.ChatMessage)
|
||||||
) {
|
) {
|
||||||
res.push(
|
res.push(...(await control.ctx.with('OnChatMessageCreated', {}, (ctx) => OnChatMessageCreated(ctx, tx, control))))
|
||||||
...(await control.ctx.with('OnChatMessageCreated', {}, async (ctx) => await OnChatMessageCreated(tx, control)))
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -13,28 +13,26 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import { removeCollaborativeDoc } from '@hcengineering/collaboration'
|
||||||
import type { CollaborativeDoc, Doc, Tx, TxRemoveDoc } from '@hcengineering/core'
|
import type { CollaborativeDoc, Doc, Tx, TxRemoveDoc } from '@hcengineering/core'
|
||||||
import core, { TxProcessor } from '@hcengineering/core'
|
import core, { TxProcessor } from '@hcengineering/core'
|
||||||
import { removeCollaborativeDoc } from '@hcengineering/collaboration'
|
|
||||||
import { type TriggerControl } from '@hcengineering/server-core'
|
import { type TriggerControl } from '@hcengineering/server-core'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export async function OnDelete (
|
export async function OnDelete (
|
||||||
tx: Tx,
|
txes: Tx[],
|
||||||
{ hierarchy, storageAdapter, workspace, removedMap, ctx }: TriggerControl
|
{ hierarchy, storageAdapter, workspace, removedMap, ctx }: TriggerControl
|
||||||
): Promise<Tx[]> {
|
): Promise<Tx[]> {
|
||||||
const rmTx = TxProcessor.extractTx(tx) as TxRemoveDoc<Doc>
|
const ltxes = txes
|
||||||
|
.map((it) => TxProcessor.extractTx(it))
|
||||||
if (rmTx._class !== core.class.TxRemoveDoc) {
|
.filter((it) => it._class === core.class.TxRemoveDoc) as TxRemoveDoc<Doc>[]
|
||||||
return []
|
for (const rmTx of ltxes) {
|
||||||
}
|
|
||||||
|
|
||||||
// Obtain document being deleted
|
// Obtain document being deleted
|
||||||
const doc = removedMap.get(rmTx.objectId)
|
const doc = removedMap.get(rmTx.objectId)
|
||||||
if (doc === undefined) {
|
if (doc === undefined) {
|
||||||
return []
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ids of files to delete from storage
|
// Ids of files to delete from storage
|
||||||
@ -54,7 +52,7 @@ export async function OnDelete (
|
|||||||
// Even though we are deleting it here, the document can be currently in use by someone else
|
// Even though we are deleting it here, the document can be currently in use by someone else
|
||||||
// and when editing session ends, the collborator service will recreate the document again
|
// and when editing session ends, the collborator service will recreate the document again
|
||||||
await removeCollaborativeDoc(storageAdapter, workspace, toDelete, ctx)
|
await removeCollaborativeDoc(storageAdapter, workspace, toDelete, ctx)
|
||||||
|
}
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1001,9 +1001,10 @@ export async function createCollabDocInfo (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const settings = await getNotificationProviderControl(ctx, control)
|
const settings = await getNotificationProviderControl(ctx, control)
|
||||||
const subscriptions = await control.findAll(ctx, notification.class.PushSubscription, {
|
const subscriptions = (await control.queryFind(ctx, notification.class.PushSubscription, {})).filter((it) =>
|
||||||
user: { $in: Array.from(targets) }
|
targets.has(it.user as Ref<PersonAccount>)
|
||||||
})
|
)
|
||||||
|
|
||||||
for (const target of targets) {
|
for (const target of targets) {
|
||||||
const info: ReceiverInfo | undefined = toReceiverInfo(control.hierarchy, usersInfo.get(target))
|
const info: ReceiverInfo | undefined = toReceiverInfo(control.hierarchy, usersInfo.get(target))
|
||||||
|
|
||||||
@ -1938,13 +1939,12 @@ async function OnEmployeeDeactivate (tx: TxCUD<Doc>, control: TriggerControl): P
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
async function OnDocRemove (originTx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
async function OnDocRemove (txes: TxCUD<Doc>[], control: TriggerControl): Promise<Tx[]> {
|
||||||
const tx = TxProcessor.extractTx(originTx) as TxRemoveDoc<Doc>
|
const ltxes = txes
|
||||||
|
.map((it) => TxProcessor.extractTx(it))
|
||||||
if (tx._class !== core.class.TxRemoveDoc) return []
|
.filter((it) => it._class === core.class.TxRemoveDoc) as TxRemoveDoc<Doc>[]
|
||||||
|
|
||||||
const res: Tx[] = []
|
const res: Tx[] = []
|
||||||
|
for (const tx of ltxes) {
|
||||||
if (control.hierarchy.isDerived(tx.objectClass, activity.class.ActivityMessage)) {
|
if (control.hierarchy.isDerived(tx.objectClass, activity.class.ActivityMessage)) {
|
||||||
const message = control.removedMap.get(tx.objectId) as ActivityMessage | undefined
|
const message = control.removedMap.get(tx.objectId) as ActivityMessage | undefined
|
||||||
|
|
||||||
@ -1962,12 +1962,11 @@ async function OnDocRemove (originTx: TxCUD<Doc>, control: TriggerControl): Prom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await removeContextNotifications(control, [tx.objectId as Ref<DocNotifyContext>])
|
res.push(...(await removeContextNotifications(control, [tx.objectId as Ref<DocNotifyContext>])))
|
||||||
}
|
}
|
||||||
|
|
||||||
const txes = await removeCollaboratorDoc(tx, control)
|
res.push(...(await removeCollaboratorDoc(tx, control)))
|
||||||
|
}
|
||||||
res.push(...txes)
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,55 +13,51 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import { DocUpdateMessage } from '@hcengineering/activity'
|
||||||
|
import { PersonAccount } from '@hcengineering/contact'
|
||||||
import core, {
|
import core, {
|
||||||
Doc,
|
Doc,
|
||||||
|
Ref,
|
||||||
Tx,
|
Tx,
|
||||||
TxCUD,
|
TxCUD,
|
||||||
TxCollectionCUD,
|
TxCollectionCUD,
|
||||||
TxCreateDoc,
|
TxCreateDoc,
|
||||||
TxUpdateDoc,
|
|
||||||
TxProcessor,
|
TxProcessor,
|
||||||
Ref,
|
TxUpdateDoc,
|
||||||
type MeasureContext
|
type MeasureContext
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import request, { Request, RequestStatus } from '@hcengineering/request'
|
|
||||||
import { getResource, translate } from '@hcengineering/platform'
|
|
||||||
import type { TriggerControl } from '@hcengineering/server-core'
|
|
||||||
import { pushDocUpdateMessages } from '@hcengineering/server-activity-resources'
|
|
||||||
import { DocUpdateMessage } from '@hcengineering/activity'
|
|
||||||
import notification from '@hcengineering/notification'
|
import notification from '@hcengineering/notification'
|
||||||
|
import { getResource, translate } from '@hcengineering/platform'
|
||||||
|
import request, { Request, RequestStatus } from '@hcengineering/request'
|
||||||
|
import { pushDocUpdateMessages } from '@hcengineering/server-activity-resources'
|
||||||
|
import type { TriggerControl } from '@hcengineering/server-core'
|
||||||
import {
|
import {
|
||||||
getNotificationTxes,
|
|
||||||
getCollaborators,
|
getCollaborators,
|
||||||
|
getNotificationProviderControl,
|
||||||
|
getNotificationTxes,
|
||||||
getTextPresenter,
|
getTextPresenter,
|
||||||
getUsersInfo,
|
getUsersInfo,
|
||||||
toReceiverInfo,
|
toReceiverInfo
|
||||||
getNotificationProviderControl
|
|
||||||
} from '@hcengineering/server-notification-resources'
|
} from '@hcengineering/server-notification-resources'
|
||||||
import { PersonAccount } from '@hcengineering/contact'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export async function OnRequest (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
export async function OnRequest (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||||
if (tx._class !== core.class.TxCollectionCUD) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const hierarchy = control.hierarchy
|
const hierarchy = control.hierarchy
|
||||||
const ptx = tx as TxCollectionCUD<Doc, Request>
|
const ltxes = (
|
||||||
|
txes.filter((it) => it._class === core.class.TxCollectionCUD) as TxCollectionCUD<Doc, Request>[]
|
||||||
if (!hierarchy.isDerived(ptx.tx.objectClass, request.class.Request)) {
|
).filter((it) => hierarchy.isDerived(it.tx.objectClass, request.class.Request))
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
let res: Tx[] = []
|
let res: Tx[] = []
|
||||||
|
|
||||||
|
for (const ptx of ltxes) {
|
||||||
res = res.concat(await getRequestNotificationTx(control.ctx, ptx, control))
|
res = res.concat(await getRequestNotificationTx(control.ctx, ptx, control))
|
||||||
|
|
||||||
if (ptx.tx._class === core.class.TxUpdateDoc) {
|
if (ptx.tx._class === core.class.TxUpdateDoc) {
|
||||||
res = res.concat(await OnRequestUpdate(ptx, control))
|
res = res.concat(await OnRequestUpdate(ptx, control))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
@ -168,9 +164,10 @@ async function getRequestNotificationTx (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const notificationControl = await getNotificationProviderControl(ctx, control)
|
const notificationControl = await getNotificationProviderControl(ctx, control)
|
||||||
const subscriptions = await control.findAll(control.ctx, notification.class.PushSubscription, {
|
const collaboratorsSet = new Set(collaborators)
|
||||||
user: { $in: collaborators }
|
const subscriptions = (await control.queryFind(control.ctx, notification.class.PushSubscription, {})).filter((it) =>
|
||||||
})
|
collaboratorsSet.has(it.user)
|
||||||
|
)
|
||||||
for (const target of collaborators) {
|
for (const target of collaborators) {
|
||||||
const targetInfo = toReceiverInfo(control.hierarchy, usersInfo.get(target))
|
const targetInfo = toReceiverInfo(control.hierarchy, usersInfo.get(target))
|
||||||
if (targetInfo === undefined) continue
|
if (targetInfo === undefined) continue
|
||||||
|
@ -49,7 +49,9 @@ export async function TagElementRemove (
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export async function onTagReference (tx: 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)
|
const actualTx = TxProcessor.extractTx(tx)
|
||||||
const isCreate = control.hierarchy.isDerived(actualTx._class, core.class.TxCreateDoc)
|
const isCreate = control.hierarchy.isDerived(actualTx._class, core.class.TxCreateDoc)
|
||||||
const isRemove = control.hierarchy.isDerived(actualTx._class, core.class.TxRemoveDoc)
|
const isRemove = control.hierarchy.isDerived(actualTx._class, core.class.TxRemoveDoc)
|
||||||
@ -57,24 +59,27 @@ export async function onTagReference (tx: Tx, control: TriggerControl): Promise<
|
|||||||
if (!control.hierarchy.isDerived((actualTx as TxCUD<Doc>).objectClass, tags.class.TagReference)) return []
|
if (!control.hierarchy.isDerived((actualTx as TxCUD<Doc>).objectClass, tags.class.TagReference)) return []
|
||||||
if (isCreate) {
|
if (isCreate) {
|
||||||
const doc = TxProcessor.createDoc2Doc(actualTx as TxCreateDoc<TagReference>)
|
const doc = TxProcessor.createDoc2Doc(actualTx as TxCreateDoc<TagReference>)
|
||||||
const res = control.txFactory.createTxUpdateDoc(tags.class.TagElement, core.space.Workspace, doc.tag, {
|
result.push(
|
||||||
|
control.txFactory.createTxUpdateDoc(tags.class.TagElement, core.space.Workspace, doc.tag, {
|
||||||
$inc: { refCount: 1 }
|
$inc: { refCount: 1 }
|
||||||
})
|
})
|
||||||
return [res]
|
)
|
||||||
}
|
}
|
||||||
if (isRemove) {
|
if (isRemove) {
|
||||||
const ctx = actualTx as TxRemoveDoc<TagReference>
|
const ctx = actualTx as TxRemoveDoc<TagReference>
|
||||||
const doc = control.removedMap.get(ctx.objectId) as TagReference
|
const doc = control.removedMap.get(ctx.objectId) as TagReference
|
||||||
if (doc !== undefined) {
|
if (doc !== undefined) {
|
||||||
if (!control.removedMap.has(doc.tag)) {
|
if (!control.removedMap.has(doc.tag)) {
|
||||||
const res = control.txFactory.createTxUpdateDoc(tags.class.TagElement, core.space.Workspace, doc.tag, {
|
result.push(
|
||||||
|
control.txFactory.createTxUpdateDoc(tags.class.TagElement, core.space.Workspace, doc.tag, {
|
||||||
$inc: { refCount: -1 }
|
$inc: { refCount: -1 }
|
||||||
})
|
})
|
||||||
return [res]
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
|
@ -20,31 +20,34 @@ import task, { Task } from '@hcengineering/task'
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export async function OnStateUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
export async function OnStateUpdate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||||
|
const result: Tx[] = []
|
||||||
|
for (const tx of txes) {
|
||||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
const actualTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
||||||
if (!control.hierarchy.isDerived(actualTx.objectClass, task.class.Task)) return []
|
if (!control.hierarchy.isDerived(actualTx.objectClass, task.class.Task)) return []
|
||||||
if (actualTx._class === core.class.TxCreateDoc) {
|
if (actualTx._class === core.class.TxCreateDoc) {
|
||||||
const doc = TxProcessor.createDoc2Doc(actualTx as TxCreateDoc<Task>)
|
const doc = TxProcessor.createDoc2Doc(actualTx as TxCreateDoc<Task>)
|
||||||
const status = (await control.modelDb.findAll(core.class.Status, { _id: doc.status }))[0]
|
const status = control.modelDb.findAllSync(core.class.Status, { _id: doc.status })[0]
|
||||||
if (status?.category === task.statusCategory.Lost || status?.category === task.statusCategory.Won) {
|
if (status?.category === task.statusCategory.Lost || status?.category === task.statusCategory.Won) {
|
||||||
return [control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, { isDone: true })]
|
result.push(control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, { isDone: true }))
|
||||||
}
|
}
|
||||||
} else if (actualTx._class === core.class.TxUpdateDoc) {
|
} else if (actualTx._class === core.class.TxUpdateDoc) {
|
||||||
const updateTx = actualTx as TxUpdateDoc<Task>
|
const updateTx = actualTx as TxUpdateDoc<Task>
|
||||||
if (updateTx.operations.status !== undefined) {
|
if (updateTx.operations.status !== undefined) {
|
||||||
const status = (await control.modelDb.findAll(core.class.Status, { _id: updateTx.operations.status }))[0]
|
const status = control.modelDb.findAllSync(core.class.Status, { _id: updateTx.operations.status })[0]
|
||||||
if (status?.category === task.statusCategory.Lost || status?.category === task.statusCategory.Won) {
|
if (status?.category === task.statusCategory.Lost || status?.category === task.statusCategory.Won) {
|
||||||
return [
|
result.push(
|
||||||
control.txFactory.createTxUpdateDoc(updateTx.objectClass, updateTx.objectSpace, updateTx.objectId, {
|
control.txFactory.createTxUpdateDoc(updateTx.objectClass, updateTx.objectSpace, updateTx.objectId, {
|
||||||
isDone: true
|
isDone: true
|
||||||
})
|
})
|
||||||
]
|
)
|
||||||
} else {
|
} else {
|
||||||
return [
|
result.push(
|
||||||
control.txFactory.createTxUpdateDoc(updateTx.objectClass, updateTx.objectSpace, updateTx.objectId, {
|
control.txFactory.createTxUpdateDoc(updateTx.objectClass, updateTx.objectSpace, updateTx.objectId, {
|
||||||
isDone: false
|
isDone: false
|
||||||
})
|
})
|
||||||
]
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import activity, { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity'
|
||||||
|
import chunter, { ChatMessage } from '@hcengineering/chunter'
|
||||||
import contact, { Channel, ChannelProvider, Contact, Employee, formatName, PersonAccount } from '@hcengineering/contact'
|
import contact, { Channel, ChannelProvider, Contact, Employee, formatName, PersonAccount } from '@hcengineering/contact'
|
||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
@ -29,27 +31,25 @@ import {
|
|||||||
TxCreateDoc,
|
TxCreateDoc,
|
||||||
TxProcessor
|
TxProcessor
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { TriggerControl } from '@hcengineering/server-core'
|
|
||||||
import notification, {
|
import notification, {
|
||||||
BaseNotificationType,
|
BaseNotificationType,
|
||||||
InboxNotification,
|
InboxNotification,
|
||||||
MentionInboxNotification,
|
MentionInboxNotification,
|
||||||
NotificationType
|
NotificationType
|
||||||
} from '@hcengineering/notification'
|
} from '@hcengineering/notification'
|
||||||
import telegram, { TelegramMessage, TelegramNotificationRequest } from '@hcengineering/telegram'
|
|
||||||
import setting, { Integration } from '@hcengineering/setting'
|
|
||||||
import { NotificationProviderFunc, ReceiverInfo, SenderInfo } from '@hcengineering/server-notification'
|
|
||||||
import { getMetadata, getResource, translate } from '@hcengineering/platform'
|
import { getMetadata, getResource, translate } from '@hcengineering/platform'
|
||||||
import serverTelegram from '@hcengineering/server-telegram'
|
import { TriggerControl } from '@hcengineering/server-core'
|
||||||
|
import { NotificationProviderFunc, ReceiverInfo, SenderInfo } from '@hcengineering/server-notification'
|
||||||
import {
|
import {
|
||||||
getTranslatedNotificationContent,
|
getNotificationLink,
|
||||||
getTextPresenter,
|
getTextPresenter,
|
||||||
getNotificationLink
|
getTranslatedNotificationContent
|
||||||
} from '@hcengineering/server-notification-resources'
|
} from '@hcengineering/server-notification-resources'
|
||||||
|
import serverTelegram from '@hcengineering/server-telegram'
|
||||||
import { generateToken } from '@hcengineering/server-token'
|
import { generateToken } from '@hcengineering/server-token'
|
||||||
import chunter, { ChatMessage } from '@hcengineering/chunter'
|
import setting, { Integration } from '@hcengineering/setting'
|
||||||
|
import telegram, { TelegramMessage, TelegramNotificationRequest } from '@hcengineering/telegram'
|
||||||
import { markupToHTML } from '@hcengineering/text'
|
import { markupToHTML } from '@hcengineering/text'
|
||||||
import activity, { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -245,6 +245,7 @@ function hasAttachments (doc: ActivityMessage | undefined, hierarchy: Hierarchy)
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const telegramNotificationKey = 'telegram.notification.reported'
|
||||||
const SendTelegramNotifications: NotificationProviderFunc = async (
|
const SendTelegramNotifications: NotificationProviderFunc = async (
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
types: BaseNotificationType[],
|
types: BaseNotificationType[],
|
||||||
@ -261,7 +262,11 @@ const SendTelegramNotifications: NotificationProviderFunc = async (
|
|||||||
const botUrl = getMetadata(serverTelegram.metadata.BotUrl)
|
const botUrl = getMetadata(serverTelegram.metadata.BotUrl)
|
||||||
|
|
||||||
if (botUrl === undefined || botUrl === '') {
|
if (botUrl === undefined || botUrl === '') {
|
||||||
|
const reported = control.cache.get(telegramNotificationKey)
|
||||||
|
if (reported === undefined) {
|
||||||
control.ctx.error('Please provide telegram bot service url to enable telegram notifications.')
|
control.ctx.error('Please provide telegram bot service url to enable telegram notifications.')
|
||||||
|
control.cache.set(telegramNotificationKey, true)
|
||||||
|
}
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,9 @@ import tracker, { Issue, IssueStatus, Project, TimeSpendReport } from '@hcengine
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export async function OnTask (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
export async function OnTask (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||||
|
const result: Tx[] = []
|
||||||
|
for (const tx of txes) {
|
||||||
const actualTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
const actualTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
||||||
const mixin = control.hierarchy.classHierarchyMixin<Class<Doc>, ToDoFactory>(
|
const mixin = control.hierarchy.classHierarchyMixin<Class<Doc>, ToDoFactory>(
|
||||||
actualTx.objectClass,
|
actualTx.objectClass,
|
||||||
@ -59,14 +61,15 @@ export async function OnTask (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
|||||||
if (mixin !== undefined) {
|
if (mixin !== undefined) {
|
||||||
if (actualTx._class !== core.class.TxRemoveDoc) {
|
if (actualTx._class !== core.class.TxRemoveDoc) {
|
||||||
const factory = await getResource(mixin.factory)
|
const factory = await getResource(mixin.factory)
|
||||||
return await factory(tx, control)
|
result.push(...(await factory(tx, control)))
|
||||||
} else {
|
} else {
|
||||||
const todos = await control.findAll(control.ctx, time.class.ToDo, { attachedTo: actualTx.objectId })
|
const todos = await control.findAll(control.ctx, time.class.ToDo, { attachedTo: actualTx.objectId })
|
||||||
return todos.map((p) => control.txFactory.createTxRemoveDoc(p._class, p.space, p._id))
|
result.push(...todos.map((p) => control.txFactory.createTxRemoveDoc(p._class, p.space, p._id)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return []
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function OnWorkSlotUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
export async function OnWorkSlotUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||||
|
@ -169,6 +169,23 @@ export class DbAdapterManagerImpl implements DBAdapterManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAdapterName (domain: Domain): string {
|
||||||
|
const adapterName = this.conf.domains[domain]
|
||||||
|
return adapterName ?? this.conf.defaultAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
getAdapterByName (name: string): DbAdapter {
|
||||||
|
if (name === this.conf.defaultAdapter) {
|
||||||
|
return this.defaultAdapter
|
||||||
|
}
|
||||||
|
const adapter = this.adapters.get(name) ?? this.defaultAdapter
|
||||||
|
if (adapter === undefined) {
|
||||||
|
throw new Error('adapter not provided: ' + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return adapter
|
||||||
|
}
|
||||||
|
|
||||||
public getAdapter (domain: Domain, requireExists: boolean): DbAdapter {
|
public getAdapter (domain: Domain, requireExists: boolean): DbAdapter {
|
||||||
const name = this.conf.domains[domain] ?? '#default'
|
const name = this.conf.domains[domain] ?? '#default'
|
||||||
const adapter = this.adapters.get(name) ?? this.defaultAdapter
|
const adapter = this.adapters.get(name) ?? this.defaultAdapter
|
||||||
|
@ -126,6 +126,7 @@ export function initStatisticsContext (
|
|||||||
workspaces: ops?.getUsers?.()
|
workspaces: ops?.getUsers?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
void fetch(
|
void fetch(
|
||||||
concatLink(statsUrl, '/api/v1/statistics') + `/?token=${encodeURIComponent(token)}&name=${serviceId}`,
|
concatLink(statsUrl, '/api/v1/statistics') + `/?token=${encodeURIComponent(token)}&name=${serviceId}`,
|
||||||
{
|
{
|
||||||
@ -141,6 +142,12 @@ export function initStatisticsContext (
|
|||||||
console.error(err)
|
console.error(err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
} catch (err: any) {
|
||||||
|
errorToSend++
|
||||||
|
if (errorToSend % 20 === 0) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, METRICS_UPDATE_INTERVAL)
|
}, METRICS_UPDATE_INTERVAL)
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
import core, {
|
import core, {
|
||||||
TxFactory,
|
TxFactory,
|
||||||
TxProcessor,
|
TxProcessor,
|
||||||
generateId,
|
|
||||||
groupByArray,
|
groupByArray,
|
||||||
matchQuery,
|
matchQuery,
|
||||||
type Class,
|
type Class,
|
||||||
@ -134,7 +133,6 @@ export class Triggers {
|
|||||||
mode: 'sync' | 'async'
|
mode: 'sync' | 'async'
|
||||||
): Promise<Tx[]> {
|
): Promise<Tx[]> {
|
||||||
const result: Tx[] = []
|
const result: Tx[] = []
|
||||||
|
|
||||||
for (const { query, trigger, arrays } of this.triggers) {
|
for (const { query, trigger, arrays } of this.triggers) {
|
||||||
if ((trigger.isAsync ? 'async' : 'sync') !== mode) {
|
if ((trigger.isAsync ? 'async' : 'sync') !== mode) {
|
||||||
continue
|
continue
|
||||||
@ -150,19 +148,14 @@ export class Triggers {
|
|||||||
trigger.resource,
|
trigger.resource,
|
||||||
{},
|
{},
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
if (mode === 'async') {
|
|
||||||
ctx.id = generateId()
|
|
||||||
}
|
|
||||||
const tresult = await this.applyTrigger(ctx, ctrl, matches, { trigger, arrays })
|
const tresult = await this.applyTrigger(ctx, ctrl, matches, { trigger, arrays })
|
||||||
result.push(...tresult)
|
result.push(...tresult)
|
||||||
if (ctx.onEnd !== undefined && mode === 'async') {
|
|
||||||
await ctx.onEnd(ctx)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{ count: matches.length, arrays }
|
{ count: matches.length, arrays }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,6 +141,9 @@ export type TxMiddlewareResult = TxResult
|
|||||||
export interface DBAdapterManager {
|
export interface DBAdapterManager {
|
||||||
getAdapter: (domain: Domain, requireExists: boolean) => DbAdapter
|
getAdapter: (domain: Domain, requireExists: boolean) => DbAdapter
|
||||||
|
|
||||||
|
getAdapterName: (domain: Domain) => string
|
||||||
|
getAdapterByName: (name: string, requireExists: boolean) => DbAdapter
|
||||||
|
|
||||||
getDefaultAdapter: () => DbAdapter
|
getDefaultAdapter: () => DbAdapter
|
||||||
|
|
||||||
close: () => Promise<void>
|
close: () => Promise<void>
|
||||||
|
@ -18,7 +18,6 @@ import type { ContentTextAdapterConfiguration, FullTextAdapterFactory } from '@h
|
|||||||
export * from './fulltext'
|
export * from './fulltext'
|
||||||
export * from './indexer'
|
export * from './indexer'
|
||||||
export * from './rekoni'
|
export * from './rekoni'
|
||||||
export * from './ydoc'
|
|
||||||
|
|
||||||
export interface FulltextDBConfiguration {
|
export interface FulltextDBConfiguration {
|
||||||
fulltextAdapter: {
|
fulltextAdapter: {
|
||||||
|
@ -63,7 +63,7 @@ import type {
|
|||||||
StorageAdapter
|
StorageAdapter
|
||||||
} from '@hcengineering/server-core'
|
} from '@hcengineering/server-core'
|
||||||
import { RateLimiter, SessionDataImpl } from '@hcengineering/server-core'
|
import { RateLimiter, SessionDataImpl } from '@hcengineering/server-core'
|
||||||
import { jsonToText, markupToJSON } from '@hcengineering/text'
|
import { jsonToText, markupToJSON, pmNodeToText, yDocContentToNodes } from '@hcengineering/text'
|
||||||
import { findSearchPresenter, updateDocWithPresenter } from '../mapper'
|
import { findSearchPresenter, updateDocWithPresenter } from '../mapper'
|
||||||
import { type FullTextPipeline } from './types'
|
import { type FullTextPipeline } from './types'
|
||||||
import { createIndexedDoc, createStateDoc, getContent } from './utils'
|
import { createIndexedDoc, createStateDoc, getContent } from './utils'
|
||||||
@ -786,12 +786,21 @@ export class FullTextIndexPipeline implements FullTextPipeline {
|
|||||||
if (collaborativeDoc !== undefined && collaborativeDoc !== '') {
|
if (collaborativeDoc !== undefined && collaborativeDoc !== '') {
|
||||||
const { documentId } = collaborativeDocParse(collaborativeDoc)
|
const { documentId } = collaborativeDocParse(collaborativeDoc)
|
||||||
|
|
||||||
const docInfo: Blob | undefined = await this.storageAdapter?.stat(ctx, this.workspace, documentId)
|
|
||||||
try {
|
try {
|
||||||
await this.handleBlob(ctx, docInfo, indexedDoc)
|
const readable = await this.storageAdapter?.read(ctx, this.workspace, documentId)
|
||||||
|
const nodes = yDocContentToNodes(Buffer.concat(readable as any))
|
||||||
|
let textContent = nodes.map(pmNodeToText).join('\n')
|
||||||
|
textContent = textContent
|
||||||
|
.split(/ +|\t+|\f+/)
|
||||||
|
.filter((it) => it)
|
||||||
|
.join(' ')
|
||||||
|
.split(/\n\n+/)
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
indexedDoc.fulltextSummary += '\n' + textContent
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
Analytics.handleError(err)
|
Analytics.handleError(err)
|
||||||
ctx.error('failed to handle blob', { _id: docInfo?._id, workspace: this.workspace.name })
|
ctx.error('failed to handle blob', { _id: documentId, workspace: this.workspace.name })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
import { type MeasureContext, type WorkspaceId } from '@hcengineering/core'
|
|
||||||
import { type ContentTextAdapter } from '@hcengineering/server-core'
|
|
||||||
import { pmNodeToText, yDocContentToNodes } from '@hcengineering/text'
|
|
||||||
import { Buffer } from 'node:buffer'
|
|
||||||
import { Readable } from 'node:stream'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
export async function createYDocAdapter (_url: string): Promise<ContentTextAdapter> {
|
|
||||||
return {
|
|
||||||
content: async (
|
|
||||||
ctx: MeasureContext,
|
|
||||||
workspace: WorkspaceId,
|
|
||||||
_name: string,
|
|
||||||
_type: string,
|
|
||||||
data: Readable | Buffer | string
|
|
||||||
): Promise<string> => {
|
|
||||||
const chunks: any[] = []
|
|
||||||
|
|
||||||
if (data instanceof Readable) {
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
data.on('readable', () => {
|
|
||||||
let chunk: any
|
|
||||||
while ((chunk = data.read()) !== null) {
|
|
||||||
const b = chunk as Buffer
|
|
||||||
chunks.push(b)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
data.on('end', () => {
|
|
||||||
resolve(null)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else if (data instanceof Buffer) {
|
|
||||||
chunks.push(data)
|
|
||||||
} else {
|
|
||||||
console.warn('ydoc content adapter does not support string content')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunks.length > 0) {
|
|
||||||
const nodes = yDocContentToNodes(Buffer.concat(chunks))
|
|
||||||
return nodes.map(pmNodeToText).join('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,9 +14,10 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import core, {
|
import core, {
|
||||||
|
Domain,
|
||||||
|
groupByArray,
|
||||||
TxProcessor,
|
TxProcessor,
|
||||||
type Doc,
|
type Doc,
|
||||||
type Domain,
|
|
||||||
type MeasureContext,
|
type MeasureContext,
|
||||||
type SessionData,
|
type SessionData,
|
||||||
type Tx,
|
type Tx,
|
||||||
@ -24,7 +25,13 @@ import core, {
|
|||||||
type TxResult
|
type TxResult
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import { PlatformError, unknownError } from '@hcengineering/platform'
|
import { PlatformError, unknownError } from '@hcengineering/platform'
|
||||||
import type { DBAdapterManager, Middleware, PipelineContext, TxMiddlewareResult } from '@hcengineering/server-core'
|
import type {
|
||||||
|
DbAdapter,
|
||||||
|
DBAdapterManager,
|
||||||
|
Middleware,
|
||||||
|
PipelineContext,
|
||||||
|
TxMiddlewareResult
|
||||||
|
} from '@hcengineering/server-core'
|
||||||
import { BaseMiddleware } from '@hcengineering/server-core'
|
import { BaseMiddleware } from '@hcengineering/server-core'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,32 +73,41 @@ export class DomainTxMiddleware extends BaseMiddleware implements Middleware {
|
|||||||
private async routeTx (ctx: MeasureContext<SessionData>, txes: Tx[]): Promise<TxResult[]> {
|
private async routeTx (ctx: MeasureContext<SessionData>, txes: Tx[]): Promise<TxResult[]> {
|
||||||
const result: TxResult[] = []
|
const result: TxResult[] = []
|
||||||
|
|
||||||
const domainGroups = new Map<Domain, TxCUD<Doc>[]>()
|
const adapterGroups = new Map<string, TxCUD<Doc>[]>()
|
||||||
|
|
||||||
const routeToAdapter = async (domain: Domain, txes: TxCUD<Doc>[]): Promise<void> => {
|
const routeToAdapter = async (adapter: DbAdapter, txes: TxCUD<Doc>[]): Promise<void> => {
|
||||||
if (txes.length > 0) {
|
if (txes.length > 0) {
|
||||||
// Find all deleted documents
|
// Find all deleted documents
|
||||||
|
const toDelete = txes.filter((it) => it._class === core.class.TxRemoveDoc)
|
||||||
const adapter = this.adapterManager.getAdapter(domain, true)
|
|
||||||
const toDelete = txes.filter((it) => it._class === core.class.TxRemoveDoc).map((it) => it.objectId)
|
|
||||||
|
|
||||||
if (toDelete.length > 0) {
|
if (toDelete.length > 0) {
|
||||||
const toDeleteDocs = await ctx.with(
|
const deleteByDomain = groupByArray(toDelete, (it) => this.context.hierarchy.getDomain(it.objectClass))
|
||||||
|
|
||||||
|
for (const [d, docs] of deleteByDomain.entries()) {
|
||||||
|
const todel = await ctx.with(
|
||||||
'adapter-load',
|
'adapter-load',
|
||||||
{ domain },
|
{},
|
||||||
async () => await adapter.load(ctx, domain, toDelete),
|
() =>
|
||||||
|
adapter.load(
|
||||||
|
ctx,
|
||||||
|
d,
|
||||||
|
docs.map((it) => it._id)
|
||||||
|
),
|
||||||
{ count: toDelete.length }
|
{ count: toDelete.length }
|
||||||
)
|
)
|
||||||
|
|
||||||
for (const ddoc of toDeleteDocs) {
|
for (const ddoc of todel) {
|
||||||
ctx.contextData.removedMap.set(ddoc._id, ddoc)
|
ctx.contextData.removedMap.set(ddoc._id, ddoc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const r = await ctx.with('adapter-tx', { domain }, (ctx) => adapter.tx(ctx, ...txes), {
|
const classes = Array.from(new Set(txes.map((it) => it.objectClass)))
|
||||||
|
const _classes = Array.from(new Set(txes.map((it) => it._class)))
|
||||||
|
const r = await ctx.with('adapter-tx', {}, (ctx) => adapter.tx(ctx, ...txes), {
|
||||||
txes: txes.length,
|
txes: txes.length,
|
||||||
classes: Array.from(new Set(txes.map((it) => it.objectClass))),
|
classes,
|
||||||
_classes: Array.from(new Set(txes.map((it) => it._class)))
|
_classes
|
||||||
})
|
})
|
||||||
|
|
||||||
if (Array.isArray(r)) {
|
if (Array.isArray(r)) {
|
||||||
@ -102,6 +118,7 @@ export class DomainTxMiddleware extends BaseMiddleware implements Middleware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const domains = new Set<Domain>()
|
||||||
for (const tx of txes) {
|
for (const tx of txes) {
|
||||||
const txCUD = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
const txCUD = TxProcessor.extractTx(tx) as TxCUD<Doc>
|
||||||
if (!TxProcessor.isExtendsCUD(txCUD._class)) {
|
if (!TxProcessor.isExtendsCUD(txCUD._class)) {
|
||||||
@ -110,16 +127,26 @@ export class DomainTxMiddleware extends BaseMiddleware implements Middleware {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const domain = this.context.hierarchy.getDomain(txCUD.objectClass)
|
const domain = this.context.hierarchy.getDomain(txCUD.objectClass)
|
||||||
|
domains.add(domain)
|
||||||
|
const adapterName = this.adapterManager.getAdapterName(domain)
|
||||||
|
|
||||||
let group = domainGroups.get(domain)
|
let group = adapterGroups.get(adapterName)
|
||||||
if (group === undefined) {
|
if (group === undefined) {
|
||||||
group = []
|
group = []
|
||||||
domainGroups.set(domain, group)
|
adapterGroups.set(adapterName, group)
|
||||||
}
|
}
|
||||||
group.push(txCUD)
|
group.push(txCUD)
|
||||||
}
|
}
|
||||||
for (const [domain, txes] of domainGroups.entries()) {
|
|
||||||
await routeToAdapter(domain, txes)
|
// We need to mark domains to set existing
|
||||||
|
for (const d of domains) {
|
||||||
|
// We need to mark adapter
|
||||||
|
this.adapterManager.getAdapter(d, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [adapterName, txes] of adapterGroups.entries()) {
|
||||||
|
const adapter = this.adapterManager.getAdapterByName(adapterName, true)
|
||||||
|
await routeToAdapter(adapter, txes)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -89,9 +89,7 @@ export class LiveQueryMiddleware extends BaseMiddleware implements Middleware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async tx (ctx: MeasureContext, tx: Tx[]): Promise<TxMiddlewareResult> {
|
async tx (ctx: MeasureContext, tx: Tx[]): Promise<TxMiddlewareResult> {
|
||||||
for (const _tx of tx) {
|
await this.liveQuery.tx(...tx)
|
||||||
await this.liveQuery.tx(_tx)
|
|
||||||
}
|
|
||||||
return await this.provideTx(ctx, tx)
|
return await this.provideTx(ctx, tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,9 @@ export class QueryJoiner {
|
|||||||
}
|
}
|
||||||
if (q.result instanceof Promise) {
|
if (q.result instanceof Promise) {
|
||||||
q.result = await q.result
|
q.result = await q.result
|
||||||
q.callbacks--
|
|
||||||
}
|
}
|
||||||
|
q.callbacks--
|
||||||
|
|
||||||
this.removeFromQueue(q)
|
this.removeFromQueue(q)
|
||||||
|
|
||||||
return q.result as FindResult<T>
|
return q.result as FindResult<T>
|
||||||
|
25
server/middleware/src/tests/queryJoiner.spec.ts
Normal file
25
server/middleware/src/tests/queryJoiner.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import core, { MeasureMetricsContext, toFindResult } from '@hcengineering/core'
|
||||||
|
import type { SessionFindAll } from '@hcengineering/server-core'
|
||||||
|
import { QueryJoiner } from '../queryJoin'
|
||||||
|
|
||||||
|
describe('test query joiner', () => {
|
||||||
|
it('test find', async () => {
|
||||||
|
let count = 0
|
||||||
|
const findT: SessionFindAll = async (ctx, _class, query, options) => {
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
count++
|
||||||
|
setTimeout(resolve, 100)
|
||||||
|
})
|
||||||
|
return toFindResult([])
|
||||||
|
}
|
||||||
|
const joiner = new QueryJoiner(findT)
|
||||||
|
const ctx = new MeasureMetricsContext('test', {})
|
||||||
|
|
||||||
|
const p1 = joiner.findAll(ctx, core.class.Class, {})
|
||||||
|
const p2 = joiner.findAll(ctx, core.class.Class, {})
|
||||||
|
await Promise.all([p1, p2])
|
||||||
|
expect(count).toBe(1)
|
||||||
|
expect((joiner as any).queries.size).toBe(1)
|
||||||
|
expect((joiner as any).queries.get(core.class.Class).length).toBe(0)
|
||||||
|
})
|
||||||
|
})
|
@ -51,6 +51,7 @@ import type {
|
|||||||
} from '@hcengineering/server-core'
|
} from '@hcengineering/server-core'
|
||||||
import serverCore, { BaseMiddleware, SessionDataImpl, SessionFindAll, Triggers } from '@hcengineering/server-core'
|
import serverCore, { BaseMiddleware, SessionDataImpl, SessionFindAll, Triggers } from '@hcengineering/server-core'
|
||||||
import { filterBroadcastOnly } from './utils'
|
import { filterBroadcastOnly } from './utils'
|
||||||
|
import { QueryJoiner } from './queryJoin'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -106,13 +107,14 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async processDerived (ctx: MeasureContext<SessionData>, txes: Tx[]): Promise<void> {
|
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
|
const _ctx: MeasureContext = (options as ServerFindOptions<Doc>)?.ctx ?? ctx
|
||||||
delete (options as ServerFindOptions<Doc>)?.ctx
|
delete (options as ServerFindOptions<Doc>)?.ctx
|
||||||
if (_ctx.contextData !== undefined) {
|
if (_ctx.contextData !== undefined) {
|
||||||
_ctx.contextData.isTriggerCtx = true
|
_ctx.contextData.isTriggerCtx = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use live query
|
||||||
const results = await this.findAll(_ctx, _class, query, options)
|
const results = await this.findAll(_ctx, _class, query, options)
|
||||||
return toFindResult(
|
return toFindResult(
|
||||||
results.map((v) => {
|
results.map((v) => {
|
||||||
@ -121,6 +123,12 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
|
|||||||
results.total
|
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 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))
|
const collections = await ctx.with('process-collection', {}, (ctx) => this.processCollection(ctx, txes, findAll))
|
||||||
const moves = await ctx.with('process-move', {}, (ctx) => this.processMove(ctx, txes, findAll))
|
const moves = await ctx.with('process-move', {}, (ctx) => this.processMove(ctx, txes, findAll))
|
||||||
@ -223,6 +231,10 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
|
|||||||
this.context.modelDb
|
this.context.modelDb
|
||||||
)
|
)
|
||||||
ctx.contextData = asyncContextData
|
ctx.contextData = asyncContextData
|
||||||
|
|
||||||
|
if (!((ctx as MeasureContext<SessionDataImpl>).contextData.isAsyncContext ?? false)) {
|
||||||
|
ctx.id = generateId()
|
||||||
|
}
|
||||||
const aresult = await this.triggers.apply(
|
const aresult = await this.triggers.apply(
|
||||||
ctx,
|
ctx,
|
||||||
txes,
|
txes,
|
||||||
@ -236,16 +248,15 @@ export class TriggersMiddleware extends BaseMiddleware implements Middleware {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (aresult.length > 0) {
|
if (aresult.length > 0) {
|
||||||
await ctx.with('process-aync-result', {}, async (ctx) => {
|
await ctx.with('process-async-result', {}, async (ctx) => {
|
||||||
ctx.id = generateId()
|
|
||||||
await this.processDerivedTxes(ctx, aresult)
|
await this.processDerivedTxes(ctx, aresult)
|
||||||
// We need to send all to recipients
|
// We need to send all to recipients
|
||||||
await this.context.head?.handleBroadcast(ctx)
|
await this.context.head?.handleBroadcast(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
if (ctx.onEnd !== undefined) {
|
if (ctx.onEnd !== undefined) {
|
||||||
await ctx.onEnd(ctx)
|
await ctx.onEnd(ctx)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async processDerivedTxes (ctx: MeasureContext<SessionData>, derived: Tx[]): Promise<void> {
|
private async processDerivedTxes (ctx: MeasureContext<SessionData>, derived: Tx[]): Promise<void> {
|
||||||
|
@ -14,6 +14,7 @@ export { serverGithubId } from '@hcengineering/server-github'
|
|||||||
export function createModel (builder: Builder): void {
|
export function createModel (builder: Builder): void {
|
||||||
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
|
||||||
trigger: serverGithub.trigger.OnProjectChanges,
|
trigger: serverGithub.trigger.OnProjectChanges,
|
||||||
|
arrays: true,
|
||||||
isAsync: true
|
isAsync: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -29,17 +29,22 @@ import tracker from '@hcengineering/tracker'
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export async function OnProjectChanges (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
export async function OnProjectChanges (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||||
|
const result: Tx[] = []
|
||||||
|
const cache = new Map<string, any>()
|
||||||
|
|
||||||
|
const toApply: Tx[] = []
|
||||||
|
for (const tx of txes) {
|
||||||
const ltx = TxProcessor.extractTx(tx)
|
const ltx = TxProcessor.extractTx(tx)
|
||||||
|
|
||||||
if (ltx._class === core.class.TxMixin && (ltx as TxMixin<Doc, Doc>).mixin === github.mixin.GithubIssue) {
|
if (ltx._class === core.class.TxMixin && (ltx as TxMixin<Doc, Doc>).mixin === github.mixin.GithubIssue) {
|
||||||
const mix = ltx as TxMixin<Doc, Doc>
|
const mix = ltx as TxMixin<Doc, Doc>
|
||||||
// Do not spend time to wait for trigger processing
|
// Do not spend time to wait for trigger processing
|
||||||
await updateDocSyncInfo(control, tx, mix.objectSpace, mix)
|
await updateDocSyncInfo(control, tx, mix.objectSpace, mix, cache, toApply)
|
||||||
return []
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (control.hierarchy.isDerived(ltx._class, core.class.TxCUD)) {
|
if (TxProcessor.isExtendsCUD(ltx._class)) {
|
||||||
const cud = ltx as TxCUD<Doc>
|
const cud = ltx as TxCUD<Doc>
|
||||||
|
|
||||||
let space: Ref<Space> = cud.objectSpace
|
let space: Ref<Space> = cud.objectSpace
|
||||||
@ -52,15 +57,14 @@ export async function OnProjectChanges (tx: Tx, control: TriggerControl): Promis
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isDocSyncUpdateRequired(control.hierarchy, cud)) {
|
if (isDocSyncUpdateRequired(control.hierarchy, cud)) {
|
||||||
// Do not spend time to wait for trigger processing
|
await updateDocSyncInfo(control, tx, space, cud, cache, toApply)
|
||||||
await updateDocSyncInfo(control, tx, space, cud)
|
|
||||||
}
|
}
|
||||||
if (control.hierarchy.isDerived(cud.objectClass, time.class.ToDo)) {
|
if (control.hierarchy.isDerived(cud.objectClass, time.class.ToDo)) {
|
||||||
if (tx._class === core.class.TxCollectionCUD) {
|
if (tx._class === core.class.TxCollectionCUD) {
|
||||||
const coll = tx as TxCollectionCUD<Doc, AttachedDoc>
|
const coll = tx as TxCollectionCUD<Doc, AttachedDoc>
|
||||||
if (control.hierarchy.isDerived(coll.objectClass, github.class.GithubPullRequest)) {
|
if (control.hierarchy.isDerived(coll.objectClass, github.class.GithubPullRequest)) {
|
||||||
// Ok we got todo change for pull request, let's mark it for sync.
|
// Ok we got todo change for pull request, let's mark it for sync.
|
||||||
return [
|
result.push(
|
||||||
control.txFactory.createTxUpdateDoc<DocSyncInfo>(
|
control.txFactory.createTxUpdateDoc<DocSyncInfo>(
|
||||||
github.class.DocSyncInfo,
|
github.class.DocSyncInfo,
|
||||||
coll.objectSpace,
|
coll.objectSpace,
|
||||||
@ -69,12 +73,16 @@ export async function OnProjectChanges (tx: Tx, control: TriggerControl): Promis
|
|||||||
needSync: ''
|
needSync: ''
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
]
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []
|
}
|
||||||
|
if (toApply.length > 0) {
|
||||||
|
await control.apply(control.ctx, toApply)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
@ -108,14 +116,16 @@ async function updateDocSyncInfo (
|
|||||||
_class: Ref<Class<Tx>>
|
_class: Ref<Class<Tx>>
|
||||||
objectId: Ref<Doc>
|
objectId: Ref<Doc>
|
||||||
objectClass: Ref<Class<Doc>>
|
objectClass: Ref<Class<Doc>>
|
||||||
}
|
},
|
||||||
|
cache: Map<string, any>,
|
||||||
|
toApply: Tx[]
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const checkTx = (tx: Tx): boolean =>
|
const checkTx = (tx: Tx): boolean =>
|
||||||
control.hierarchy.isDerived(tx._class, core.class.TxCUD) &&
|
control.hierarchy.isDerived(tx._class, core.class.TxCUD) &&
|
||||||
(tx as TxCUD<Doc>).objectClass === github.class.DocSyncInfo &&
|
(tx as TxCUD<Doc>).objectClass === github.class.DocSyncInfo &&
|
||||||
(tx as TxCUD<Doc>).objectId === cud.objectId
|
(tx as TxCUD<Doc>).objectId === cud.objectId
|
||||||
|
|
||||||
const txes = [...control.txes, ...control.ctx.contextData.broadcast.txes]
|
const txes = [...control.txes, ...control.ctx.contextData.broadcast.txes, ...toApply]
|
||||||
// Check already captured Txes
|
// Check already captured Txes
|
||||||
for (const i of txes) {
|
for (const i of txes) {
|
||||||
if (checkTx(i)) {
|
if (checkTx(i)) {
|
||||||
@ -131,20 +141,29 @@ async function updateDocSyncInfo (
|
|||||||
if (account === undefined) {
|
if (account === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const projects =
|
||||||
|
(cache.get('projects') as GithubProject[]) ??
|
||||||
|
(await control.queryFind(control.ctx, github.mixin.GithubProject, {}, { projection: { _id: 1 } }))
|
||||||
|
cache.set('projects', projects)
|
||||||
|
|
||||||
const projects = await control.queryFind(control.ctx, github.mixin.GithubProject, {}, { projection: { _id: 1 } })
|
|
||||||
if (projects.some((it) => it._id === (space as Ref<GithubProject>))) {
|
if (projects.some((it) => it._id === (space as Ref<GithubProject>))) {
|
||||||
const [sdoc] = await control.findAll(control.ctx, github.class.DocSyncInfo, {
|
const sdoc =
|
||||||
|
(cache.get(cud.objectId) as DocSyncInfo) ??
|
||||||
|
(
|
||||||
|
await control.findAll(control.ctx, github.class.DocSyncInfo, {
|
||||||
_id: cud.objectId as Ref<DocSyncInfo>
|
_id: cud.objectId as Ref<DocSyncInfo>
|
||||||
})
|
})
|
||||||
|
).shift()
|
||||||
|
|
||||||
// We need to check if sync doc is already exists.
|
// We need to check if sync doc is already exists.
|
||||||
if (sdoc === undefined) {
|
if (sdoc === undefined) {
|
||||||
// Created by non github integration
|
// Created by non github integration
|
||||||
// We need to create the doc sync info
|
// We need to create the doc sync info
|
||||||
await createSyncDoc(control, cud, tx, space)
|
createSyncDoc(control, cud, tx, space, toApply)
|
||||||
} else {
|
} else {
|
||||||
|
cache.set(cud.objectId, sdoc)
|
||||||
// We need to create the doc sync info
|
// We need to create the doc sync info
|
||||||
await updateSyncDoc(control, cud, space, sdoc)
|
updateSyncDoc(control, cud, space, sdoc, toApply)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,7 +179,7 @@ function isDocSyncUpdateRequired (h: Hierarchy, coll: TxCUD<Doc>): boolean {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateSyncDoc (
|
function updateSyncDoc (
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
cud: {
|
cud: {
|
||||||
_class: Ref<Class<Tx>>
|
_class: Ref<Class<Tx>>
|
||||||
@ -168,8 +187,9 @@ async function updateSyncDoc (
|
|||||||
objectClass: Ref<Class<Doc>>
|
objectClass: Ref<Class<Doc>>
|
||||||
},
|
},
|
||||||
space: Ref<Space>,
|
space: Ref<Space>,
|
||||||
info: DocSyncInfo
|
info: DocSyncInfo,
|
||||||
): Promise<void> {
|
toApply: Tx[]
|
||||||
|
): void {
|
||||||
const data: DocumentUpdate<DocSyncInfo> =
|
const data: DocumentUpdate<DocSyncInfo> =
|
||||||
cud._class === core.class.TxRemoveDoc
|
cud._class === core.class.TxRemoveDoc
|
||||||
? {
|
? {
|
||||||
@ -183,14 +203,14 @@ async function updateSyncDoc (
|
|||||||
data.externalVersion = '#' // We need to put this one to handle new documents.)
|
data.externalVersion = '#' // We need to put this one to handle new documents.)
|
||||||
data.space = space
|
data.space = space
|
||||||
}
|
}
|
||||||
await control.apply(control.ctx, [
|
toApply.push(
|
||||||
control.txFactory.createTxUpdateDoc<DocSyncInfo>(
|
control.txFactory.createTxUpdateDoc<DocSyncInfo>(
|
||||||
github.class.DocSyncInfo,
|
github.class.DocSyncInfo,
|
||||||
info.space,
|
info.space,
|
||||||
cud.objectId as Ref<DocSyncInfo>,
|
cud.objectId as Ref<DocSyncInfo>,
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
])
|
)
|
||||||
|
|
||||||
control.ctx.contextData.broadcast.targets.github = (it) => {
|
control.ctx.contextData.broadcast.targets.github = (it) => {
|
||||||
if (control.hierarchy.isDerived(it._class, core.class.TxCUD)) {
|
if (control.hierarchy.isDerived(it._class, core.class.TxCUD)) {
|
||||||
@ -201,7 +221,7 @@ async function updateSyncDoc (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createSyncDoc (
|
function createSyncDoc (
|
||||||
control: TriggerControl,
|
control: TriggerControl,
|
||||||
cud: {
|
cud: {
|
||||||
_class: Ref<Class<Tx>>
|
_class: Ref<Class<Tx>>
|
||||||
@ -209,8 +229,9 @@ async function createSyncDoc (
|
|||||||
objectClass: Ref<Class<Doc>>
|
objectClass: Ref<Class<Doc>>
|
||||||
},
|
},
|
||||||
tx: Tx,
|
tx: Tx,
|
||||||
space: Ref<Space>
|
space: Ref<Space>,
|
||||||
): Promise<void> {
|
toApply: Tx[]
|
||||||
|
): void {
|
||||||
const data: DocumentUpdate<DocSyncInfo> = {
|
const data: DocumentUpdate<DocSyncInfo> = {
|
||||||
url: '',
|
url: '',
|
||||||
githubNumber: 0,
|
githubNumber: 0,
|
||||||
@ -226,14 +247,14 @@ async function createSyncDoc (
|
|||||||
data.attachedTo = coll.objectId
|
data.attachedTo = coll.objectId
|
||||||
}
|
}
|
||||||
|
|
||||||
await control.apply(control.ctx, [
|
toApply.push(
|
||||||
control.txFactory.createTxCreateDoc<DocSyncInfo>(
|
control.txFactory.createTxCreateDoc<DocSyncInfo>(
|
||||||
github.class.DocSyncInfo,
|
github.class.DocSyncInfo,
|
||||||
space,
|
space,
|
||||||
data,
|
data,
|
||||||
cud.objectId as Ref<DocSyncInfo>
|
cud.objectId as Ref<DocSyncInfo>
|
||||||
)
|
)
|
||||||
])
|
)
|
||||||
control.ctx.contextData.broadcast.targets.github = (it) => {
|
control.ctx.contextData.broadcast.targets.github = (it) => {
|
||||||
if (control.hierarchy.isDerived(it._class, core.class.TxCUD)) {
|
if (control.hierarchy.isDerived(it._class, core.class.TxCUD)) {
|
||||||
if ((it as TxCUD<Doc>).objectClass === github.class.DocSyncInfo) {
|
if ((it as TxCUD<Doc>).objectClass === github.class.DocSyncInfo) {
|
||||||
|
@ -56,6 +56,7 @@ services:
|
|||||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||||
- MODEL_ENABLED=*
|
- MODEL_ENABLED=*
|
||||||
- BRANDING_PATH=/var/cfg/branding.json
|
- BRANDING_PATH=/var/cfg/branding.json
|
||||||
|
- STATS_URL=http://stats:4901
|
||||||
workspace:
|
workspace:
|
||||||
image: hardcoreeng/workspace
|
image: hardcoreeng/workspace
|
||||||
links:
|
links:
|
||||||
@ -72,6 +73,7 @@ services:
|
|||||||
- MODEL_ENABLED=*
|
- MODEL_ENABLED=*
|
||||||
- ACCOUNTS_URL=http://account:3003
|
- ACCOUNTS_URL=http://account:3003
|
||||||
- BRANDING_PATH=/var/cfg/branding.json
|
- BRANDING_PATH=/var/cfg/branding.json
|
||||||
|
- STATS_URL=http://stats:4901
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
front:
|
front:
|
||||||
image: hardcoreeng/front
|
image: hardcoreeng/front
|
||||||
@ -100,6 +102,8 @@ services:
|
|||||||
- COLLABORATOR_URL=ws://localhost:3079
|
- COLLABORATOR_URL=ws://localhost:3079
|
||||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||||
- BRANDING_URL=http://localhost:8083/branding-test.json
|
- BRANDING_URL=http://localhost:8083/branding-test.json
|
||||||
|
- STATS_URL=http://stats:4901
|
||||||
|
- STATS_API=http://localhost:4901
|
||||||
transactor:
|
transactor:
|
||||||
image: hardcoreeng/transactor
|
image: hardcoreeng/transactor
|
||||||
pull_policy: never
|
pull_policy: never
|
||||||
@ -128,6 +132,7 @@ services:
|
|||||||
- LAST_NAME_FIRST=true
|
- LAST_NAME_FIRST=true
|
||||||
- BRANDING_PATH=/var/cfg/branding.json
|
- BRANDING_PATH=/var/cfg/branding.json
|
||||||
- FULLTEXT_URL=http://fulltext:4710
|
- FULLTEXT_URL=http://fulltext:4710
|
||||||
|
- STATS_URL=http://stats:4901
|
||||||
collaborator:
|
collaborator:
|
||||||
image: hardcoreeng/collaborator
|
image: hardcoreeng/collaborator
|
||||||
links:
|
links:
|
||||||
@ -142,20 +147,18 @@ services:
|
|||||||
- ACCOUNTS_URL=http://account:3003
|
- ACCOUNTS_URL=http://account:3003
|
||||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||||
- FULLTEXT_URL=http://fulltext:4710
|
- FULLTEXT_URL=http://fulltext:4710
|
||||||
|
- STATS_URL=http://stats:4901
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
rekoni:
|
rekoni:
|
||||||
image: hardcoreeng/rekoni-service
|
image: hardcoreeng/rekoni-service
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
ports:
|
ports:
|
||||||
- 4007:4004
|
- 4007:4004
|
||||||
deploy:
|
environment:
|
||||||
resources:
|
- STATS_URL=http://stats:4901
|
||||||
limits:
|
|
||||||
memory: 1024M
|
|
||||||
fulltext:
|
fulltext:
|
||||||
image: hardcoreeng/fulltext
|
image: hardcoreeng/fulltext
|
||||||
extra_hosts:
|
|
||||||
- "host.docker.internal:host-gateway"
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
links:
|
links:
|
||||||
- elastic
|
- elastic
|
||||||
@ -173,3 +176,12 @@ services:
|
|||||||
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
- STORAGE_CONFIG=${STORAGE_CONFIG}
|
||||||
- REKONI_URL=http://rekoni:4007
|
- REKONI_URL=http://rekoni:4007
|
||||||
- ACCOUNTS_URL=http://account:3003
|
- ACCOUNTS_URL=http://account:3003
|
||||||
|
- STATS_URL=http://stats:4901
|
||||||
|
stats:
|
||||||
|
image: hardcoreeng/stats
|
||||||
|
ports:
|
||||||
|
- 4901:4901
|
||||||
|
environment:
|
||||||
|
- PORT=4901
|
||||||
|
- SERVER_SECRET=secret
|
||||||
|
restart: unless-stopped
|
@ -23,6 +23,8 @@ fi
|
|||||||
# Create user record in accounts
|
# Create user record in accounts
|
||||||
./tool.sh create-account user1 -f John -l Appleseed -p 1234
|
./tool.sh create-account user1 -f John -l Appleseed -p 1234
|
||||||
./tool.sh create-account user2 -f Kainin -l Dirak -p 1234
|
./tool.sh create-account user2 -f Kainin -l Dirak -p 1234
|
||||||
|
./tool.sh create-account super -f Super -l User -p 1234
|
||||||
|
./tool.sh set-user-admin super true
|
||||||
./tool.sh assign-workspace user1 sanity-ws
|
./tool.sh assign-workspace user1 sanity-ws
|
||||||
./tool.sh assign-workspace user2 sanity-ws
|
./tool.sh assign-workspace user2 sanity-ws
|
||||||
./tool.sh set-user-role user1 sanity-ws OWNER
|
./tool.sh set-user-role user1 sanity-ws OWNER
|
||||||
|
@ -253,29 +253,40 @@ test.describe('Inbox tests', () => {
|
|||||||
const linkText = await page.locator('.antiPopup .link').textContent()
|
const linkText = await page.locator('.antiPopup .link').textContent()
|
||||||
const page2 = await browser.newPage()
|
const page2 = await browser.newPage()
|
||||||
try {
|
try {
|
||||||
const leftSideMenuPageSecond = new LeftSideMenuPage(page2)
|
const channelPage2 = new ChannelPage(page2)
|
||||||
const inboxPageSecond = new InboxPage(page2)
|
const leftSideMenuPage2 = new LeftSideMenuPage(page2)
|
||||||
|
const inboxPage2 = new InboxPage(page2)
|
||||||
await leftSideMenuPage.clickOnCloseInvite()
|
await leftSideMenuPage.clickOnCloseInvite()
|
||||||
await page2.goto(linkText ?? '')
|
await page2.goto(linkText ?? '')
|
||||||
const joinPage = new SignInJoinPage(page2)
|
|
||||||
await joinPage.join(newUser2)
|
const joinPage2 = new SignInJoinPage(page2)
|
||||||
|
await joinPage2.join(newUser2)
|
||||||
|
|
||||||
|
await leftSideMenuPage2.clickChunter()
|
||||||
|
await channelPage2.clickChannel('general')
|
||||||
|
|
||||||
await leftSideMenuPage.clickChunter()
|
await leftSideMenuPage.clickChunter()
|
||||||
await channelPage.clickChannel('general')
|
await channelPage.clickChannel('general')
|
||||||
await channelPage.sendMessage('Test message')
|
await channelPage.sendMessage('Test message')
|
||||||
|
|
||||||
|
await leftSideMenuPage2.clickNotification()
|
||||||
|
await inboxPage2.clickOnInboxFilter('Channels')
|
||||||
|
|
||||||
await leftSideMenuPage.clickTracker()
|
await leftSideMenuPage.clickTracker()
|
||||||
|
|
||||||
const newIssue = createNewIssueData(newUser2.firstName, newUser2.lastName)
|
const newIssue = createNewIssueData(newUser2.firstName, newUser2.lastName)
|
||||||
await prepareNewIssueWithOpenStep(page, newIssue, false)
|
await prepareNewIssueWithOpenStep(page, newIssue, false)
|
||||||
await issuesDetailsPage.checkIssue(newIssue)
|
await issuesDetailsPage.checkIssue(newIssue)
|
||||||
await leftSideMenuPageSecond.clickTracker()
|
|
||||||
await leftSideMenuPageSecond.clickNotification()
|
await leftSideMenuPage2.clickTracker()
|
||||||
await inboxPageSecond.clickOnInboxFilter('Channels')
|
await leftSideMenuPage2.clickNotification()
|
||||||
await inboxPageSecond.checkIfInboxChatExists(newIssue.title, false)
|
|
||||||
await inboxPageSecond.checkIfInboxChatExists('Test message', true)
|
await inboxPage2.clickOnInboxFilter('Channels')
|
||||||
await inboxPageSecond.clickOnInboxFilter('Issues')
|
await inboxPage2.checkIfInboxChatExists(newIssue.title, false)
|
||||||
await inboxPageSecond.checkIfIssueIsPresentInInbox(newIssue.title)
|
await inboxPage2.checkIfInboxChatExists('Test message', true)
|
||||||
await inboxPageSecond.checkIfInboxChatExists('Channel general', false)
|
await inboxPage2.clickOnInboxFilter('Issues')
|
||||||
|
await inboxPage2.checkIfIssueIsPresentInInbox(newIssue.title)
|
||||||
|
await inboxPage2.checkIfInboxChatExists('Channel general', false)
|
||||||
} finally {
|
} finally {
|
||||||
await page2.close()
|
await page2.close()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user