mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
feat: Implement process import/export functionality, enhance process … (#10528)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -145,7 +145,9 @@
|
||||
"Approvers": "承認者",
|
||||
"UnlockCard": "カードのロックを解除",
|
||||
"UnlockSection": "セクションのロックを解除",
|
||||
"CancelToDo": "アクションをキャンセル"
|
||||
"CancelToDo": "アクションをキャンセル",
|
||||
"Export": "エクスポート",
|
||||
"Import": "インポート"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "メソッドが見つかりません: {methodId}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -146,7 +146,9 @@
|
||||
"Approvers": "Утверждающие",
|
||||
"UnlockCard": "Разблокировать карточку",
|
||||
"UnlockSection": "Разблокировать секцию",
|
||||
"CancelToDo": "Отменить задачу"
|
||||
"CancelToDo": "Отменить задачу",
|
||||
"Export": "Экспорт",
|
||||
"Import": "Импорт"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Метод не найден: {methodId}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user