feat: Implement process import/export functionality, enhance process … (#10528)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2026-02-19 14:41:51 +05:00
committed by GitHub
parent f391dd0a5d
commit feb76fcafa
25 changed files with 228 additions and 103 deletions
@@ -249,6 +249,7 @@ export abstract class MemDb extends TxProcessor implements Storage {
// We need to filter instances without mixin was set
result = result.filter((r) => (r as any)[_class] !== undefined)
}
if (options?.sort !== undefined) resultSort(result, options?.sort, _class, this.hierarchy, this)
const total = result.length
result = result.slice(0, options?.limit)
+20 -1
View File
@@ -43,7 +43,7 @@ import presentation from '@hcengineering/model-presentation'
import { TToDo } from '@hcengineering/model-time'
import view, { createAction } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import notification from '@hcengineering/notification'
import notification, { type NotificationGroup } from '@hcengineering/notification'
import { type Asset, type IntlString, type Resource } from '@hcengineering/platform'
import {
type ApproveRequest,
@@ -727,6 +727,25 @@ export function createModel (builder: Builder): void {
component: process.component.ProcessesSettingSection
})
builder.createDoc(notification.class.NotificationType, core.space.Model, {
hidden: false,
generated: false,
allowedForAuthor: true,
label: process.string.NewProcessToDo,
group: time.ids.TimeNotificationGroup as Ref<NotificationGroup>,
txClasses: [core.class.TxCreateDoc],
objectClass: process.class.ProcessToDo,
txMatch: {
objectClass: process.class.ProcessToDo
},
defaultEnabled: true,
templates: {
textTemplate: '{body}',
htmlTemplate: '<p>{body}</p>',
subjectTemplate: '{title}'
}
})
// builder.createDoc(presentation.class.ComponentPointExtension, core.space.Model, {
// extension: workbench.extensions.WorkbenchExtensions,
// component: process.component.NotifierExtension
+3 -1
View File
@@ -141,7 +141,9 @@
"Approvers": "Schvalovatelé",
"UnlockCard": "Odemknout kartu",
"UnlockSection": "Odemknout sekci",
"CancelToDo": "Zrušit akční položku"
"CancelToDo": "Zrušit akční položku",
"Export": "Exportovat",
"Import": "Importovat"
},
"error": {
"MethodNotFound": "Metoda nenalezena: {methodId}",
+3 -1
View File
@@ -141,7 +141,9 @@
"Approvers": "Genehmiger",
"UnlockCard": "Karte entsperren",
"UnlockSection": "Abschnitt entsperren",
"CancelToDo": "Action Item abbrechen"
"CancelToDo": "Action Item abbrechen",
"Export": "Exportieren",
"Import": "Importieren"
},
"error": {
"MethodNotFound": "Methode nicht gefunden: {methodId}",
+3 -1
View File
@@ -146,7 +146,9 @@
"Approvers": "Approvers",
"UnlockCard": "Unlock card",
"UnlockSection": "Unlock section",
"CancelToDo": "Cancel Action item"
"CancelToDo": "Cancel Action item",
"Export": "Export",
"Import": "Import"
},
"error": {
"MethodNotFound": "Method not found: {methodId}",
+3 -1
View File
@@ -146,7 +146,9 @@
"Approvers": "Aprobadores",
"UnlockCard": "Desbloquear tarjeta",
"UnlockSection": "Desbloquear sección",
"CancelToDo": "Cancelar acción"
"CancelToDo": "Cancelar acción",
"Export": "Exportar",
"Import": "Importar"
},
"error": {
"MethodNotFound": "Método no encontrado: {methodId}",
+3 -1
View File
@@ -146,7 +146,9 @@
"Approvers": "Approbateurs",
"UnlockCard": "Déverrouiller la carte",
"UnlockSection": "Déverrouiller la section",
"CancelToDo": "Annuler l'action"
"CancelToDo": "Annuler l'action",
"Export": "Exporter",
"Import": "Importer"
},
"error": {
"MethodNotFound": "Méthode introuvable : {methodId}",
+3 -1
View File
@@ -146,7 +146,9 @@
"Approvers": "Approvatori",
"UnlockCard": "Sblocca scheda",
"UnlockSection": "Sblocca sezione",
"CancelToDo": "Annulla azione"
"CancelToDo": "Annulla azione",
"Export": "Esporta",
"Import": "Importa"
},
"error": {
"MethodNotFound": "Metodo non trovato: {methodId}",
+3 -1
View File
@@ -145,7 +145,9 @@
"Approvers": "承認者",
"UnlockCard": "カードのロックを解除",
"UnlockSection": "セクションのロックを解除",
"CancelToDo": "アクションをキャンセル"
"CancelToDo": "アクションをキャンセル",
"Export": "エクスポート",
"Import": "インポート"
},
"error": {
"MethodNotFound": "メソッドが見つかりません: {methodId}",
+3 -1
View File
@@ -134,7 +134,9 @@
"FieldIsEmpty": "Campo vazio",
"UnlockCard": "Desbloquear cartão",
"UnlockSection": "Desbloquear seção",
"CancelToDo": "Cancelar ação"
"CancelToDo": "Cancelar ação",
"Export": "Exportar",
"Import": "Importar"
},
"error": {
"MethodNotFound": "Método não encontrado: {methodId}",
+3 -1
View File
@@ -146,7 +146,9 @@
"Approvers": "Aprovadores",
"UnlockCard": "Desbloquear cartão",
"UnlockSection": "Desbloquear seção",
"CancelToDo": "Cancelar ação"
"CancelToDo": "Cancelar ação",
"Export": "Exportar",
"Import": "Importar"
},
"error": {
"MethodNotFound": "Método não encontrado: {methodId}",
+3 -1
View File
@@ -146,7 +146,9 @@
"Approvers": "Утверждающие",
"UnlockCard": "Разблокировать карточку",
"UnlockSection": "Разблокировать секцию",
"CancelToDo": "Отменить задачу"
"CancelToDo": "Отменить задачу",
"Export": "Экспорт",
"Import": "Импорт"
},
"error": {
"MethodNotFound": "Метод не найден: {methodId}",
+3 -1
View File
@@ -143,7 +143,9 @@
"Approvers": "Onaylayanlar",
"UnlockCard": "Kartın kilidini aç",
"UnlockSection": "Bölümün kilidini aç",
"CancelToDo": "Eylem öğesini iptal et"
"CancelToDo": "Eylem öğesini iptal et",
"Export": "Dışa aktar",
"Import": "İçe aktar"
},
"error": {
"MethodNotFound": "Metod bulunamadı: {methodId}",
+3 -1
View File
@@ -146,7 +146,9 @@
"Approvers": "批准人",
"UnlockCard": "解锁卡片",
"UnlockSection": "解锁部分",
"CancelToDo": "取消任务"
"CancelToDo": "取消任务",
"Export": "导出",
"Import": "导入"
},
"error": {
"MethodNotFound": "找不到方法:{methodId}",
@@ -18,9 +18,10 @@
import { translate } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Process, State } from '@hcengineering/process'
import { ButtonIcon, getCurrentLocation, Icon, IconAdd, Label, navigate } from '@hcengineering/ui'
import { ButtonIcon, getCurrentLocation, Icon, IconAdd, IconFile, IconOpen, Label, navigate } from '@hcengineering/ui'
import process from '../plugin'
import { makeRank } from '@hcengineering/rank'
import { importProcess } from '../exporter'
export let masterTag: MasterTag
@@ -76,12 +77,35 @@
loc.path[6] = id
navigate(loc, true)
}
function handleImport (): void {
const input = document.createElement('input')
input.type = 'file'
input.accept = '.json'
input.onchange = async (evt) => {
const file = (evt.target as HTMLInputElement).files?.[0]
if (file != null) {
const text = await file.text()
await importProcess(masterTag._id, text)
}
}
input.click()
}
</script>
<div class="hulyTableAttr-header font-medium-12">
<Icon icon={process.icon.Process} size="small" />
<span><Label label={process.string.Processes} /></span>
<ButtonIcon kind="primary" icon={IconAdd} size="small" dataId={'btnAdd'} on:click={add} />
<div class="flex-row-center flex-gap-1">
<ButtonIcon
kind="primary"
icon={IconFile}
size="small"
tooltip={{ label: process.string.Import, direction: 'bottom' }}
on:click={handleImport}
/>
<ButtonIcon kind="primary" icon={IconAdd} size="small" dataId={'btnAdd'} on:click={add} />
</div>
</div>
{#if processes.length}
<div class="hulyTableAttr-content task">
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import { Ref, SortingOrder } from '@hcengineering/core'
import { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
import { createQuery, getClient, MessageBox, IconDownload } from '@hcengineering/presentation'
import { Process, State, Transition } from '@hcengineering/process'
import { clearSettingsStore, settingsStore } from '@hcengineering/setting-resources'
import {
@@ -30,6 +30,7 @@
secondNavSeparators,
showPopup
} from '@hcengineering/ui'
import { exportProcess } from '../../exporter'
import view from '@hcengineering/view'
import { createEventDispatcher } from 'svelte'
import process from '../../plugin'
@@ -135,6 +136,24 @@
function handleSettings (): void {
showPopup(ProcesssSetting, { value })
}
function handleExport (): void {
if (value === undefined) return
const str = JSON.stringify(
exportProcess(value).docs.map((doc) => {
const { modifiedBy, modifiedOn, createdBy, createdOn, ...rest } = doc
return rest
})
)
const blob = new Blob([str], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `${value.name}.json`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
</script>
<div class="hulyComponent-content__container columns">
@@ -153,6 +172,13 @@
/>
<div class="flex-row-center flex-gap-2">
<ButtonIcon icon={IconSettings} size="small" kind="secondary" on:click={handleSettings} />
<ButtonIcon
icon={IconDownload}
tooltip={{ label: process.string.Export, direction: 'bottom' }}
size="small"
kind="secondary"
on:click={handleExport}
/>
<ButtonIcon
icon={IconDetails}
tooltip={{ label: process.string.Data, direction: 'bottom' }}
@@ -15,13 +15,11 @@
<script lang="ts">
import { Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Process, State } from '@hcengineering/process'
import { Process } from '@hcengineering/process'
import { DropdownLabels, Label } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import plugin from '../../plugin'
import { ModeId, Modes } from '../../query'
import { getContext } from '../../utils'
import BaseCriteria from '../criterias/BaseCriteria.svelte'
import { ModeId } from '../../query'
import SubProcessStateCriteria from '../criterias/SubProcessStateCriteria.svelte'
export let readonly: boolean
+44 -7
View File
@@ -1,9 +1,9 @@
import { type MasterTag } from '@hcengineering/card'
import { type Class, type Doc, type Ref } from '@hcengineering/core'
import core, { type Doc, type Ref, type Class, generateId } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import process from './plugin'
export function exportProcess (_id: Ref<MasterTag>): {
export function exportProcesses (_id: Ref<MasterTag>): {
docs: Doc[]
required: Array<Ref<Class<Doc>>>
} {
@@ -12,11 +12,48 @@ export function exportProcess (_id: Ref<MasterTag>): {
const m = client.getModel()
const processes = m.findAllSync(process.class.Process, { masterTag: _id })
for (const proc of processes) {
docs.push(proc)
docs.push(...m.findAllSync(process.class.State, { process: proc._id }))
docs.push(...m.findAllSync(process.class.Transition, { process: proc._id }))
const res = exportProcess(proc)
docs.push(...res.docs)
}
return { docs, required: [] }
}
export function exportProcess (proc: Doc): {
docs: Doc[]
required: Array<Ref<Class<Doc>>>
} {
const docs: Doc[] = [proc]
const client = getClient()
const m = client.getModel()
docs.push(...m.findAllSync(process.class.State, { process: proc._id as any }))
docs.push(...m.findAllSync(process.class.Transition, { process: proc._id as any }))
return { docs, required: [] }
}
export async function importProcess (masterTag: Ref<MasterTag>, json: string): Promise<void> {
try {
const data = JSON.parse(json) as Doc[]
if (data[0] === undefined) return
const client = getClient()
const m = client.getModel()
const apply = client.apply('Import process')
;(data[0] as any).masterTag = masterTag
;(data[0] as any)._id = generateId()
for (const elem of data) {
if (m.findObject(elem._id) !== undefined) continue
await apply.createDoc(elem._class, core.space.Model, stripData(elem), elem._id)
}
await apply.commit(true)
} catch (e) {
console.error('Failed to import process:', e)
}
}
function stripData<T extends Doc> (
doc: T
): Omit<T, '_id' | '_class' | 'space' | 'modifiedBy' | 'modifiedOn' | 'createdBy' | 'createdOn'> {
const { _id, _class, space, modifiedBy, modifiedOn, createdBy, createdOn, ...rest } = doc
return rest
}
+13 -12
View File
@@ -12,6 +12,7 @@
// limitations under the License.
import { type Resources } from '@hcengineering/platform'
import ApproveRequestPresenter from './components/ApproveRequestPresenter.svelte'
import ExecutionContextSelector from './components/attributeEditors/ExecutionContextSelector.svelte'
import FunctionSelector from './components/attributeEditors/FunctionSelector.svelte'
import NestedContextSelector from './components/attributeEditors/NestedContextSelector.svelte'
@@ -24,20 +25,19 @@ import ExecutionMyToDos from './components/ExecutionMyToDos.svelte'
import ExecutonPresenter from './components/ExecutonPresenter.svelte'
import ExecutonProgressPresenter from './components/ExecutonProgressPresenter.svelte'
import Main from './components/Main.svelte'
import CancelToDoEditor from './components/settings/CancelToDoEditor.svelte'
import ApproveRequestPresenter from './components/ApproveRequestPresenter.svelte'
import SubProcessPresenter from './components/presenters/SubProcessPresenter.svelte'
import ToDoPresenter from './components/presenters/ToDoPresenter.svelte'
import UpdateCardPresenter from './components/presenters/UpdateCardPresenter.svelte'
import ProcessesCardSection from './components/ProcessesCardSection.svelte'
import ProcessesExtension from './components/ProcessesExtension.svelte'
import RequestsCardSection from './components/RequestsCardSection.svelte'
import RequestsExtension from './components/RequestsExtension.svelte'
import ProcessesSettingSection from './components/ProcessesSection.svelte'
import ProcessPresenter from './components/ProcessPresenter.svelte'
import RequestsCardSection from './components/RequestsCardSection.svelte'
import RequestsExtension from './components/RequestsExtension.svelte'
import RunProcessCardPopup from './components/RunProcessCardPopup.svelte'
import RunProcessPopup from './components/RunProcessPopup.svelte'
import ActionsPresenter from './components/settings/ActionsPresenter.svelte'
import CancelToDoEditor from './components/settings/CancelToDoEditor.svelte'
import FunctionSubmenu from './components/settings/FunctionSubmenu.svelte'
import ProcessEditor from './components/settings/ProcessEditor.svelte'
import StatePresenter from './components/settings/StatePresenter.svelte'
@@ -65,21 +65,22 @@ import CreateCardPresenter from './components/presenters/CreateCardPresenter.sve
import ProcessesHeaderExtension from './components/ProcessesHeaderExtension.svelte'
import AddRelationEditor from './components/settings/AddRelationEditor.svelte'
import AddTagEditor from './components/settings/AddTagEditor.svelte'
import ApproveRequestEditor from './components/settings/ApproveRequestEditor.svelte'
import ApproveRequestTriggerEditor from './components/settings/ApproveRequestTriggerEditor.svelte'
import ApproveRequestTriggerPresenter from './components/settings/ApproveRequestTriggerPresenter.svelte'
import CardUpdateEditor from './components/settings/CardUpdateEditor.svelte'
import CardUpdatePresenter from './components/settings/CardUpdatePresenter.svelte'
import CreateCardEditor from './components/settings/CreateCardEditor.svelte'
import FieldChangesEditor from './components/settings/FieldChangesEditor.svelte'
import LockSectionEditor from './components/settings/LockSectionEditor.svelte'
import LockSectionPresenter from './components/settings/LockSectionPresenter.svelte'
import SubProcessMatchEditor from './components/settings/SubProcessMatchEditor.svelte'
import SubProcessMatchPresenter from './components/settings/SubProcessMatchPresenter.svelte'
import TimeEditor from './components/settings/TimeEditor.svelte'
import TimePresenter from './components/settings/TimePresenter.svelte'
import ToDoSettingPresenter from './components/settings/ToDoPresenter.svelte'
import ToDoValuePresenter from './components/settings/ToDoValuePresenter.svelte'
import TransitionRefPresenter from './components/settings/TransitionRefPresenter.svelte'
import ApproveRequestEditor from './components/settings/ApproveRequestEditor.svelte'
import ApproveRequestTriggerEditor from './components/settings/ApproveRequestTriggerEditor.svelte'
import ApproveRequestTriggerPresenter from './components/settings/ApproveRequestTriggerPresenter.svelte'
import LockSectionEditor from './components/settings/LockSectionEditor.svelte'
import LockSectionPresenter from './components/settings/LockSectionPresenter.svelte'
import UnLockSectionPresenter from './components/settings/UnLockSectionPresenter.svelte'
import AppendEditor from './components/transformEditors/AppendEditor.svelte'
import CutEditor from './components/transformEditors/CutEditor.svelte'
@@ -87,8 +88,7 @@ import ReplaceEditor from './components/transformEditors/ReplaceEditor.svelte'
import SplitEditor from './components/transformEditors/SplitEditor.svelte'
import NumberPresenter from './components/transformPresenters/NumberPresenter.svelte'
import RolePresenter from './components/transformPresenters/RolePresenter.svelte'
import ToDoValuePresenter from './components/settings/ToDoValuePresenter.svelte'
import { exportProcess } from './exporter'
import { exportProcesses, importProcess } from './exporter'
import { ProcessMiddleware } from './middleware'
import {
approveRequestApproved,
@@ -207,7 +207,8 @@ export default async (): Promise<Resources> => ({
ApproveRequestRejected: approveRequestRejected
},
function: {
ExportProcess: exportProcess,
ExportProcess: exportProcesses,
ImportProcess: importProcess,
ShowDoneQuery: showDoneQuery,
CheckProcessSectionVisibility: checkProcessSectionVisibility,
CheckRequestsSectionVisibility: checkRequestsSectionVisibility,
+3 -1
View File
@@ -249,7 +249,9 @@ export default mergeIds(processId, process, {
LockCard: '' as IntlString,
LockSection: '' as IntlString,
UnlockCard: '' as IntlString,
UnlockSection: '' as IntlString
UnlockSection: '' as IntlString,
Export: '' as IntlString,
Import: '' as IntlString
},
permission: {
RunProcess: '' as Ref<Permission>,
+19 -43
View File
@@ -17,6 +17,7 @@ import cardPlugin, { Card } from '@hcengineering/card'
import core, {
ArrOf,
Doc,
Ref,
RefTo,
Tx,
TxCreateDoc,
@@ -41,7 +42,8 @@ import process, {
isUpdateTx,
ProcessCustomEvent,
ApproveRequest,
ExecutionStatus
ExecutionStatus,
Trigger
} from '@hcengineering/process'
import { QueueTopic, TriggerControl } from '@hcengineering/server-core'
import { ProcessMessage } from '@hcengineering/server-process'
@@ -138,9 +140,18 @@ export async function OnProcessToDoClose (txes: Tx[], control: TriggerControl):
await control.findAll(control.ctx, process.class.ProcessToDo, { _id: updateTx.objectId }, { limit: 1 })
)[0]
if (todo === undefined) continue
const events: Ref<Trigger>[] = [process.trigger.OnToDoClose]
if (todo._class === process.class.ApproveRequest) {
const request = todo as ApproveRequest
if (request.approved === true) {
events.push(process.trigger.OnApproveRequestApproved)
} else if (request.approved === false) {
events.push(process.trigger.OnApproveRequestRejected)
}
}
await putEventToQueue(
{
event: process.trigger.OnToDoClose,
event: events,
execution: todo.execution,
createdOn: tx.modifiedOn,
context: {
@@ -151,19 +162,7 @@ export async function OnProcessToDoClose (txes: Tx[], control: TriggerControl):
)
if (todo._class === process.class.ApproveRequest) {
const request = todo as ApproveRequest
if (request.approved === true) {
await putEventToQueue(
{
event: process.trigger.OnApproveRequestApproved,
execution: todo.execution,
createdOn: tx.modifiedOn,
context: {
todo
}
},
control
)
} else if (request.approved === false) {
if (request.approved === false) {
// remove all other approve requests for this execution
const toRemove = await control.findAll(control.ctx, process.class.ApproveRequest, {
group: request.group,
@@ -172,17 +171,6 @@ export async function OnProcessToDoClose (txes: Tx[], control: TriggerControl):
for (const req of toRemove) {
res.push(control.txFactory.createTxRemoveDoc(req._class, req.space, req._id))
}
await putEventToQueue(
{
event: process.trigger.OnApproveRequestRejected,
execution: todo.execution,
createdOn: tx.modifiedOn,
context: {
todo
}
},
control
)
}
}
}
@@ -199,7 +187,7 @@ export async function OnCustomEvent (txes: Tx[], control: TriggerControl): Promi
if (card.length === 0) continue
await putEventToQueue(
{
event: process.trigger.OnEvent,
event: [process.trigger.OnEvent],
execution: customEvent.execution,
createdOn: tx.modifiedOn,
card: customEvent.card,
@@ -222,7 +210,7 @@ export async function OnExecutionCreate (txes: Tx[], control: TriggerControl): P
const execution = TxProcessor.createDoc2Doc(createTx)
await putEventToQueue(
{
event: process.trigger.OnExecutionStart,
event: [process.trigger.OnExecutionStart],
execution: execution._id,
createdOn: tx.modifiedOn,
context: {}
@@ -242,7 +230,7 @@ export async function OnProcessToDoRemove (txes: Tx[], control: TriggerControl):
if (removedTodo === undefined) continue
await putEventToQueue(
{
event: process.trigger.OnToDoRemove,
event: [process.trigger.OnToDoRemove],
execution: removedTodo.execution,
createdOn: tx.modifiedOn,
context: {
@@ -271,7 +259,7 @@ export async function OnExecutionContinue (txes: Tx[], control: TriggerControl):
if (transition === undefined) continue
await putEventToQueue(
{
event: process.trigger.OnExecutionContinue,
event: [process.trigger.OnExecutionContinue],
execution: execution._id,
createdOn: tx.modifiedOn,
context: {}
@@ -404,19 +392,7 @@ export async function OnCardUpdate (txes: Tx[], control: TriggerControl): Promis
const ops = isUpdateTx(cudTx) ? cudTx.operations : cudTx.attributes
await putEventToQueue(
{
event: process.trigger.OnCardUpdate,
card: cudTx.objectId,
createdOn: tx.modifiedOn,
context: {
card: card[0],
operations: ops ?? {}
}
},
control
)
await putEventToQueue(
{
event: process.trigger.WhenFieldChanges,
event: [process.trigger.OnCardUpdate, process.trigger.WhenFieldChanges],
card: cudTx.objectId,
createdOn: tx.modifiedOn,
context: {
+1 -1
View File
@@ -33,7 +33,7 @@ export type TransformFunc = (
export interface ProcessMessage {
account: PersonId
createdOn: Timestamp
event: Ref<Trigger>
event: Ref<Trigger>[]
context: Record<string, any>
execution?: Ref<Execution>
card?: Ref<Card>
+2 -1
View File
@@ -263,7 +263,8 @@ export async function OnToDoCreate (txes: TxCUD<Doc>[], control: TriggerControl)
if (
!hierarchy.isDerived(objectSpace._class, core.class.SystemSpace) &&
!objectSpace.members.includes(currentAcc.uuid)
!objectSpace.members.includes(currentAcc.uuid) &&
currentAcc.primarySocialId !== core.account.System
) {
continue
}
+33 -17
View File
@@ -102,15 +102,15 @@ export async function messageHandler (record: ProcessMessage, ws: WorkspaceUuid,
}
}
function isActiveExecution (execution: Execution, trigger: Ref<Trigger>): boolean {
function isActiveExecution (execution: Execution, trigger: Ref<Trigger>[]): boolean {
if (execution.status !== ExecutionStatus.Active) return false
if (execution.error != null && trigger !== process.trigger.OnExecutionContinue) return false
if (execution.error != null && !trigger.includes(process.trigger.OnExecutionContinue)) return false
return true
}
async function processBroadcast (control: ProcessControl, record: ProcessMessage): Promise<void> {
const transitions = control.client.getModel().findAllSync(process.class.Transition, {
trigger: record.event
trigger: { $in: record.event }
})
for (const transition of transitions) {
if (transition.from == null) continue
@@ -128,7 +128,7 @@ async function processBroadcast (control: ProcessControl, record: ProcessMessage
async function processCardExecutions (control: ProcessControl, record: ProcessMessage): Promise<void> {
const transitions = control.client.getModel().findAllSync(process.class.Transition, {
trigger: record.event
trigger: { $in: record.event }
})
const states = new Set<Ref<State>>()
for (const transition of transitions) {
@@ -148,7 +148,7 @@ async function processCardExecutions (control: ProcessControl, record: ProcessMe
}
// we should update timers, probably context value changed
if ([process.trigger.OnCardUpdate, process.trigger.WhenFieldChanges].includes(record.event)) {
if (record.event.some((p) => p === process.trigger.OnCardUpdate || p === process.trigger.WhenFieldChanges)) {
await updateTimers(control, record)
}
}
@@ -158,18 +158,18 @@ async function findTransitions (
record: ProcessMessage,
execution: Execution
): Promise<Transition | undefined> {
if (record.event === process.trigger.OnExecutionStart) {
if (record.event.includes(process.trigger.OnExecutionStart)) {
const transitions = control.client.getModel().findAllSync(
process.class.Transition,
{
process: execution.process,
trigger: record.event
trigger: { $in: record.event }
},
{ sort: { rank: SortingOrder.Ascending } }
)
return await pickTransition(control, execution, transitions, record.context)
}
if (record.event === process.trigger.OnExecutionContinue) {
if (record.event.includes(process.trigger.OnExecutionContinue)) {
const transition = execution.error?.[0].transition
if (transition === undefined) return
const res = control.client.getModel().findAllSync(process.class.Transition, {
@@ -180,16 +180,20 @@ async function findTransitions (
return res
}
}
const transitions = control.client.getModel().findAllSync(process.class.Transition, {
from: execution.currentState,
process: execution.process,
trigger: record.event
})
const transitions = control.client.getModel().findAllSync(
process.class.Transition,
{
from: execution.currentState,
process: execution.process,
trigger: { $in: record.event }
},
{ sort: { rank: SortingOrder.Ascending } }
)
return await pickTransition(control, execution, transitions, record.context)
}
async function checkToDoResult (control: ProcessControl, record: ProcessMessage, execution: Execution): Promise<void> {
if (record.event !== process.trigger.OnToDoClose) return
if (!record.event.includes(process.trigger.OnToDoClose)) return
const todo = record.context.todo as ProcessToDo
if (todo === undefined) return
if (todo.results == null) return
@@ -284,10 +288,10 @@ async function processExecution (control: ProcessControl, record: ProcessMessage
}
function isRollback (record: ProcessMessage): boolean {
if (record.event === process.trigger.OnEvent && record.context.eventType === 'rollback') {
if (record.event.includes(process.trigger.OnEvent) && record.context.eventType === 'rollback') {
return true
}
if (record.event === process.trigger.OnToDoRemove && record.context.todo?.withRollback === true) {
if (record.event.includes(process.trigger.OnToDoRemove) && record.context.todo?.withRollback === true) {
return true
}
return false
@@ -393,11 +397,23 @@ async function executeTransition (
} else {
rollback.push(client.txFactory.createTxRemoveDoc(execution._class, execution.space, execution._id))
}
const card: Card =
let card: Card | undefined =
control.cache.get(execution.card) ??
(await control.client.findOne(cardPlugin.class.Card, { _id: execution.card }))
if (card !== undefined) {
control.cache.set(execution.card, card)
} else if (transition.trigger === process.trigger.OnExecutionStart) {
for (let attempt = 1; attempt < 5; attempt++) {
await new Promise((resolve) => setTimeout(resolve, 1000 * attempt))
card = await control.client.findOne(cardPlugin.class.Card, { _id: execution.card })
if (card !== undefined) {
control.cache.set(execution.card, card)
break
}
}
}
if (card === undefined) {
return
}
const context: Record<string, any> = {}
for (const action of transition.actions) {
+1 -1
View File
@@ -14,7 +14,7 @@ export async function SendTimeEvent (ws: WorkspaceUuid, _execution: Ref<Executio
await producer.send(ctx, ws, [
{
account: core.account.System,
event: process.trigger.OnTime,
event: [process.trigger.OnTime],
createdOn: Date.now(),
context: {},
execution: _execution