mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-23 18:15:03 +02:00
WhenFieldChanges trigger (#9913)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
<!--
|
||||
// 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 } from '@hcengineering/core'
|
||||
import { getAttributePresenterClass, getClient } from '@hcengineering/presentation'
|
||||
import { Process } from '@hcengineering/process'
|
||||
import { Button, eventToHTMLElement, SelectPopup, showPopup } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getCirteriaEditor } from '../../utils'
|
||||
import CriteriasEditor from '../criterias/CriteriasEditor.svelte'
|
||||
|
||||
export let readonly: boolean
|
||||
export let process: Process
|
||||
export let params: Record<string, any>
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
function change (e: CustomEvent<any>): void {
|
||||
if (readonly || e.detail == null) return
|
||||
params = e.detail
|
||||
dispatch('change', { params })
|
||||
}
|
||||
|
||||
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
|
||||
const presenterClass = getAttributePresenterClass(hierarchy, attr.type)
|
||||
const updateCriteria = getCirteriaEditor(presenterClass.attrClass, presenterClass.category)
|
||||
const editor = updateCriteria?.editor
|
||||
if (editor == null) continue
|
||||
res.push(attr)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
let key: string | undefined = Object.keys(params)[0]
|
||||
|
||||
const allAttrs = getKeys(process.masterTag)
|
||||
|
||||
function onSelect (e: MouseEvent): void {
|
||||
const possibleAttrs = allAttrs.filter((attr) => key !== attr.name)
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{
|
||||
value: possibleAttrs.map((p) => {
|
||||
return { id: p.name, label: p.label }
|
||||
})
|
||||
},
|
||||
eventToHTMLElement(e),
|
||||
(res) => {
|
||||
if (res != null) {
|
||||
key = res
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function remove (e: CustomEvent<any>): void {
|
||||
if (e.detail !== undefined) {
|
||||
if (key !== undefined) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete (params as any)[key]
|
||||
}
|
||||
key = undefined
|
||||
dispatch('change', { params })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if key !== undefined}
|
||||
<CriteriasEditor keys={[key]} {readonly} {process} {params} on:remove={remove} on:change={change} />
|
||||
{:else}
|
||||
<div class="flex-center mt-4">
|
||||
<Button label={view.string.Select} width={'100%'} kind={'link-bordered'} size={'large'} on:click={onSelect} />
|
||||
</div>
|
||||
{/if}
|
||||
@@ -77,9 +77,11 @@ import {
|
||||
showDoneQuery,
|
||||
timeTransitionCheck,
|
||||
todoTranstionCheck,
|
||||
updateCardTranstionCheck,
|
||||
subProcessesDoneCheck
|
||||
matchCardCheck,
|
||||
subProcessesDoneCheck,
|
||||
fieldChangesCheck
|
||||
} from './utils'
|
||||
import FieldChangesEditor from './components/settings/FieldChangesEditor.svelte'
|
||||
|
||||
export * from './query'
|
||||
|
||||
@@ -132,7 +134,8 @@ export default async (): Promise<Resources> => ({
|
||||
TimePresenter,
|
||||
AddTagEditor,
|
||||
AddTagPresenter,
|
||||
ExecutionMyToDos
|
||||
ExecutionMyToDos,
|
||||
FieldChangesEditor
|
||||
},
|
||||
criteriaEditor: {
|
||||
BaseCriteria,
|
||||
@@ -154,7 +157,8 @@ export default async (): Promise<Resources> => ({
|
||||
CutEditor
|
||||
},
|
||||
triggerCheck: {
|
||||
UpdateCheck: updateCardTranstionCheck,
|
||||
MatchCheck: matchCardCheck,
|
||||
FieldChangedCheck: fieldChangesCheck,
|
||||
SubProcessesDoneCheck: subProcessesDoneCheck,
|
||||
ToDo: todoTranstionCheck,
|
||||
Time: timeTransitionCheck
|
||||
|
||||
@@ -21,10 +21,11 @@ import core, {
|
||||
type TxApplyIf,
|
||||
type TxResult,
|
||||
type TxUpdateDoc,
|
||||
TxProcessor
|
||||
TxProcessor,
|
||||
SortingOrder
|
||||
} from '@hcengineering/core'
|
||||
import { BasePresentationMiddleware, type PresentationMiddleware } from '@hcengineering/presentation'
|
||||
import process, { ExecutionStatus, type ProcessToDo } from '@hcengineering/process'
|
||||
import process, { ExecutionStatus, type ProcessToDo, isUpdateTx } from '@hcengineering/process'
|
||||
import { createExecution, getNextStateUserInput, requestResult, pickTransition } from './utils'
|
||||
import cardPlugin, { type Card } from '@hcengineering/card'
|
||||
|
||||
@@ -68,8 +69,8 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
}
|
||||
|
||||
private async handleCardUpdate (etx: Tx): Promise<void> {
|
||||
if (etx._class === core.class.TxUpdateDoc) {
|
||||
const updateTx = etx as TxUpdateDoc<Card>
|
||||
if (etx._class === core.class.TxUpdateDoc || etx._class === core.class.TxMixin) {
|
||||
const updateTx = etx as TxUpdateDoc<Card> | TxMixin<Card, Card>
|
||||
const hierarchy = this.client.getHierarchy()
|
||||
if (!hierarchy.isDerived(updateTx.objectClass, cardPlugin.class.Card)) return
|
||||
const executions = await this.client.findAll(process.class.Execution, {
|
||||
@@ -81,14 +82,23 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
_id: updateTx.objectId
|
||||
})
|
||||
if (card === undefined) return
|
||||
const updated = TxProcessor.updateDoc2Doc(hierarchy.clone(card), updateTx)
|
||||
const updated = isUpdateTx(updateTx)
|
||||
? TxProcessor.updateDoc2Doc<Card>(hierarchy.clone(card), updateTx)
|
||||
: TxProcessor.updateMixin4Doc<Card, Card>(hierarchy.clone(card), updateTx)
|
||||
for (const execution of executions) {
|
||||
const transitions = this.client.getModel().findAllSync(process.class.Transition, {
|
||||
process: execution.process,
|
||||
from: execution.currentState,
|
||||
trigger: process.trigger.OnCardUpdate
|
||||
const transitions = this.client.getModel().findAllSync(
|
||||
process.class.Transition,
|
||||
{
|
||||
process: execution.process,
|
||||
from: execution.currentState,
|
||||
trigger: { $in: [process.trigger.OnCardUpdate, process.trigger.WhenFieldChanges] }
|
||||
},
|
||||
{ sort: { rank: SortingOrder.Ascending } }
|
||||
)
|
||||
const transition = await pickTransition(this.client, execution, transitions, {
|
||||
card: updated,
|
||||
operations: isUpdateTx(updateTx) ? updateTx.operations : updateTx.attributes
|
||||
})
|
||||
const transition = await pickTransition(this.client, execution, transitions, updated)
|
||||
if (transition === undefined) return
|
||||
const context = await getNextStateUserInput(execution, transition, execution.context)
|
||||
const txop = new TxOperations(this.client, getCurrentAccount().primarySocialId)
|
||||
@@ -153,7 +163,9 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
from: execution.currentState,
|
||||
trigger: process.trigger.OnToDoClose
|
||||
})
|
||||
const transition = await pickTransition(this.client, execution, transitions, todo)
|
||||
const transition = await pickTransition(this.client, execution, transitions, {
|
||||
todo
|
||||
})
|
||||
if (transition === undefined) return
|
||||
const context = await getNextStateUserInput(execution, transition, execution.context)
|
||||
if (context !== undefined) {
|
||||
|
||||
@@ -65,6 +65,7 @@ export default mergeIds(processId, process, {
|
||||
AddRelationPresenter: '' as AnyComponent,
|
||||
CardUpdateEditor: '' as AnyComponent,
|
||||
CardUpdatePresenter: '' as AnyComponent,
|
||||
FieldChangesEditor: '' as AnyComponent,
|
||||
ToDoSettingPresenter: '' as AnyComponent,
|
||||
TimeEditor: '' as AnyComponent,
|
||||
TimePresenter: '' as AnyComponent,
|
||||
@@ -161,7 +162,8 @@ export default mergeIds(processId, process, {
|
||||
OnToDoDone: '' as IntlString,
|
||||
OnSubProcessesDone: '' as IntlString,
|
||||
WaitUntil: '' as IntlString,
|
||||
OnCardUpdate: '' as IntlString,
|
||||
WhenCardMatches: '' as IntlString,
|
||||
WhenFieldChanges: '' as IntlString,
|
||||
Result: '' as IntlString,
|
||||
RequestResult: '' as IntlString,
|
||||
NoResultRequired: '' as IntlString,
|
||||
|
||||
@@ -113,7 +113,7 @@ export async function pickTransition (
|
||||
client: Client,
|
||||
execution: Execution,
|
||||
transitions: Transition[],
|
||||
doc: Doc
|
||||
context: Record<string, any>
|
||||
): Promise<Transition | undefined> {
|
||||
for (const tr of transitions) {
|
||||
const trigger = client.getModel().findObject(tr.trigger)
|
||||
@@ -122,7 +122,7 @@ export async function pickTransition (
|
||||
const filled = fillParams(tr.triggerParams, execution)
|
||||
const checkFunc = await getResource(trigger.checkFunction)
|
||||
if (checkFunc === undefined) continue
|
||||
const res = await checkFunc(client, execution, filled, doc)
|
||||
const res = await checkFunc(client, execution, filled, context)
|
||||
if (res) return tr
|
||||
}
|
||||
}
|
||||
@@ -600,10 +600,10 @@ export function todoTranstionCheck (
|
||||
client: Client,
|
||||
execution: Execution,
|
||||
params: Record<string, any>,
|
||||
doc: Doc
|
||||
context: Record<string, any>
|
||||
): boolean {
|
||||
if (params._id === undefined) return false
|
||||
return doc._id === params._id
|
||||
return context.card?._id === params._id
|
||||
}
|
||||
|
||||
export function timeTransitionCheck (
|
||||
@@ -616,12 +616,28 @@ export function timeTransitionCheck (
|
||||
return params.value <= Date.now()
|
||||
}
|
||||
|
||||
export function updateCardTranstionCheck (
|
||||
export function matchCardCheck (
|
||||
client: Client,
|
||||
execution: Execution,
|
||||
params: Record<string, any>,
|
||||
doc: Doc
|
||||
context: Record<string, any>
|
||||
): boolean {
|
||||
const doc = context.card
|
||||
if (doc === undefined) return false
|
||||
const res = matchQuery([doc], params, doc._class, client.getHierarchy(), true)
|
||||
return res.length > 0
|
||||
}
|
||||
|
||||
export function fieldChangesCheck (
|
||||
client: Client,
|
||||
execution: Execution,
|
||||
params: Record<string, any>,
|
||||
context: Record<string, any>
|
||||
): boolean {
|
||||
const doc = context.card
|
||||
if (doc === undefined) return false
|
||||
const param = Object.keys(params)[0]
|
||||
if (!Object.keys(context.operations ?? {}).includes(param)) return false
|
||||
const res = matchQuery([doc], params, doc._class, client.getHierarchy(), true)
|
||||
return res.length > 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user