Add subprocess match trigger (#10101)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2025-10-16 12:17:51 +07:00
committed by GitHub
parent c3d15734eb
commit db3bd4d39e
28 changed files with 422 additions and 30 deletions
@@ -16,10 +16,11 @@
<script lang="ts">
import { AnyAttribute } from '@hcengineering/core'
import { Context, Process } from '@hcengineering/process'
import { Component, DropdownLabelsIntl } from '@hcengineering/ui'
import { Component } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { buildResult, Mode, ModeId, Modes, parseValue } from '../../query'
import BaseCriteriaEditor from './BaseCriteriaEditor.svelte'
import ModeSelector from './ModeSelector.svelte'
export let readonly: boolean
export let value: any
@@ -32,7 +33,6 @@
let [val, selectedMode] = parseValue(modesValues, value)
$: mode = selectedMode.id
const dispatch = createEventDispatcher()
$: [val, selectedMode] = parseValue(modesValues, value)
@@ -42,27 +42,18 @@
value = result
dispatch('change', result)
}
function changeMode (e: CustomEvent): void {
const editorChanged = e.detail
if (editorChanged) {
val = undefined
}
changeResult(val)
}
</script>
<div class="flex-row-center flex-gap-4">
<DropdownLabelsIntl
items={modesValues}
selected={mode}
disabled={readonly}
minW0={false}
kind={'no-border'}
width={'100%'}
on:selected={(e) => {
mode = e.detail
const prevEditor = (selectedMode.editor ?? selectedMode.withoutEditor) ? null : undefined
selectedMode = modesValues.find((m) => m.id === mode) ?? modesValues[0]
const newEditor = (selectedMode.editor ?? selectedMode.withoutEditor) ? null : undefined
if (prevEditor !== newEditor) {
val = undefined
}
changeResult(val)
}}
/>
<ModeSelector bind:selectedMode modes={modesValues} {readonly} on:change={changeMode} />
{#if selectedMode.editor}
<div class="w-full">
<Component
@@ -0,0 +1,44 @@
<!--
// 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 { DropdownLabelsIntl } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { Mode } from '../../query'
export let selectedMode: Mode
export let readonly: boolean
export let modes: Mode[] = []
$: mode = selectedMode.id
const dispatch = createEventDispatcher()
</script>
<DropdownLabelsIntl
items={modes}
selected={mode}
disabled={readonly}
minW0={false}
kind={'no-border'}
width={'100%'}
on:selected={(e) => {
mode = e.detail
const prevEditor = (selectedMode.editor ?? selectedMode.withoutEditor) ? null : undefined
selectedMode = modes.find((m) => m.id === mode) ?? modes[0]
const newEditor = (selectedMode.editor ?? selectedMode.withoutEditor) ? null : undefined
dispatch('change', prevEditor !== newEditor)
}}
/>
@@ -0,0 +1,73 @@
<!--
// 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 { Ref, SortingOrder } from '@hcengineering/core'
import { Process, State } from '@hcengineering/process'
import { Dropdown, DropdownLabels } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import plugin from '../../plugin'
import { buildResult, Modes, parseValue } from '../../query'
import ModeSelector from './ModeSelector.svelte'
import { ObjectBox } from '@hcengineering/view-resources'
import { createQuery } from '@hcengineering/presentation'
export let readonly: boolean
export let target: Ref<Process> | undefined
export let value: any
const dispatch = createEventDispatcher()
const modes = [Modes.ArrayAll, Modes.ArrayAny, Modes.ArrayNotIncludes]
let [val, selectedMode] = parseValue(modes, value)
$: [val, selectedMode] = parseValue(modes, value)
function changeMode (): void {
value = buildResult(selectedMode, val)
dispatch('change', value)
}
function change (e: CustomEvent): void {
val = e.detail ?? []
value = buildResult(selectedMode, val)
dispatch('change', value)
}
const query = createQuery()
let states: State[] = []
$: query.query(
plugin.class.State,
{ process: target },
(items) => {
states = items
},
{ sort: { rank: SortingOrder.Ascending } }
)
$: selected = states.filter((state) => val?.includes(state._id))?.map((p) => p._id)
</script>
<div class="flex-row-center flex-gap-4">
<ModeSelector bind:selectedMode {modes} {readonly} on:change={changeMode} />
<DropdownLabels
useFlexGrow
width={'100%'}
multiselect={true}
items={states.map((state) => ({ id: state._id, label: state.title }))}
{selected}
on:selected={change}
/>
</div>
@@ -0,0 +1,106 @@
<!--
// 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 { Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Process, State } 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 SubProcessStateCriteria from '../criterias/SubProcessStateCriteria.svelte'
export let readonly: boolean
export let process: Process
export let params: Record<string, any>
const dispatch = createEventDispatcher()
const client = getClient()
$: possibleProcesses = getPossibleProcesses(process)
function getPossibleProcesses (proc: Process): Ref<Process>[] {
const res = new Set<Ref<Process>>()
const transitions = client.getModel().findAllSync(plugin.class.Transition, { process: proc._id })
for (const transition of transitions) {
for (const action of transition.actions ?? []) {
if (action.methodId === plugin.method.RunSubProcess) {
if (action.params._id) {
res.add(action.params._id as Ref<Process>)
}
}
}
}
return [...res]
}
$: processes = client.getModel().findAllSync(plugin.class.Process, { _id: { $in: possibleProcesses } })
$: items = processes.map((it) => ({
id: it._id,
label: it.name
}))
let selectedProcessId: Ref<Process> | undefined = params.process
$: selectedProcessId = params.process
$: selected = selectedProcessId !== undefined ? items.find((it) => it.id === selectedProcessId)?.id : undefined
function changeProcess (e: CustomEvent<any>): void {
if (readonly || e.detail == null) return
selectedProcessId = e.detail
params = {
process: selectedProcessId
}
dispatch('change', { params })
}
function changeStateCriteria (e: CustomEvent<any>): void {
if (readonly || e.detail == null) return
params = {
...params,
currentState: e.detail
}
dispatch('change', { params })
}
const modes: ModeId[] = ['ArrayAll', 'ArrayAny', 'ArrayNotIncludes']
</script>
<div class="editor-grid">
<Label label={plugin.string.Process} />
<DropdownLabels
disabled={readonly}
autoSelect={false}
enableSearch={false}
width={'100%'}
{items}
{selected}
placeholder={plugin.string.Process}
on:selected={changeProcess}
/>
{#if selectedProcessId !== undefined}
<Label label={plugin.string.Step} />
<SubProcessStateCriteria
{readonly}
target={selectedProcessId}
value={params.currentState}
on:change={changeStateCriteria}
/>
{/if}
</div>
@@ -0,0 +1,52 @@
<!--
// 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 { getClient } from '@hcengineering/presentation'
import { Process, State } from '@hcengineering/process'
import { Label } from '@hcengineering/ui'
import plugin from '../../plugin'
export let params: Record<string, any>
const client = getClient()
let selectedProcess: Process | undefined
$: if (params.process) {
selectedProcess = client.getModel().findAllSync(plugin.class.Process, { _id: params.process })[0]
} else {
selectedProcess = undefined
}
let states: State[] = []
$: if (params.process) {
states = client.getModel().findAllSync(plugin.class.State, { process: params.process })
} else {
states = []
}
</script>
{#if selectedProcess}
: <Label label={plugin.string.Process} />
<span class="overflow-label">{selectedProcess.name}</span>
{/if}
<style lang="scss">
.overflow-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>