Process result and context (#9782)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2025-09-04 18:20:52 +05:00
committed by GitHub
parent 83313bd679
commit dcba4ec1bf
22 changed files with 327 additions and 165 deletions
@@ -19,7 +19,7 @@
import {
Context,
Process,
ProcessContext,
ProcessExecutionContext,
ProcessFunction,
RelatedContext,
SelectedContext
@@ -83,7 +83,7 @@
dispatch('close')
}
function onProcessContext (ctx: ProcessContext): void {
function onProcessContext (ctx: ProcessExecutionContext): void {
onSelect(ctx.value)
dispatch('close')
}
@@ -155,15 +155,30 @@
<div class="menu-separator" />
{/if}
{#if processContext.length > 0}
{#each processContext as f}
<button
on:click={() => {
onProcessContext(f)
}}
class="menu-item"
>
<ExecutionContextPresenter {process} contextValue={f.value} />
</button>
{#each processContext as pc}
{#if pc.attributes.length > 0}
<Submenu
component={ExecutionContextPresenter}
props={{
context: pc,
target: attribute,
contextValue: pc.value,
process,
onSelect: onClick
}}
options={{ component: plugin.component.ExecutionContextSelector }}
withHover
/>
{:else}
<button
on:click={() => {
onProcessContext(pc)
}}
class="menu-item"
>
<ExecutionContextPresenter {process} contextValue={pc.value} />
</button>
{/if}
{/each}
<div class="menu-separator" />
{/if}
@@ -13,18 +13,28 @@
// limitations under the License.
-->
<script lang="ts">
import { Process, SelectedExecutonContext } from '@hcengineering/process'
import { Context, Process, SelectedExecutionContext } from '@hcengineering/process'
import ui, { Label } from '@hcengineering/ui'
import ProcessContextPresenter from '../contextEditors/ProcessContextPresenter.svelte'
import { getClient } from '@hcengineering/presentation'
export let contextValue: SelectedExecutonContext
export let contextValue: SelectedExecutionContext
export let process: Process
const client = getClient()
$: ctx = process.context[contextValue.id]
$: attr = contextValue.key !== '' ? client.getHierarchy().findAttribute(ctx._class, contextValue.key) : undefined
</script>
{#if ctx !== undefined}
<ProcessContextPresenter context={ctx} />
{#if attr !== undefined}
<span class="attr">
<Label label={attr.label} />
</span>
{/if}
{:else}
<Label label={ui.string.NotSelected} />
{/if}
@@ -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 { AnyAttribute } from '@hcengineering/core'
import { ProcessExecutionContext, SelectedContext } from '@hcengineering/process'
import { Label, resizeObserver, Scroller } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { getValueReduceFunc } from '../../utils'
export let context: ProcessExecutionContext
export let target: AnyAttribute
export let onSelect: (val: SelectedContext) => void
const dispatch = createEventDispatcher()
const elements: HTMLButtonElement[] = []
const keyDown = (event: KeyboardEvent, index: number): void => {
if (event.key === 'ArrowDown') {
elements[(index + 1) % elements.length].focus()
}
if (event.key === 'ArrowUp') {
elements[(elements.length + index - 1) % elements.length].focus()
}
if (event.key === 'ArrowLeft') {
dispatch('close')
}
}
function onAttribute (attr: AnyAttribute): void {
const valueFunc = getValueReduceFunc(attr, target)
onSelect({
type: 'context',
key: attr.name,
id: context.context,
functions: valueFunc !== undefined ? [{ func: valueFunc, props: {} }] : []
})
}
</script>
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
<div class="menu-space" />
<Scroller>
{#each context.attributes as attr, i}
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<button
bind:this={elements[i]}
on:keydown={(event) => {
keyDown(event, i)
}}
on:mouseover={() => {
elements[i]?.focus()
}}
on:click={() => {
onAttribute(attr)
}}
class="menu-item"
>
<span class="overflow-label pr-1">
<Label label={attr.label} />
</span>
</button>
{/each}
</Scroller>
<div class="menu-space" />
</div>
@@ -16,7 +16,6 @@
import { getClient } from '@hcengineering/presentation'
import { ProcessContext } from '@hcengineering/process'
import { Label } from '@hcengineering/ui'
import process from '../../plugin'
export let context: ProcessContext
@@ -21,7 +21,7 @@
Process,
ProcessToDo,
SelectedContext,
SelectedExecutonContext
SelectedExecutionContext
} from '@hcengineering/process'
import ui, {
Button,
@@ -52,14 +52,14 @@
const client = getClient()
function getContext (value: string): SelectedExecutonContext | undefined {
function getContext (value: string): SelectedExecutionContext | undefined {
const context = parseContext(value)
if (context !== undefined && isExecutionContext(context)) {
return context
}
}
function isExecutionContext (context: SelectedContext): context is SelectedExecutonContext {
function isExecutionContext (context: SelectedContext): context is SelectedExecutionContext {
return context.type === 'context'
}
@@ -15,7 +15,7 @@
<script lang="ts">
import core, { Ref } from '@hcengineering/core'
import { Card, createQuery, getClient } from '@hcengineering/presentation'
import { Process, State, Trigger } from '@hcengineering/process'
import { Process, State, Trigger, TriggerResult } from '@hcengineering/process'
import { Component, Dropdown, DropdownIntlItem, DropdownLabelsIntl, Label, ListItem } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import plugin from '../../plugin'
@@ -25,6 +25,7 @@
const dispatch = createEventDispatcher()
let to: Ref<State> | undefined = undefined
let states: State[] = []
let result: TriggerResult | null = null
const query = createQuery()
query.query(plugin.class.State, { process: process._id }, (res) => {
@@ -66,13 +67,19 @@
trigger,
triggerParams: params,
process: process._id,
result,
actions: []
})
dispatch('close')
}
function change (e: CustomEvent<Record<string, any>>): void {
params = e.detail
if (e.detail?.params !== undefined) {
params = e.detail.params
}
if (e.detail?.result !== undefined) {
result = e.detail.result
}
}
</script>
@@ -111,13 +118,17 @@
items={triggersItems}
bind:selected={trigger}
label={plugin.string.Trigger}
on:selected={() => {
params = {}
result = null
}}
justify={'left'}
width={'100%'}
kind={'no-border'}
/>
</div>
{#if triggerValue?.editor}
<Component is={triggerValue.editor} props={{ process, params }} on:change={change} />
<Component is={triggerValue.editor} props={{ process, params, result }} on:change={change} />
{/if}
</Card>
@@ -20,11 +20,12 @@
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 })
await client.update(transition, { triggerParams: params, trigger: selectedTrigger, result })
clearSettingsStore()
}
@@ -34,7 +35,12 @@
}
function change (e: CustomEvent<Record<string, any>>): void {
params = e.detail
if (e.detail?.params !== undefined) {
params = e.detail.params
}
if (e.detail?.result !== undefined) {
result = e.detail.result
}
}
let selectedTrigger: Ref<Trigger> = transition.trigger
@@ -79,7 +85,7 @@
</div>
{#if trigger.editor !== undefined}
<div class="editor">
<Component is={trigger.editor} props={{ process, params, readonly }} on:change={change} />
<Component is={trigger.editor} props={{ process, params, result, readonly }} on:change={change} />
</div>
{/if}
{/if}
@@ -35,7 +35,7 @@
function change (e: CustomEvent<any>): void {
if (readonly || e.detail == null) return
params = e.detail
dispatch('change', params)
dispatch('change', { params })
}
function getKeys (_class: Ref<Class<MasterTag>>): AnyAttribute[] {
@@ -87,7 +87,7 @@
keys = keys.filter((k) => k !== key)
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete (params as any)[key]
dispatch('change', params)
dispatch('change', { params })
}
}
</script>
@@ -16,33 +16,23 @@
<script lang="ts">
import { Doc } from '@hcengineering/core'
import { Process, Step } from '@hcengineering/process'
import ProcessContextPresenter from '../contextEditors/ProcessContextPresenter.svelte'
import { Label } from '@hcengineering/ui'
import processPlugin from '../../plugin'
import ProcessContextPresenter from '../contextEditors/ProcessContextPresenter.svelte'
export let process: Process
export let step: Step<Doc>
$: currentContext = step.context ? process.context[step.context._id] : undefined
$: currentResultContext = step.result ? process.context[step.result._id] : undefined
</script>
{#if currentContext || currentResultContext}
<Label label={processPlugin.string.Result} />
{/if}
{#if currentContext}
<Label label={processPlugin.string.Result} />
<div class="container">
<ProcessContextPresenter context={currentContext} />
</div>
{/if}
{#if currentResultContext}
<div class="container">
<ProcessContextPresenter context={currentResultContext} />
</div>
{/if}
<style lang="scss">
.container {
padding: 0.25rem 0.5rem;
@@ -13,9 +13,9 @@
// limitations under the License.
-->
<script lang="ts">
import core, { Class, Doc, PropertyType, Ref, Type } from '@hcengineering/core'
import core, { Class, PropertyType, Ref, Type } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Step } from '@hcengineering/process'
import { TriggerResult } from '@hcengineering/process'
import setting from '@hcengineering/setting-resources/src/plugin'
import {
AnyComponent,
@@ -32,10 +32,10 @@
import plugin from '../../plugin'
import { generateContextId } from '../../utils'
export let step: Step<Doc>
export let result: TriggerResult | null
let type: Type<any> | undefined | null = step.result?.type
let name: string = step.result?.name ?? ''
let type: Type<any> | undefined | null = result?.type
let name: string = result?.name ?? ''
let is: AnyComponent | undefined
const client = getClient()
const hierarchy = client.getHierarchy()
@@ -43,15 +43,15 @@
function update (): void {
if (type == null) {
step.result = undefined
result = null
} else {
step.result = {
result = {
_id: generateContextId(),
name,
type
}
}
dispatch('change', step)
dispatch('change', result)
}
function getTypes (): DropdownIntlItem[] {
@@ -91,9 +91,9 @@
}
function handleNameChange (e: any): void {
if (step.result != null) {
step.result.name = name
dispatch('change', step)
if (result != null) {
result.name = name
dispatch('change', result)
}
}
@@ -110,7 +110,7 @@
<div class="grid">
<Label label={plugin.string.Result} />
<Toggle
on={step.result !== undefined}
on={result != null}
on:change={(e) => {
changeRequired(e.detail)
}}
@@ -150,14 +150,12 @@
<style lang="scss">
.grid {
display: grid;
grid-template-columns: 1fr 1.5fr;
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: 0.25rem 2rem 0;
width: calc(100% - 4rem);
height: min-content;
}
</style>
@@ -36,13 +36,6 @@
}
const keys = ['title', 'user', 'dueDate']
function changeResult (e: CustomEvent<any>): void {
if (e.detail !== undefined) {
step = e.detail
dispatch('change', step)
}
}
</script>
<ParamsEditor _class={plugin.class.ProcessToDo} {process} {keys} {params} on:change={changeParams} />
@@ -60,10 +53,6 @@
}}
/>
</div>
<div class="divider" />
{#key step._id}
<ResultEditor {step} on:change={changeResult} />
{/key}
<style lang="scss">
.divider {
@@ -14,23 +14,32 @@
-->
<script lang="ts">
import { Process } from '@hcengineering/process'
import { Process, TriggerResult } 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()
function change (e: CustomEvent<string>): void {
if (readonly || e.detail == null) return
params._id = e.detail
dispatch('change', params)
dispatch('change', { params })
}
function changeResult (e: CustomEvent<any>): void {
if (e.detail !== undefined) {
result = e.detail
dispatch('change', { result })
}
}
</script>
@@ -38,8 +47,17 @@
<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;
@@ -14,7 +14,7 @@
-->
<script lang="ts">
import { parseContext, Process, SelectedContext, SelectedExecutonContext } from '@hcengineering/process'
import { parseContext, Process, SelectedContext, SelectedExecutionContext } from '@hcengineering/process'
import ui, { Label } from '@hcengineering/ui'
import ExecutionContextPresenter from '../attributeEditors/ExecutionContextPresenter.svelte'
@@ -23,7 +23,7 @@
$: context = getContext(params._id)
function getContext (value: string | undefined): SelectedExecutonContext | undefined {
function getContext (value: string | undefined): SelectedExecutionContext | undefined {
if (value === undefined) return
const context = parseContext(value)
if (context !== undefined && isExecutionContext(context)) {
@@ -31,7 +31,7 @@
}
}
function isExecutionContext (context: SelectedContext): context is SelectedExecutonContext {
function isExecutionContext (context: SelectedContext): context is SelectedExecutionContext {
return context.type === 'context'
}
</script>