mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-17 18:05:42 +02:00
Rework todo result (#9893)
This commit is contained in:
@@ -63,7 +63,6 @@ import {
|
||||
type Step,
|
||||
type Transition,
|
||||
type Trigger,
|
||||
type TriggerResult,
|
||||
type UpdateCriteriaComponent,
|
||||
processId
|
||||
} from '@hcengineering/process'
|
||||
@@ -136,8 +135,6 @@ export class TTransition extends TDoc implements Transition {
|
||||
|
||||
triggerParams!: Record<string, any>
|
||||
|
||||
result?: TriggerResult | null
|
||||
|
||||
@Prop(TypeRank(), core.string.Rank)
|
||||
@Hidden()
|
||||
rank!: Rank
|
||||
|
||||
@@ -906,3 +906,17 @@
|
||||
.emoji.fitSize > img {
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.editor-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
grid-auto-rows: minmax(2rem, max-content);
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
row-gap: 0.5rem;
|
||||
margin: 0.25rem 2rem 0;
|
||||
width: calc(100% - 4rem);
|
||||
column-gap: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
height: min-content;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,11 @@
|
||||
"Cancelled": "Zrušeno",
|
||||
"WaitUntil": "Čekat do",
|
||||
"CurrentUser": "Aktuální uživatel",
|
||||
"CurrentDate": "Aktuální datum"
|
||||
"CurrentDate": "Aktuální datum",
|
||||
"AddResult": "Přidat výsledek",
|
||||
"For": "Pro",
|
||||
"Context": "Kontext",
|
||||
"Attribute": "Atribut"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Metoda nenalezena: {methodId}",
|
||||
|
||||
@@ -99,7 +99,11 @@
|
||||
"Cancelled": "Abgebrochen",
|
||||
"WaitUntil": "Warten bis",
|
||||
"CurrentUser": "Aktueller Benutzer",
|
||||
"CurrentDate": "Aktuelles Datum"
|
||||
"CurrentDate": "Aktuelles Datum",
|
||||
"AddResult": "Ergebnis hinzufügen",
|
||||
"For": "Für",
|
||||
"Context": "Kontext",
|
||||
"Attribute": "Attribut"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Methode nicht gefunden: {methodId}",
|
||||
|
||||
@@ -104,7 +104,11 @@
|
||||
"Cancelled": "Cancelled",
|
||||
"WaitUntil": "Wait until",
|
||||
"CurrentUser": "Current user",
|
||||
"CurrentDate": "Current date"
|
||||
"CurrentDate": "Current date",
|
||||
"AddResult": "Add result",
|
||||
"For": "For",
|
||||
"Context": "Context",
|
||||
"Attribute": "Attribute"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Method not found: {methodId}",
|
||||
|
||||
@@ -104,7 +104,11 @@
|
||||
"Cancelled": "Cancelado",
|
||||
"WaitUntil": "Esperar hasta",
|
||||
"CurrentUser": "Usuario Actual",
|
||||
"CurrentDate": "Fecha Actual"
|
||||
"CurrentDate": "Fecha Actual",
|
||||
"AddResult": "Agregar resultado",
|
||||
"For": "Para",
|
||||
"Context": "Contexto",
|
||||
"Attribute": "Atributo"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Método no encontrado: {methodId}",
|
||||
|
||||
@@ -104,7 +104,11 @@
|
||||
"Cancelled": "Annulé",
|
||||
"WaitUntil": "Attendre jusqu'à",
|
||||
"CurrentUser": "Utilisateur actuel",
|
||||
"CurrentDate": "Date actuelle"
|
||||
"CurrentDate": "Date actuelle",
|
||||
"AddResult": "Ajouter un résultat",
|
||||
"For": "Pour",
|
||||
"Context": "Contexte",
|
||||
"Attribute": "Attribut"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Méthode introuvable : {methodId}",
|
||||
|
||||
@@ -104,7 +104,11 @@
|
||||
"Cancelled": "Annullato",
|
||||
"WaitUntil": "Aspetta fino a",
|
||||
"CurrentUser": "Utente corrente",
|
||||
"CurrentDate": "Data corrente"
|
||||
"CurrentDate": "Data corrente",
|
||||
"AddResult": "Aggiungi risultato",
|
||||
"For": "Per",
|
||||
"Context": "Contesto",
|
||||
"Attribute": "Attributo"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Metodo non trovato: {methodId}",
|
||||
|
||||
@@ -103,7 +103,11 @@
|
||||
"Cancelled": "キャンセル済み",
|
||||
"WaitUntil": "待機する",
|
||||
"CurrentUser": "現在のユーザー",
|
||||
"CurrentDate": "現在の日付"
|
||||
"CurrentDate": "現在の日付",
|
||||
"AddResult": "結果を追加",
|
||||
"For": "〜のために",
|
||||
"Context": "コンテキスト",
|
||||
"Attribute": "属性"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "メソッドが見つかりません: {methodId}",
|
||||
|
||||
@@ -104,7 +104,11 @@
|
||||
"Cancelled": "Cancelado",
|
||||
"WaitUntil": "Aguardar até",
|
||||
"CurrentUser": "Usuário Atual",
|
||||
"CurrentDate": "Data Atual"
|
||||
"CurrentDate": "Data Atual",
|
||||
"AddResult": "Adicionar resultado",
|
||||
"For": "Para",
|
||||
"Attribute": "Atributo",
|
||||
"Context": "Contexto"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Método não encontrado: {methodId}",
|
||||
|
||||
@@ -104,7 +104,11 @@
|
||||
"Cancelled": "Отменено",
|
||||
"WaitUntil": "Ждать до",
|
||||
"CurrentUser": "Текущий пользователь",
|
||||
"CurrentDate": "Текущая дата"
|
||||
"CurrentDate": "Текущая дата",
|
||||
"AddResult": "Добавить результат",
|
||||
"For": "Для",
|
||||
"Context": "Контекст",
|
||||
"Attribute": "Аттрибут"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Метод не найден: {methodId}",
|
||||
|
||||
@@ -104,7 +104,11 @@
|
||||
"Cancelled": "已取消",
|
||||
"WaitUntil": "等待直到",
|
||||
"CurrentUser": "当前用户",
|
||||
"CurrentDate": "当前日期"
|
||||
"CurrentDate": "当前日期",
|
||||
"AddResult": "添加结果",
|
||||
"For": "对于",
|
||||
"Context": "上下文",
|
||||
"Attribute": "属性"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "找不到方法:{methodId}",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid">
|
||||
<div class="editor-grid">
|
||||
{#each keys as key}
|
||||
<AttributeCriteria
|
||||
{process}
|
||||
@@ -54,17 +54,3 @@
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
grid-auto-rows: minmax(2rem, max-content);
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
row-gap: 0.5rem;
|
||||
column-gap: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
height: min-content;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid">
|
||||
<div class="editor-grid">
|
||||
<span
|
||||
class="labelOnPanel"
|
||||
use:tooltip={{
|
||||
@@ -118,18 +118,3 @@
|
||||
<Button label={presentation.string.Add} width={'100%'} kind={'link-bordered'} size={'large'} on:click={onAdd} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.5fr;
|
||||
grid-auto-rows: minmax(2rem, max-content);
|
||||
justify-content: start;
|
||||
width: calc(100% - 4rem);
|
||||
align-items: center;
|
||||
margin: 0.25rem 2rem 0.5rems;
|
||||
row-gap: 0.5rem;
|
||||
column-gap: 1rem;
|
||||
height: min-content;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import core, { Ref, SortingOrder } from '@hcengineering/core'
|
||||
import { Card, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Process, State, Transition, Trigger, TriggerResult } from '@hcengineering/process'
|
||||
import { Process, State, Transition, Trigger } from '@hcengineering/process'
|
||||
import { Component, Dropdown, DropdownIntlItem, DropdownLabelsIntl, Label, ListItem } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../../plugin'
|
||||
@@ -26,7 +26,6 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
let to: Ref<State> | undefined = undefined
|
||||
let states: State[] = []
|
||||
let result: TriggerResult | null = null
|
||||
|
||||
const query = createQuery()
|
||||
query.query(
|
||||
@@ -96,7 +95,6 @@
|
||||
triggerParams: params,
|
||||
process: process._id,
|
||||
rank,
|
||||
result,
|
||||
actions: []
|
||||
})
|
||||
dispatch('close')
|
||||
@@ -106,9 +104,6 @@
|
||||
if (e.detail?.params !== undefined) {
|
||||
params = e.detail.params
|
||||
}
|
||||
if (e.detail?.result !== undefined) {
|
||||
result = e.detail.result
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -119,7 +114,7 @@
|
||||
width={'medium'}
|
||||
on:close
|
||||
>
|
||||
<div class="grid">
|
||||
<div class="editor-grid">
|
||||
<Label label={plugin.string.From} />
|
||||
{#if !withoutFrom}
|
||||
<Dropdown
|
||||
@@ -149,7 +144,6 @@
|
||||
label={plugin.string.Trigger}
|
||||
on:selected={() => {
|
||||
params = {}
|
||||
result = null
|
||||
}}
|
||||
justify={'left'}
|
||||
width={'100%'}
|
||||
@@ -157,19 +151,6 @@
|
||||
/>
|
||||
</div>
|
||||
{#if triggerValue?.editor}
|
||||
<Component is={triggerValue.editor} props={{ process, params, result }} on:change={change} />
|
||||
<Component is={triggerValue.editor} props={{ process, params }} on:change={change} />
|
||||
{/if}
|
||||
</Card>
|
||||
|
||||
<style lang="scss">
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
grid-auto-rows: minmax(2rem, max-content);
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
row-gap: 0.5rem;
|
||||
column-gap: 1rem;
|
||||
height: min-content;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,12 +20,11 @@
|
||||
export let transition: Transition
|
||||
|
||||
let params = transition.triggerParams ?? {}
|
||||
let result = transition.result ?? null
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function save (): Promise<void> {
|
||||
await client.update(transition, { triggerParams: params, trigger: selectedTrigger, result })
|
||||
await client.update(transition, { triggerParams: params, trigger: selectedTrigger })
|
||||
clearSettingsStore()
|
||||
}
|
||||
|
||||
@@ -38,9 +37,6 @@
|
||||
if (e.detail?.params !== undefined) {
|
||||
params = e.detail.params
|
||||
}
|
||||
if (e.detail?.result !== undefined) {
|
||||
result = e.detail.result
|
||||
}
|
||||
}
|
||||
|
||||
let selectedTrigger: Ref<Trigger> = transition.trigger
|
||||
@@ -72,7 +68,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{#if trigger !== undefined}
|
||||
<div class="grid">
|
||||
<div class="editor-grid">
|
||||
<Label label={plugin.string.Trigger} />
|
||||
<DropdownLabelsIntl
|
||||
items={triggersItems}
|
||||
@@ -80,7 +76,6 @@
|
||||
label={plugin.string.Trigger}
|
||||
on:selected={() => {
|
||||
params = {}
|
||||
result = null
|
||||
}}
|
||||
justify={'left'}
|
||||
width={'100%'}
|
||||
@@ -89,7 +84,7 @@
|
||||
</div>
|
||||
{#if trigger.editor !== undefined}
|
||||
<div class="editor">
|
||||
<Component is={trigger.editor} props={{ process, params, result, readonly }} on:change={change} />
|
||||
<Component is={trigger.editor} props={{ process, params, readonly }} on:change={change} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -105,18 +100,6 @@
|
||||
margin: 0.25rem 2rem 0;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
grid-auto-rows: minmax(2rem, max-content);
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
row-gap: 0.25rem;
|
||||
column-gap: 1rem;
|
||||
margin: 0.25rem 2rem 0;
|
||||
height: min-content;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
@@ -24,13 +24,23 @@
|
||||
export let step: Step<Doc>
|
||||
|
||||
$: currentContext = step.context ? process.context[step.context._id] : undefined
|
||||
|
||||
$: resultContext =
|
||||
step.results != null ? step.results.map((r) => process.context[r._id]).filter((ctx) => ctx != null) : []
|
||||
</script>
|
||||
|
||||
{#if currentContext}
|
||||
<Label label={processPlugin.string.Result} />
|
||||
<div class="container">
|
||||
<ProcessContextPresenter context={currentContext} />
|
||||
</div>
|
||||
{#if currentContext || resultContext.length > 0}
|
||||
<Label label={processPlugin.string.Result} />:
|
||||
{#if currentContext}
|
||||
<div class="container">
|
||||
<ProcessContextPresenter context={currentContext} />
|
||||
</div>
|
||||
{/if}
|
||||
{#each resultContext as ctx}
|
||||
<div class="container">
|
||||
<ProcessContextPresenter context={ctx} />
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -13,32 +13,20 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { Class, PropertyType, Ref, Type } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { TriggerResult } from '@hcengineering/process'
|
||||
import setting from '@hcengineering/setting-resources/src/plugin'
|
||||
import {
|
||||
AnyComponent,
|
||||
Component,
|
||||
DropdownIntlItem,
|
||||
DropdownLabelsIntl,
|
||||
EditBox,
|
||||
Label,
|
||||
Toggle
|
||||
} from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import core, { Type } from '@hcengineering/core'
|
||||
import { Process, UserResult } from '@hcengineering/process'
|
||||
import { Button, EditBox, IconClose, Label } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../../plugin'
|
||||
import { generateContextId } from '../../utils'
|
||||
import ResultTypeSelector from './ResultTypeSelector.svelte'
|
||||
|
||||
export let result: TriggerResult | null
|
||||
export let result: UserResult | null
|
||||
export let process: Process
|
||||
|
||||
let type: Type<any> | undefined | null = result?.type
|
||||
let name: string = result?.name ?? ''
|
||||
let is: AnyComponent | undefined
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
let key: string | undefined = result?.key
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function update (): void {
|
||||
@@ -48,114 +36,34 @@
|
||||
result = {
|
||||
_id: generateContextId(),
|
||||
name,
|
||||
key,
|
||||
type
|
||||
}
|
||||
}
|
||||
dispatch('change', result)
|
||||
}
|
||||
|
||||
function getTypes (): DropdownIntlItem[] {
|
||||
const descendants = hierarchy.getDescendants(core.class.Type)
|
||||
const res: DropdownIntlItem[] = []
|
||||
for (const descendant of descendants) {
|
||||
const _class = hierarchy.getClass(descendant)
|
||||
if (_class.label !== undefined && hierarchy.hasMixin(_class, view.mixin.ObjectEditor)) {
|
||||
res.push({
|
||||
label: _class.label,
|
||||
id: _class._id
|
||||
})
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
const items = getTypes()
|
||||
let selectedType: Ref<Class<Type<PropertyType>>> | undefined = type?._class
|
||||
$: selectType(selectedType)
|
||||
function selectType (type: Ref<Class<Type<PropertyType>>> | undefined): void {
|
||||
if (type == null) return
|
||||
const _class = hierarchy.getClass(type)
|
||||
const editor = hierarchy.as(_class, view.mixin.ObjectEditor)
|
||||
if (editor.editor !== undefined) {
|
||||
is = editor.editor
|
||||
}
|
||||
}
|
||||
const handleSelection = (e: { detail: Ref<Class<Type<any>>> }): void => {
|
||||
selectType(e.detail)
|
||||
}
|
||||
const handleChange = (e: any): void => {
|
||||
if (e.detail?.type === undefined) return
|
||||
if (!deepEqual(e.detail.type, type)) {
|
||||
type = e.detail?.type
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
function handleNameChange (e: any): void {
|
||||
function handleNameChange (): void {
|
||||
if (result != null) {
|
||||
result.name = name
|
||||
dispatch('change', result)
|
||||
}
|
||||
}
|
||||
|
||||
function changeRequired (e: boolean): void {
|
||||
if (!e) {
|
||||
type = undefined
|
||||
} else {
|
||||
type = null
|
||||
}
|
||||
update()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid">
|
||||
<Label label={plugin.string.Result} />
|
||||
<Toggle
|
||||
on={result != null}
|
||||
on:change={(e) => {
|
||||
changeRequired(e.detail)
|
||||
}}
|
||||
/>
|
||||
{#if type !== undefined}
|
||||
<span class="label">
|
||||
<Label label={core.string.Description} />
|
||||
</span>
|
||||
<div class="editor-grid">
|
||||
<span class="label">
|
||||
<Label label={core.string.Description} />
|
||||
</span>
|
||||
<div class="flex-row-center flex-gap-2">
|
||||
<EditBox bind:value={name} placeholder={core.string.Description} on:change={handleNameChange} kind={'default'} />
|
||||
<span class="label">
|
||||
<Label label={setting.string.Type} />
|
||||
</span>
|
||||
<DropdownLabelsIntl
|
||||
label={setting.string.Type}
|
||||
{items}
|
||||
size={'large'}
|
||||
width={'100%'}
|
||||
bind:selected={selectedType}
|
||||
on:selected={handleSelection}
|
||||
<Button
|
||||
icon={IconClose}
|
||||
kind="ghost"
|
||||
on:click={() => {
|
||||
dispatch('remove')
|
||||
}}
|
||||
/>
|
||||
{#if is}
|
||||
<Component
|
||||
{is}
|
||||
props={{
|
||||
type,
|
||||
width: '100%',
|
||||
isCard: true,
|
||||
kind: 'regular',
|
||||
size: 'large'
|
||||
}}
|
||||
on:change={handleChange}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<ResultTypeSelector {process} bind:key bind:type on:change={update} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
grid-auto-rows: minmax(2rem, max-content);
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
row-gap: 0.5rem;
|
||||
column-gap: 1rem;
|
||||
height: min-content;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<!--
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { MasterTag } from '@hcengineering/card'
|
||||
import core, { AnyAttribute, Class, Ref, Type } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Process } from '@hcengineering/process'
|
||||
import { DropdownLabelsIntl, Label, tooltip } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../../plugin'
|
||||
|
||||
export let process: Process
|
||||
export let key: string | undefined
|
||||
export let type: Type<any> | null | undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const dispatch = createEventDispatcher()
|
||||
$: setType(key)
|
||||
|
||||
function setType (key: string | undefined): void {
|
||||
const attr = key != null ? hierarchy.findAttribute(process.masterTag, key) : undefined
|
||||
if (attr != null) {
|
||||
type = attr.type
|
||||
} else {
|
||||
type = null
|
||||
}
|
||||
dispatch('change')
|
||||
}
|
||||
|
||||
function getKeys (_class: Ref<Class<MasterTag>>): AnyAttribute[] {
|
||||
const ignoreKeys = ['_class', 'content', 'parent', 'attachments', 'todos']
|
||||
const attributes = hierarchy.getAllAttributes(_class, core.class.Doc)
|
||||
const res: AnyAttribute[] = []
|
||||
for (const [key, attr] of attributes) {
|
||||
if (attr.hidden === true) continue
|
||||
if (ignoreKeys.includes(key)) continue
|
||||
res.push(attr)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
const allAttrs = getKeys(process.masterTag)
|
||||
const items = allAttrs.map((attr) => {
|
||||
return {
|
||||
id: attr.name,
|
||||
label: attr.label ?? attr.name
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<span
|
||||
class="labelOnPanel"
|
||||
use:tooltip={{
|
||||
props: { label: plugin.string.Attribute }
|
||||
}}
|
||||
>
|
||||
<Label label={plugin.string.Attribute} />
|
||||
</span>
|
||||
<DropdownLabelsIntl
|
||||
label={plugin.string.Attribute}
|
||||
{items}
|
||||
size={'large'}
|
||||
width={'100%'}
|
||||
shouldUpdateUndefined={false}
|
||||
bind:selected={key}
|
||||
/>
|
||||
@@ -0,0 +1,92 @@
|
||||
<!--
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { Class, PropertyType, Ref, Type } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import setting from '@hcengineering/setting-resources/src/plugin'
|
||||
import { AnyComponent, Component, DropdownIntlItem, DropdownLabelsIntl, Label } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let type: Type<any> | null | undefined
|
||||
|
||||
let is: AnyComponent | undefined
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function getTypes (): DropdownIntlItem[] {
|
||||
const descendants = hierarchy.getDescendants(core.class.Type)
|
||||
const res: DropdownIntlItem[] = []
|
||||
for (const descendant of descendants) {
|
||||
const _class = hierarchy.getClass(descendant)
|
||||
if (_class.label !== undefined && hierarchy.hasMixin(_class, view.mixin.ObjectEditor)) {
|
||||
res.push({
|
||||
label: _class.label,
|
||||
id: _class._id
|
||||
})
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
const items = getTypes()
|
||||
|
||||
let selectedType: Ref<Class<Type<PropertyType>>> | undefined = type?._class
|
||||
$: selectType(selectedType)
|
||||
function selectType (type: Ref<Class<Type<PropertyType>>> | undefined): void {
|
||||
if (type == null) return
|
||||
const _class = hierarchy.getClass(type)
|
||||
const editor = hierarchy.as(_class, view.mixin.ObjectEditor)
|
||||
if (editor.editor !== undefined) {
|
||||
is = editor.editor
|
||||
}
|
||||
}
|
||||
const handleSelection = (e: { detail: Ref<Class<Type<any>>> }): void => {
|
||||
selectType(e.detail)
|
||||
}
|
||||
const handleChange = (e: any): void => {
|
||||
if (e.detail?.type === undefined) return
|
||||
if (!deepEqual(e.detail.type, type)) {
|
||||
type = e.detail?.type
|
||||
dispatch('change', type)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<span class="label">
|
||||
<Label label={setting.string.Type} />
|
||||
</span>
|
||||
<DropdownLabelsIntl
|
||||
label={setting.string.Type}
|
||||
{items}
|
||||
size={'large'}
|
||||
width={'100%'}
|
||||
bind:selected={selectedType}
|
||||
on:selected={handleSelection}
|
||||
/>
|
||||
{#if is}
|
||||
<Component
|
||||
{is}
|
||||
props={{
|
||||
type,
|
||||
width: '100%',
|
||||
isCard: true,
|
||||
kind: 'regular',
|
||||
size: 'large'
|
||||
}}
|
||||
on:change={handleChange}
|
||||
/>
|
||||
{/if}
|
||||
@@ -0,0 +1,54 @@
|
||||
<!--
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Type } from '@hcengineering/core'
|
||||
import { Process } from '@hcengineering/process'
|
||||
import { DropdownIntlItem, DropdownLabelsIntl, Label } from '@hcengineering/ui'
|
||||
import plugin from '../../plugin'
|
||||
import ResultFieldTypeEditor from './ResultFieldTypeEditor.svelte'
|
||||
import ResultTypeEditor from './ResultTypeEditor.svelte'
|
||||
|
||||
export let process: Process
|
||||
export let type: Type<any> | null | undefined
|
||||
export let key: string | undefined
|
||||
|
||||
enum Modes {
|
||||
Attribute = 'attribute',
|
||||
Context = 'context'
|
||||
}
|
||||
|
||||
const items: DropdownIntlItem[] = [
|
||||
{
|
||||
id: Modes.Attribute,
|
||||
label: plugin.string.Attribute
|
||||
},
|
||||
{
|
||||
id: Modes.Context,
|
||||
label: plugin.string.Context
|
||||
}
|
||||
]
|
||||
|
||||
let selected: Modes = key !== undefined || type == null ? Modes.Attribute : Modes.Context
|
||||
</script>
|
||||
|
||||
<span class="label">
|
||||
<Label label={plugin.string.For} />
|
||||
</span>
|
||||
<DropdownLabelsIntl label={plugin.string.For} {items} size={'large'} width={'100%'} bind:selected />
|
||||
{#if selected === Modes.Attribute}
|
||||
<ResultFieldTypeEditor {process} bind:key bind:type on:change />
|
||||
{:else}
|
||||
<ResultTypeEditor bind:type on:change />
|
||||
{/if}
|
||||
@@ -0,0 +1,64 @@
|
||||
<!--
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Process, UserResult } from '@hcengineering/process'
|
||||
import { Button } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../../plugin'
|
||||
import ResultEditor from './ResultEditor.svelte'
|
||||
|
||||
export let process: Process
|
||||
export let result: UserResult[] | undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let items: (UserResult | null)[] = result ?? []
|
||||
|
||||
function handleChange (e: CustomEvent<UserResult>, index: number) {
|
||||
if (e.detail !== undefined) {
|
||||
items[index === -1 ? items.length : index] = e.detail
|
||||
result = items.filter((r) => r != null)
|
||||
dispatch('change', result)
|
||||
}
|
||||
}
|
||||
|
||||
function onAdd (): void {
|
||||
items.push(null)
|
||||
items = items
|
||||
}
|
||||
|
||||
function remove (index: number): void {
|
||||
items.splice(index, 1)
|
||||
items = items
|
||||
result = items.filter((r) => r != null)
|
||||
dispatch('change', result)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each items as item, i}
|
||||
<ResultEditor
|
||||
{process}
|
||||
result={item}
|
||||
on:change={(e) => {
|
||||
handleChange(e, i)
|
||||
}}
|
||||
on:remove={() => {
|
||||
remove(i)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
<div class="flex-center mt-4">
|
||||
<Button label={plugin.string.AddResult} width={'100%'} kind={'link-bordered'} size={'large'} on:click={onAdd} />
|
||||
</div>
|
||||
@@ -70,7 +70,7 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<div class="grid">
|
||||
<div class="editor-grid">
|
||||
<ProcessAttribute
|
||||
{process}
|
||||
masterTag={process.masterTag}
|
||||
@@ -88,17 +88,3 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
grid-auto-rows: minmax(2rem, max-content);
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
row-gap: 0.5rem;
|
||||
column-gap: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
height: min-content;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Process, ProcessToDo, Step } from '@hcengineering/process'
|
||||
import { Process, ProcessToDo, Step, UserResult } from '@hcengineering/process'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../../plugin'
|
||||
import ParamsEditor from './ParamsEditor.svelte'
|
||||
import ResultEditor from './ResultEditor.svelte'
|
||||
import { Label, Toggle } from '@hcengineering/ui'
|
||||
import ResultsEditor from './ResultsEditor.svelte'
|
||||
|
||||
export let process: Process
|
||||
export let step: Step<ProcessToDo>
|
||||
@@ -35,6 +35,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function changeResults (e: CustomEvent<UserResult[]>): void {
|
||||
if (e.detail !== undefined) {
|
||||
step.results = e.detail
|
||||
dispatch('change', step)
|
||||
}
|
||||
}
|
||||
|
||||
const keys = ['title', 'user', 'dueDate']
|
||||
</script>
|
||||
|
||||
@@ -53,6 +60,8 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="divider" />
|
||||
<ResultsEditor {process} result={step.results} on:change={changeResults} />
|
||||
|
||||
<style lang="scss">
|
||||
.divider {
|
||||
|
||||
@@ -14,18 +14,16 @@
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { Process, TriggerResult } from '@hcengineering/process'
|
||||
import { Process } from '@hcengineering/process'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../../plugin'
|
||||
import ToDoContextSelector from '../contextEditors/ToDoContextSelector.svelte'
|
||||
import ResultEditor from './ResultEditor.svelte'
|
||||
|
||||
export let readonly: boolean
|
||||
export let process: Process
|
||||
export let params: Record<string, any>
|
||||
export let skipRollback: boolean = false
|
||||
export let result: TriggerResult | null = null
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -34,39 +32,9 @@
|
||||
params._id = e.detail
|
||||
dispatch('change', { params })
|
||||
}
|
||||
|
||||
function changeResult (e: CustomEvent<any>): void {
|
||||
if (e.detail !== undefined) {
|
||||
result = e.detail
|
||||
dispatch('change', { result })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid">
|
||||
<div class="editor-grid">
|
||||
<Label label={plugin.string.ToDo} />
|
||||
<ToDoContextSelector {readonly} {skipRollback} {process} value={params._id} on:change={change} />
|
||||
</div>
|
||||
{#if !skipRollback}
|
||||
<div class="divider" />
|
||||
<ResultEditor {result} on:change={changeResult} />
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.divider {
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3fr;
|
||||
grid-auto-rows: minmax(2rem, max-content);
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
row-gap: 0.5rem;
|
||||
column-gap: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
height: min-content;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -95,7 +95,6 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
await txop.update(execution, {
|
||||
context
|
||||
})
|
||||
await requestResult(txop, execution, transition, execution.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,6 +146,8 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
_id: todo.execution
|
||||
})
|
||||
if (execution === undefined) return
|
||||
const txop = new TxOperations(this.client, getCurrentAccount().primarySocialId)
|
||||
await requestResult(txop, execution, todo.results, execution.context)
|
||||
const transitions = this.client.getModel().findAllSync(process.class.Transition, {
|
||||
process: execution.process,
|
||||
from: execution.currentState,
|
||||
@@ -155,13 +156,11 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
const transition = await pickTransition(this.client, execution, transitions, todo)
|
||||
if (transition === undefined) return
|
||||
const context = await getNextStateUserInput(execution, transition, execution.context)
|
||||
const txop = new TxOperations(this.client, getCurrentAccount().primarySocialId)
|
||||
if (context !== undefined) {
|
||||
await txop.update(execution, {
|
||||
context
|
||||
})
|
||||
}
|
||||
await requestResult(txop, execution, transition, execution.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +195,10 @@ export default mergeIds(processId, process, {
|
||||
ArraySizeLt: '' as IntlString,
|
||||
ArraySizeLte: '' as IntlString,
|
||||
CurrentUser: '' as IntlString,
|
||||
CurrentDate: '' as IntlString
|
||||
CurrentDate: '' as IntlString,
|
||||
AddResult: '' as IntlString,
|
||||
For: '' as IntlString,
|
||||
Attribute: '' as IntlString,
|
||||
Context: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -49,7 +49,8 @@ import {
|
||||
type State,
|
||||
type Step,
|
||||
type StepId,
|
||||
type Transition
|
||||
type Transition,
|
||||
type UserResult
|
||||
} from '@hcengineering/process'
|
||||
import { type AnyComponent, showPopup } from '@hcengineering/ui'
|
||||
import { type AttributeCategory } from '@hcengineering/view'
|
||||
@@ -567,27 +568,24 @@ export function getToDoEndAction (prevState: State): Step<Doc> {
|
||||
export async function requestResult (
|
||||
txop: TxOperations,
|
||||
execution: Execution,
|
||||
transition: Transition,
|
||||
results: UserResult[] | undefined,
|
||||
context: ExecutionContext
|
||||
): Promise<void> {
|
||||
if (transition.result == null) return
|
||||
const promise = new Promise<void>((resolve, reject) => {
|
||||
showPopup(
|
||||
process.component.ResultInput,
|
||||
{ type: transition.result?.type, name: transition.result?.name },
|
||||
undefined,
|
||||
(res) => {
|
||||
if (transition.result?._id === undefined) return
|
||||
if (results == null) return
|
||||
for (const result of results) {
|
||||
const promise = new Promise<void>((resolve, reject) => {
|
||||
showPopup(process.component.ResultInput, { type: result.type, name: result.name }, undefined, (res) => {
|
||||
if (result._id === undefined) return
|
||||
if (res?.value !== undefined) {
|
||||
context[transition.result._id] = res.value
|
||||
context[result._id] = res.value
|
||||
resolve()
|
||||
} else {
|
||||
reject(new PlatformError(new Status(Severity.ERROR, process.error.ResultNotProvided, {})))
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
await promise
|
||||
})
|
||||
})
|
||||
await promise
|
||||
}
|
||||
await txop.update(execution, {
|
||||
context
|
||||
})
|
||||
|
||||
@@ -38,6 +38,6 @@ export function processError (
|
||||
return new ProcessError(message, message, props, intlProps, shouldLog)
|
||||
}
|
||||
|
||||
export function parseError (err: ProcessError, transition: Ref<Transition>): ExecutionError {
|
||||
export function parseError (err: ProcessError, transition: Ref<Transition> | undefined): ExecutionError {
|
||||
return { error: err.message, props: err.props, intlProps: err.intlProps, transition }
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@ export interface Transition extends Doc {
|
||||
actions: Step<Doc>[]
|
||||
trigger: Ref<Trigger>
|
||||
triggerParams: Record<string, any>
|
||||
result?: TriggerResult | null
|
||||
rank: Rank
|
||||
}
|
||||
|
||||
@@ -115,13 +114,15 @@ export interface ExecutionError {
|
||||
error: IntlString
|
||||
props: Record<string, any>
|
||||
intlProps: Record<string, IntlString>
|
||||
transition: Ref<Transition>
|
||||
transition: Ref<Transition> | undefined
|
||||
}
|
||||
|
||||
export interface ProcessToDo extends ToDo {
|
||||
execution: Ref<Execution>
|
||||
|
||||
withRollback: boolean
|
||||
|
||||
results?: UserResult[]
|
||||
}
|
||||
|
||||
export type MethodParams<T extends Doc> = {
|
||||
@@ -142,6 +143,7 @@ export interface Step<T extends Doc> {
|
||||
context: StepContext | null
|
||||
methodId: Ref<Method<T>>
|
||||
params: MethodParams<T>
|
||||
results?: UserResult[]
|
||||
}
|
||||
|
||||
export interface StepContext {
|
||||
@@ -149,9 +151,10 @@ export interface StepContext {
|
||||
_class?: Ref<Class<Doc>> // class of the context
|
||||
}
|
||||
|
||||
export interface TriggerResult {
|
||||
export interface UserResult {
|
||||
_id: ContextId // context id
|
||||
name: string
|
||||
key?: string
|
||||
type: Type<any>
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@ import process, {
|
||||
MethodParams,
|
||||
Process,
|
||||
processError,
|
||||
ProcessToDo
|
||||
ProcessToDo,
|
||||
UserResult
|
||||
} from '@hcengineering/process'
|
||||
import { ExecuteResult, ProcessControl, SuccessExecutionContext } from '@hcengineering/server-process'
|
||||
import time, { ToDoPriority } from '@hcengineering/time'
|
||||
@@ -282,7 +283,8 @@ export async function RunSubProcess (
|
||||
export async function CreateToDo (
|
||||
params: MethodParams<ProcessToDo>,
|
||||
execution: Execution,
|
||||
control: ProcessControl
|
||||
control: ProcessControl,
|
||||
results: UserResult[] | undefined
|
||||
): Promise<ExecuteResult> {
|
||||
for (const key in { user: params.user, title: params.title }) {
|
||||
const val = (params as any)[key]
|
||||
@@ -311,7 +313,8 @@ export async function CreateToDo (
|
||||
visibility: 'public',
|
||||
doneOn: null,
|
||||
rank: '',
|
||||
withRollback: params.withRollback ?? false
|
||||
withRollback: params.withRollback ?? false,
|
||||
results
|
||||
},
|
||||
id
|
||||
)
|
||||
|
||||
@@ -128,7 +128,6 @@ export async function OnProcessToDoClose (txes: Tx[], control: TriggerControl):
|
||||
}
|
||||
|
||||
export async function OnExecutionCreate (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
if (tx._class !== core.class.TxCreateDoc) continue
|
||||
const createTx = tx as TxCreateDoc<Execution>
|
||||
@@ -143,11 +142,10 @@ export async function OnExecutionCreate (txes: Tx[], control: TriggerControl): P
|
||||
control
|
||||
)
|
||||
}
|
||||
return res
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnProcessToDoRemove (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
if (tx._class !== core.class.TxRemoveDoc) continue
|
||||
const removeTx = tx as TxRemoveDoc<ProcessToDo>
|
||||
@@ -165,11 +163,10 @@ export async function OnProcessToDoRemove (txes: Tx[], control: TriggerControl):
|
||||
control
|
||||
)
|
||||
}
|
||||
return res
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnExecutionContinue (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
const res: Tx[] = []
|
||||
for (const tx of txes) {
|
||||
if (tx._class !== core.class.TxUpdateDoc) continue
|
||||
if (tx.space !== core.space.Tx) continue
|
||||
@@ -192,7 +189,7 @@ export async function OnExecutionContinue (txes: Tx[], control: TriggerControl):
|
||||
control
|
||||
)
|
||||
}
|
||||
return res
|
||||
return []
|
||||
}
|
||||
|
||||
export async function OnProcessRemove (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
|
||||
@@ -282,30 +279,6 @@ async function syncContext (control: TriggerControl, _process: Process): Promise
|
||||
let changed = false
|
||||
let index = 1
|
||||
for (const transition of transitions) {
|
||||
if (transition.result?._id != null) {
|
||||
exists.add(transition.result._id)
|
||||
const context = _process.context[transition.result._id]
|
||||
changed = true
|
||||
const ctx: SelectedExecutionContext = {
|
||||
type: 'context',
|
||||
id: transition.result._id,
|
||||
key: ''
|
||||
}
|
||||
const parentType =
|
||||
transition.result.type._class === core.class.ArrOf
|
||||
? (transition.result.type as ArrOf<Doc>).of
|
||||
: transition.result.type
|
||||
const _class = parentType._class === core.class.RefTo ? (parentType as RefTo<Doc>).to : parentType._class
|
||||
_process.context[transition.result._id] = {
|
||||
name: context?.name ?? transition.result.name,
|
||||
isResult: true,
|
||||
type: transition.result.type,
|
||||
_class,
|
||||
index: index++,
|
||||
producer: transition._id,
|
||||
value: ctx
|
||||
}
|
||||
}
|
||||
for (const action of transition.actions) {
|
||||
if (action.context != null) {
|
||||
exists.add(action.context._id)
|
||||
@@ -328,6 +301,29 @@ async function syncContext (control: TriggerControl, _process: Process): Promise
|
||||
}
|
||||
}
|
||||
}
|
||||
if (action.results != null) {
|
||||
for (const result of action.results) {
|
||||
exists.add(result._id)
|
||||
const context = _process.context[result._id]
|
||||
changed = true
|
||||
const ctx: SelectedExecutionContext = {
|
||||
type: 'context',
|
||||
id: result._id,
|
||||
key: ''
|
||||
}
|
||||
const parentType = result.type._class === core.class.ArrOf ? (result.type as ArrOf<Doc>).of : result.type
|
||||
const _class = parentType._class === core.class.RefTo ? (parentType as RefTo<Doc>).to : parentType._class
|
||||
_process.context[result._id] = {
|
||||
name: context?.name ?? result.name,
|
||||
isResult: true,
|
||||
type: result.type,
|
||||
_class,
|
||||
index: index++,
|
||||
producer: transition._id,
|
||||
value: ctx
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const newContext: Record<ContextId, ProcessContext> = {}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Card } from '@hcengineering/card'
|
||||
import { Doc, MeasureContext, PersonId, Ref, Tx, TxOperations } from '@hcengineering/core'
|
||||
import { Execution, ExecutionError, MethodParams, Trigger } from '@hcengineering/process'
|
||||
import { Execution, ExecutionError, MethodParams, Trigger, UserResult } from '@hcengineering/process'
|
||||
|
||||
export type ExecuteFunc = (
|
||||
params: MethodParams<Doc>,
|
||||
execution: Execution,
|
||||
control: ProcessControl
|
||||
control: ProcessControl,
|
||||
results: UserResult[] | undefined
|
||||
) => Promise<ExecuteResult>
|
||||
|
||||
export type ExecuteResult = SuccessExecutionResult | ExecutionError
|
||||
|
||||
@@ -40,7 +40,8 @@ import process, {
|
||||
ProcessToDo,
|
||||
State,
|
||||
Step,
|
||||
Transition
|
||||
Transition,
|
||||
UserResult
|
||||
} from '@hcengineering/process'
|
||||
import serverProcess, {
|
||||
ExecuteResult,
|
||||
@@ -165,7 +166,83 @@ async function findTransitions (
|
||||
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
|
||||
const todo = record.context.todo as ProcessToDo
|
||||
if (todo === undefined) return
|
||||
if (todo.results == null) return
|
||||
const results = todo.results.filter((it) => it.key != null)
|
||||
if (results.length === 0) return
|
||||
const res = await executeResultSet(control, results, execution)
|
||||
if (!isError(res)) {
|
||||
for (const tx of res.txes) {
|
||||
const updateTx = tx as TxUpdateDoc<Doc>
|
||||
if (updateTx._class === core.class.TxUpdateDoc && updateTx.objectId === execution.card) {
|
||||
const updatedCard = TxProcessor.updateDoc2Doc(
|
||||
control.client.getHierarchy().clone(control.cache.get(execution.card)),
|
||||
updateTx
|
||||
)
|
||||
control.cache.set(execution.card, updatedCard)
|
||||
}
|
||||
}
|
||||
for (const tx of res.txes) {
|
||||
const timeout = setTimeout(() => {
|
||||
control.ctx.warn('TX HANG', tx)
|
||||
}, 30000)
|
||||
await control.client.tx(tx)
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
if (res.rollback != null) {
|
||||
execution.rollback = execution.rollback.length > 30 ? execution.rollback.slice(-30) : execution.rollback
|
||||
await control.client.update(execution, { rollback: execution.rollback })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function executeResultSet (
|
||||
control: ProcessControl,
|
||||
results: UserResult[],
|
||||
execution: Execution
|
||||
): Promise<ExecuteResult> {
|
||||
try {
|
||||
const method = control.client.getModel().findObject(process.method.UpdateCard)
|
||||
if (method === undefined) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: process.method.UpdateCard }, {}, true)
|
||||
}
|
||||
const impl = control.client.getHierarchy().as(method, serverProcess.mixin.MethodImpl) as MethodImpl<any>
|
||||
if (impl === undefined) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: process.method.UpdateCard }, {}, true)
|
||||
}
|
||||
const params = {}
|
||||
for (const res of results) {
|
||||
if (res.key == null) continue
|
||||
;(params as any)[res.key] = execution.context[res._id]
|
||||
}
|
||||
if (!control.cache.has(execution.card)) {
|
||||
const card = await control.client.findOne(cardPlugin.class.Card, { _id: execution.card })
|
||||
if (card !== undefined) {
|
||||
control.cache.set(execution.card, card)
|
||||
}
|
||||
}
|
||||
const f = await getResource(impl.func)
|
||||
const res = await f(params, execution, control, undefined)
|
||||
return res
|
||||
} catch (err) {
|
||||
if (err instanceof ProcessError) {
|
||||
if (err.shouldLog) {
|
||||
control.ctx.error(err.message, { props: err.props })
|
||||
}
|
||||
return parseError(err, undefined)
|
||||
} else {
|
||||
const errorId = generateId()
|
||||
control.ctx.error(err instanceof Error ? err.message : String(err), { errorId })
|
||||
return parseError(processError(process.error.InternalServerError, { errorId }), undefined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function processExecution (control: ProcessControl, record: ProcessMessage, execution: Execution): Promise<void> {
|
||||
await checkToDoResult(control, record, execution)
|
||||
const transition = await findTransitions(control, record, execution)
|
||||
if (transition !== undefined) {
|
||||
await execute(execution, transition, control)
|
||||
@@ -476,7 +553,7 @@ async function executeAction<T extends Doc> (
|
||||
if (impl === undefined) throw processError(process.error.MethodNotFound, { methodId: action.methodId }, {}, true)
|
||||
const params = await fillParams(action.params, execution, control)
|
||||
const f = await getResource(impl.func)
|
||||
const res = await f(params, execution, control)
|
||||
const res = await f(params, execution, control, action.results)
|
||||
if (!isError(res) && action.context?._id != null && res.context != null) {
|
||||
execution.context[action.context._id] =
|
||||
res.context.length === 1 ? res.context[0]._id : res.context.map((it) => it._id)
|
||||
|
||||
Reference in New Issue
Block a user