Fix processes

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2025-05-21 16:14:27 +05:00
parent ee97398277
commit 2e6a9411ec
No known key found for this signature in database
GPG Key ID: 211936D31001B31C
9 changed files with 119 additions and 148 deletions

2
.vscode/launch.json vendored
View File

@ -97,7 +97,7 @@
"MODEL_JSON": "${workspaceRoot}/models/all/bundle/model.json",
// "SERVER_PROVIDER":"uweb"
"SERVER_PROVIDER": "ws",
"MODEL_VERSION": "0.7.75",
"MODEL_VERSION": "0.7.110",
// "VERSION": "0.6.289",
"ELASTIC_INDEX_NAME": "local_storage_index",
"UPLOAD_URL": "/files",

View File

@ -1,9 +1,25 @@
<script lang="ts">
import { Doc } from '@hcengineering/core'
import presentation, { getClient } from '@hcengineering/presentation'
import { Process, Transition } from '@hcengineering/process'
import { Process, Step, StepId, Transition } from '@hcengineering/process'
import { clearSettingsStore } from '@hcengineering/setting-resources'
import { ButtonIcon, Component, IconDelete, Label, Modal } from '@hcengineering/ui'
import {
Button,
ButtonIcon,
Component,
DropdownIntlItem,
DropdownLabelsPopupIntl,
getEventPositionElement,
IconAdd,
IconDelete,
Label,
Modal,
showPopup
} from '@hcengineering/ui'
import plugin from '../../plugin'
import { initState } from '../../utils'
import ActionPresenter from './ActionPresenter.svelte'
import StepEditor from './StepEditor.svelte'
import TransitionPresenter from './TransitionPresenter.svelte'
export let readonly: boolean
@ -14,11 +30,8 @@
const client = getClient()
const from = client.getModel().findObject(transition.from)
const to = transition.to === null ? null : client.getModel().findObject(transition.to)
async function save (): Promise<void> {
await client.update(transition, { triggerParams: params })
await client.update(transition, { triggerParams: params, actions: transition.actions })
clearSettingsStore()
}
@ -31,7 +44,28 @@
params = e.detail
}
function addAction (e: MouseEvent): void {
const items: DropdownIntlItem[] = client
.getModel()
.findAllSync(plugin.class.Method, {})
.map((x) => ({
id: x._id,
label: x.label
}))
showPopup(DropdownLabelsPopupIntl, { items }, getEventPositionElement(e), async (res) => {
if (res !== undefined) {
const step = await initState(res)
transition.actions.push(step)
transition.actions = transition.actions
await client.update(transition, { actions: transition.actions })
}
})
}
$: trigger = client.getModel().findObject(transition.trigger)
let expanded = new Set<StepId>()
</script>
<Modal
@ -48,19 +82,47 @@
<ButtonIcon icon={IconDelete} size={'small'} kind={'tertiary'} on:click={remove} />
{/if}
</svelte:fragment>
{#if trigger !== undefined}
<div class="content clear-mins">
<div class="header">
<div class="fs-title title text-xl">
<Label label={trigger.label} />
</div>
<div class="content clear-mins">
<div class="header">
<div class="fs-title title text-xl">
<TransitionPresenter {transition} />
</div>
</div>
{#if trigger !== undefined}
<div class="flex-col-center text-lg">
<Label label={trigger.label} />
</div>
{#if trigger.editor !== undefined}
<Component is={trigger.editor} props={{ process, params, readonly }} on:change={change} />
{/if}
{/if}
<div class="divider" />
<div class="flex-col flex-gap-2">
{#each transition.actions as action}
<Button
justify="left"
size="large"
kind="regular"
width="100%"
on:click={() => {
if (readonly) return
expanded.has(action._id) ? expanded.delete(action._id) : expanded.add(action._id)
expanded = expanded
}}
>
<svelte:fragment slot="content">
<ActionPresenter {action} {process} {readonly} />
</svelte:fragment>
</Button>
{#if expanded.has(action._id)}
<StepEditor bind:step={action} {process} withoutHeader />
{/if}
{/each}
{#if !readonly}
<Button kind={'ghost'} width={'100%'} icon={IconAdd} label={plugin.string.AddAction} on:click={addAction} />
{/if}
</div>
{/if}
</div>
</Modal>
<style lang="scss">
@ -71,4 +133,9 @@
.title {
padding-bottom: 1rem;
}
.divider {
border-bottom: 1px solid var(--theme-divider-color);
margin: 1rem 0;
}
</style>

View File

@ -46,7 +46,7 @@
{key}
{allowRemove}
object={params}
on:update={(e) => {
on:change={(e) => {
change(e, key)
}}
on:remove

View File

@ -19,6 +19,7 @@
import { AnySvelteComponent } from '@hcengineering/ui'
import { getContext } from '../../utils'
import ProcessAttribute from '../ProcessAttribute.svelte'
import { createEventDispatcher } from 'svelte'
export let process: Process
export let _class: Ref<Class<Doc>>
@ -28,6 +29,7 @@
const client = getClient()
const hierarchy = client.getHierarchy()
const dispatch = createEventDispatcher()
function onChange (value: any | undefined): void {
if (value === undefined) {
@ -36,6 +38,7 @@
} else {
;(object as any)[key] = value
}
dispatch('change', { value })
}
$: value = object[key]

View File

@ -22,6 +22,7 @@
export let step: Step<Doc>
export let process: Process
export let withoutHeader: boolean = false
const client = getClient()
const dispatch = createEventDispatcher()
@ -44,16 +45,18 @@
{#if method !== undefined}
<div class="content clear-mins">
<div class="header">
<div class="fs-title title text-xl">
<Label label={method.label} />
</div>
{#if method.description}
<div class="descr">
<Label label={method.description} />
{#if !withoutHeader}
<div class="header">
<div class="fs-title title text-xl">
<Label label={method.label} />
</div>
{/if}
</div>
{#if method.description}
<div class="descr">
<Label label={method.description} />
</div>
{/if}
</div>
{/if}
{#if method.editor !== undefined}
<Component is={method.editor} props={{ step, process }} on:change={change} />
{/if}

View File

@ -13,31 +13,14 @@
// limitations under the License.
-->
<script lang="ts">
import { createQuery, getClient, MessageBox } from '@hcengineering/presentation'
import { Process, Step, Transition } from '@hcengineering/process'
import {
Button,
ButtonIcon,
DropdownIntlItem,
DropdownLabelsPopupIntl,
eventToHTMLElement,
Icon,
IconAdd,
IconMoreV,
Label,
Menu,
showPopup
} from '@hcengineering/ui'
import view from '@hcengineering/view'
import plugin from '../../plugin'
import { initState } from '../../utils'
import ActionPresenter from './ActionPresenter.svelte'
import TriggerPresenter from './TriggerPresenter.svelte'
import AsideStepEditor from './AsideStepEditor.svelte'
import { Doc } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Process, Transition } from '@hcengineering/process'
import { settingsStore } from '@hcengineering/setting-resources'
import { Button, Icon } from '@hcengineering/ui'
import plugin from '../../plugin'
import AsideTransitionEditor from './AsideTransitionEditor.svelte'
import TransitionPresenter from './TransitionPresenter.svelte'
import TriggerPresenter from './TriggerPresenter.svelte'
export let transition: Transition
export let process: Process
@ -46,8 +29,6 @@
const client = getClient()
let collapsed: boolean = true
let from = client.getModel().findObject(transition.from)
let to = transition.to === null ? null : client.getModel().findObject(transition.to)
@ -57,64 +38,13 @@
to = transition.to === null ? null : client.getModel().findObject(transition.to)
})
function editAction (step: Step<Doc>): void {
$settingsStore = {
id: transition._id,
component: AsideStepEditor,
props: { readonly, process, step, _id: transition._id }
}
}
let hovered: boolean = false
async function onMenuClick (ev: MouseEvent): Promise<void> {
const actions = [
{
label: view.string.Delete,
icon: view.icon.Delete,
action: async () => {
showPopup(MessageBox, {
label: plugin.string.DeleteTransition,
message: plugin.string.DeleteTransitionConfirm,
action: async () => {
await client.remove(transition)
}
})
}
}
]
hovered = true
showPopup(Menu, { actions }, eventToHTMLElement(ev), () => {
hovered = false
})
}
function add (e: MouseEvent): void {
const items: DropdownIntlItem[] = client
.getModel()
.findAllSync(plugin.class.Method, {})
.map((x) => ({
id: x._id,
label: x.label
}))
showPopup(DropdownLabelsPopupIntl, { items }, eventToHTMLElement(e), async (res) => {
if (res !== undefined) {
const step = await initState(res)
const length = transition.actions.push(step)
await client.update(transition, { actions: transition.actions })
editAction(step)
}
})
}
function edit (): void {
$settingsStore = { id: transition._id, component: AsideTransitionEditor, props: { readonly, process, transition } }
}
</script>
<div class="w-full container" class:hovered>
<div class:expand={!collapsed} class="innerContainer">
<div class="w-full container">
<div class="innerContainer">
<Button on:click={edit} kind="ghost" width={'100%'} padding={'0'} size="small">
<div class="flex-between w-full" slot="content">
<div class="flex-row-center flex-gap-2">
@ -122,49 +52,13 @@
<TransitionPresenter {transition} {direction} />
</div>
<div class="flex-row-center">
{#if !readonly}
<div class="tool">
<ButtonIcon kind="tertiary" icon={IconMoreV} size={'min'} on:click={onMenuClick} />
</div>
{/if}
{#if transition.to !== null}
<Button
on:click={() => {
collapsed = !collapsed
}}
padding={'0.25rem'}
kind="ghost"
size="small"
>
<div class="flex-row-center" slot="content">
<Icon icon={plugin.icon.Process} size="small" />
{transition.actions.length}
</div>
</Button>
<Icon icon={plugin.icon.Process} size="small" />
{transition.actions.length}
{/if}
</div>
</div>
</Button>
{#if !collapsed}
{#each transition.actions as action}
<Button
justify="left"
kind="ghost"
width="100%"
on:click={() => {
if (readonly) return
editAction(action)
}}
>
<svelte:fragment slot="content">
<ActionPresenter {action} {process} {readonly} />
</svelte:fragment>
</Button>
{/each}
{#if !readonly}
<Button label={plugin.string.AddAction} icon={IconAdd} kind={'ghost'} width={'100%'} on:click={add} />
{/if}
{/if}
</div>
</div>

View File

@ -318,13 +318,17 @@ export function showDoneQuery (value: any, query: DocumentQuery<Doc>): DocumentQ
export async function continueExecution (value: Execution): Promise<void> {
if (value.error == null) return
const transition = value.error[0].transition
if (transition == null) return
const client = getClient()
const _transition = client.getModel().findObject(transition)
if (_transition === undefined) return
const targetState = _transition.to
const context = targetState == null ? value.context : await getNextStateUserInput(value, targetState, value.context)
let context = value.context
const transition = value.error[0].transition
if (transition == null) {
context = await newExecutionUserInput(value.process, context)
} else {
const _transition = client.getModel().findObject(transition)
if (_transition === undefined) return
const targetState = _transition.to
context = targetState == null ? value.context : await getNextStateUserInput(value, targetState, value.context)
}
await client.update(value, { status: ExecutionStatus.Active, context })
}

View File

@ -17,5 +17,5 @@ import { ExecutionError } from '@hcengineering/process'
import { ExecuteResult } from '@hcengineering/server-process'
export function isError (value: ExecuteResult | any): value is ExecutionError {
return (value as ExecutionError).error !== undefined
return (value as ExecutionError)?.error !== undefined
}

View File

@ -559,7 +559,7 @@ export async function CreateToDo (
execution: Execution,
control: TriggerControl
): Promise<ExecuteResult | undefined> {
if (params.user === undefined || params.state === undefined || params.title === undefined) return
if (params.user === undefined || params.title === undefined) return
const res: Tx[] = []
const rollback: Tx[] = []
const id = generateId<ProcessToDo>()
@ -573,7 +573,7 @@ export async function CreateToDo (
collection: 'todos',
workslots: 0,
execution: execution._id,
state: params.state,
state: execution.currentState,
title: params.title,
user: params.user,
description: params.description ?? '',