Add FirstMatchValue and Filter functions (#10042)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2025-10-07 23:17:04 +07:00
committed by GitHub
parent 0a682d03e3
commit f8205e57a1
29 changed files with 423 additions and 68 deletions
+27
View File
@@ -185,6 +185,19 @@ export function defineFunctions (builder: Builder): void {
process.function.All
)
builder.createDoc(
process.class.ProcessFunction,
core.space.Model,
{
of: core.class.ArrOf,
category: undefined,
label: process.string.FirstMatchValue,
type: 'reduce',
editor: process.transformEditor.FilterEditor
},
process.function.FirstMatchValue
)
builder.createDoc(
process.class.ProcessFunction,
core.space.Model,
@@ -420,6 +433,20 @@ export function defineFunctions (builder: Builder): void {
process.function.RemoveLast
)
builder.createDoc(
process.class.ProcessFunction,
core.space.Model,
{
of: core.class.ArrOf,
category: 'array',
label: process.string.Filter,
allowMany: true,
type: 'transform',
editor: process.transformEditor.FilterEditor
},
process.function.Filter
)
builder.createDoc(
process.class.ProcessFunction,
core.space.Model,
+8
View File
@@ -241,6 +241,14 @@ export function createModel (builder: Builder): void {
func: serverProcess.transform.EmptyArray
})
builder.mixin(process.function.FirstMatchValue, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, {
func: serverProcess.transform.FirstMatchValue
})
builder.mixin(process.function.Filter, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, {
func: serverProcess.transform.Filter
})
builder.mixin(
process.function.ExecutionEmployeeInitiator,
process.class.ProcessFunction,
+2 -1
View File
@@ -743,7 +743,8 @@ export function findAttributeEditor (
key: KeyedAttribute | string
): AnyComponent | undefined {
const hierarchy = client.getHierarchy()
const attribute = typeof key === 'string' ? hierarchy.getAttribute(_class, key) : key.attr
const attribute = typeof key === 'string' ? hierarchy.findAttribute(_class, key) : key.attr
if (attribute === undefined) return
if (attribute.type._class === core.class.TypeAny) {
const _type: TypeAny = attribute.type as TypeAny<AnyComponent>
+3 -1
View File
@@ -112,7 +112,9 @@
"NewProcessToDo": "Nový Action Item procesu",
"EmptyArray": "Prázdné pole",
"ExecutionInitiator": "Iniciátor provedení",
"ExecutionStarted": "Provedení spuštěno"
"ExecutionStarted": "Provedení spuštěno",
"Filter": "Filtr",
"FirstMatchValue": "První odpovídající hodnota"
},
"error": {
"MethodNotFound": "Metoda nenalezena: {methodId}",
+3 -1
View File
@@ -112,7 +112,9 @@
"NewProcessToDo": "Neuer Prozess Action Item",
"EmptyArray": "Leeres Array",
"ExecutionInitiator": "Ausführungsinitiator",
"ExecutionStarted": "Ausführung gestartet"
"ExecutionStarted": "Ausführung gestartet",
"Filter": "Filter",
"FirstMatchValue": "Erster passender Wert"
},
"error": {
"MethodNotFound": "Methode nicht gefunden: {methodId}",
+3 -1
View File
@@ -117,7 +117,9 @@
"NewProcessToDo": "New process Action item",
"EmptyArray": "Empty array",
"ExecutionInitiator": "Execution initiator",
"ExecutionStarted": "Execution started"
"ExecutionStarted": "Execution started",
"Filter": "Filter",
"FirstMatchValue": "First match value"
},
"error": {
"MethodNotFound": "Method not found: {methodId}",
+3 -1
View File
@@ -117,7 +117,9 @@
"NewProcessToDo": "Nueva acción de proceso",
"EmptyArray": "Matriz vacía",
"ExecutionInitiator": "Iniciador de Ejecución",
"ExecutionStarted": "Ejecución Iniciada"
"ExecutionStarted": "Ejecución Iniciada",
"Filter": "Filtro",
"FirstMatchValue": "Primer valor coincidente"
},
"error": {
"MethodNotFound": "Método no encontrado: {methodId}",
+3 -1
View File
@@ -117,7 +117,9 @@
"NewProcessToDo": "Nouvel Action Item de processus",
"EmptyArray": "Tableau vide",
"ExecutionInitiator": "Initiateur d'exécution",
"ExecutionStarted": "Exécution démarrée"
"ExecutionStarted": "Exécution démarrée",
"Filter": "Filtrer",
"FirstMatchValue": "Première valeur correspondante"
},
"error": {
"MethodNotFound": "Méthode introuvable : {methodId}",
+3 -1
View File
@@ -117,7 +117,9 @@
"NewProcessToDo": "Nuovo Action Item di processo",
"EmptyArray": "Array vuoto",
"ExecutionInitiator": "Iniziatore dell'esecuzione",
"ExecutionStarted": "Esecuzione avviata"
"ExecutionStarted": "Esecuzione avviata",
"Filter": "Filtro",
"FirstMatchValue": "Primo valore corrispondente"
},
"error": {
"MethodNotFound": "Metodo non trovato: {methodId}",
+3 -1
View File
@@ -116,7 +116,9 @@
"NewProcessToDo": "新しいプロセスアクション",
"EmptyArray": "空の配列",
"ExecutionInitiator": "実行の発起人",
"ExecutionStarted": "実行が開始されました"
"ExecutionStarted": "実行が開始されました",
"Filter": "フィルター",
"FirstMatchValue": "最初の一致する値"
},
"error": {
"MethodNotFound": "メソッドが見つかりません: {methodId}",
+3 -1
View File
@@ -117,7 +117,9 @@
"NewProcessToDo": "Nova ação de processo",
"EmptyArray": "Array vazio",
"ExecutionInitiator": "Iniciador de Execução",
"ExecutionStarted": "Execução Iniciada"
"ExecutionStarted": "Execução Iniciada",
"Filter": "Filtrar",
"FirstMatchValue": "Primeiro Valor Correspondente"
},
"error": {
"MethodNotFound": "Método não encontrado: {methodId}",
+3 -1
View File
@@ -117,7 +117,9 @@
"NewProcessToDo": "Новый Action Item процесса",
"EmptyArray": "Пустой массив",
"ExecutionInitiator": "Инициатор выполнения",
"ExecutionStarted": "Выполнение начато"
"ExecutionStarted": "Выполнение начато",
"Filter": "Фильтр",
"FirstMatchValue": "Первое подходящее значение"
},
"error": {
"MethodNotFound": "Метод не найден: {methodId}",
+3 -1
View File
@@ -114,7 +114,9 @@
"ProcessRunned": "\"{process}\" süreci başlatıldı",
"ProcessStateChanged": "\"{process}\" süreci \"{state}\" durumuna geçti",
"ProcessFinished": "\"{process}\" süreci \"{state}\" durumunda tamamlandı",
"NewProcessToDo": "Yeni süreç Eylem öğesi"
"NewProcessToDo": "Yeni süreç Eylem öğesi",
"Filter": "Filtre",
"FirstMatchValue": "İlk eşleşen değer"
},
"error": {
"MethodNotFound": "Metod bulunamadı: {methodId}",
+3 -1
View File
@@ -117,7 +117,9 @@
"NewProcessToDo": "新过程待办事项",
"EmptyArray": "空数组",
"ExecutionInitiator": "执行发起者",
"ExecutionStarted": "执行已启动"
"ExecutionStarted": "执行已启动",
"Filter": "过滤",
"FirstMatchValue": "第一个匹配值"
},
"error": {
"MethodNotFound": "找不到方法:{methodId}",
@@ -136,10 +136,31 @@
}
function onFunctionSelect (e: Ref<ProcessFunction>): void {
// if editor is undefined
const func = client.getModel().findAllSync(plugin.class.ProcessFunction, { _id: e })[0]
onFunction(e, {}, addFunction)
}
function addFunction (func: Func): void {
const arr = contextValue.functions ?? []
arr.push(func)
contextValue.functions = arr
onChange(contextValue)
}
function onSourceFunctionSelect (e: Ref<ProcessFunction>): void {
onFunction(e, contextValue.sourceFunction?.props ?? {}, (res) => (contextValue.sourceFunction = res))
}
function onSourceFunctionChange (e: Func): void {
contextValue.sourceFunction = e
onChange(contextValue)
}
function onFunction (_func: Ref<ProcessFunction>, props: Record<string, any>, cb: (res: Func) => void) {
const func = client.getModel().findAllSync(plugin.class.ProcessFunction, { _id: _func })[0]
if (func.editor === undefined) {
addFunction(e, {})
const res: Func = { func: _func, props: {} }
cb(res)
onChange(contextValue)
closeTooltip()
} else {
showPopup(
@@ -149,46 +170,49 @@
process,
masterTag: process.masterTag,
context,
attribute
attribute,
props
},
elements[functionButtonIndex],
elements[0],
(res) => {
if (res != null) {
addFunction(e, res)
const result: Func = { func: _func, props: res }
cb(result)
onChange(contextValue)
closeTooltip()
}
}
)
}
}
function addFunction (func: Ref<ProcessFunction>, props: Record<string, any>): void {
const arr = contextValue.functions ?? []
arr.push({
func,
props
})
contextValue.functions = arr
onChange(contextValue)
}
function onSourceFunctionSelect (e: Ref<ProcessFunction>): void {
contextValue.sourceFunction = { func: e, props: {} }
onChange(contextValue)
}
function onFunctionChange (e: Ref<ProcessFunction>, i: number): void {
if (contextValue.functions === undefined) return
contextValue.functions[i].func = e
contextValue.functions = contextValue.functions
onChange(contextValue)
onFunction(e, contextValue.functions?.[i]?.props ?? {}, (res) => {
if (contextValue.functions === undefined) {
contextValue.functions = []
}
contextValue.functions[i] = res
contextValue.functions = contextValue.functions
})
}
function getFunctionChange (i: number): (e: Ref<ProcessFunction>) => void {
function getFunctionSelect (i: number): (e: Ref<ProcessFunction>) => void {
return (e: Ref<ProcessFunction>) => {
onFunctionChange(e, i)
}
}
function getFunctionChange (i: number): (e: Func) => void {
return (e: Func) => {
if (contextValue.functions === undefined) {
contextValue.functions = []
}
contextValue.functions[i] = e
contextValue.functions = contextValue.functions
onChange(contextValue)
}
}
function onFunctionRemove (pos: number): void {
const arr = contextValue.functions ?? []
if (pos !== -1) {
@@ -256,9 +280,15 @@
}}
label={sourceFunc.label}
props={{
attribute,
process,
context,
func: contextValue.sourceFunction,
availableFunctions: reduceFuncs,
onSelect: onSourceFunctionSelect
onSelect: onSourceFunctionSelect,
onChange: onSourceFunctionChange
}}
component={plugin.component.FunctionSubmenu}
options={{ component: plugin.component.FunctionSelector }}
withHover
/>
@@ -279,9 +309,15 @@
}}
label={getFunction(f.func)?.label}
props={{
func: f,
attribute,
process,
context,
availableFunctions: reduceFuncs,
onSelect: getFunctionChange(i)
onSelect: getFunctionSelect(i),
onChange: getFunctionChange(i)
}}
component={plugin.component.FunctionSubmenu}
options={{ component: plugin.component.FunctionSelector }}
withHover
/>
@@ -14,13 +14,14 @@
-->
<script lang="ts">
import { Doc, DocumentQuery } from '@hcengineering/core'
import { Class, Doc, DocumentQuery, Ref } from '@hcengineering/core'
import { getAttributePresenterClass, getClient } from '@hcengineering/presentation'
import { Process } from '@hcengineering/process'
import { Component, Label, tooltip } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { getCirteriaEditor, getContext } from '../../utils'
export let _class: Ref<Class<Doc>>
export let readonly: boolean
export let process: Process
export let key: string
@@ -30,7 +31,7 @@
const client = getClient()
const hierarchy = client.getHierarchy()
$: attribute = hierarchy.getAttribute(process.masterTag, key)
$: attribute = hierarchy.getAttribute(_class, key)
$: presenterClass = getAttributePresenterClass(hierarchy, attribute.type)
$: updateCriteria = getCirteriaEditor(presenterClass.attrClass, presenterClass.category)
@@ -60,8 +60,7 @@
const hierarchy = client.getHierarchy()
$: presenterClass = getAttributePresenterClass(hierarchy, attribute.type)
$: baseEditor = findAttributeEditor(client, process.masterTag, attribute.name)
$: baseEditor = findAttributeEditor(client, attribute.attributeOf, attribute.name)
</script>
<div class="text-input" class:context={contextValue}>
@@ -14,11 +14,12 @@
-->
<script lang="ts">
import { Doc, DocumentQuery } from '@hcengineering/core'
import { Class, Doc, DocumentQuery, Ref } from '@hcengineering/core'
import { Process } from '@hcengineering/process'
import { createEventDispatcher } from 'svelte'
import AttributeCriteria from './AttributeCriteria.svelte'
export let _class: Ref<Class<Doc>>
export let readonly: boolean
export let process: Process
export let keys: string[]
@@ -41,6 +42,7 @@
<div class="editor-grid">
{#each keys as key}
<AttributeCriteria
{_class}
{process}
{key}
{params}
@@ -93,7 +93,15 @@
}
</script>
<CriteriasEditor {keys} {readonly} {process} {params} on:remove={remove} on:change={change} />
<CriteriasEditor
_class={process.masterTag}
{keys}
{readonly}
{process}
{params}
on:remove={remove}
on:change={change}
/>
{#if possibleAttrs.length > 0}
<div class="flex-center mt-4">
<Button label={presentation.string.Add} width={'100%'} kind={'link-bordered'} size={'large'} on:click={onAdd} />
@@ -90,7 +90,15 @@
</script>
{#if key !== undefined}
<CriteriasEditor keys={[key]} {readonly} {process} {params} on:remove={remove} on:change={change} />
<CriteriasEditor
keys={[key]}
_class={process.masterTag}
{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} />
@@ -0,0 +1,65 @@
<!--
// 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, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Context, Func, Process, ProcessFunction } from '@hcengineering/process'
import { ButtonIcon, eventToHTMLElement, IconSettings, Label, showPopup } from '@hcengineering/ui'
import plugin from '../../plugin'
export let attribute: AnyAttribute
export let process: Process
export let context: Context
export let func: Func
export let onChange: (func: Func) => void
const client = getClient()
function getFunction (_id: Ref<ProcessFunction>): ProcessFunction {
return client.getModel().findAllSync(plugin.class.ProcessFunction, { _id })[0]
}
function onConfigure (e: MouseEvent): void {
const f = getFunction(func.func)
if (f.editor === undefined) return
showPopup(
f.editor,
{
func: f,
masterTag: process.masterTag,
process,
context,
attribute,
props: func.props
},
eventToHTMLElement(e),
(res) => {
if (res != null) {
func.props = res
onChange(func)
}
}
)
}
</script>
<div class="flex-row-center flex-gap-2 pr-1 flex-shrink">
<span class="overflow-label">
<Label label={getFunction(func.func).label} />
</span>
{#if getFunction(func.func).editor}
<ButtonIcon icon={IconSettings} size="small" kind="tertiary" on:click={onConfigure} />
{/if}
</div>
@@ -0,0 +1,112 @@
<!--
// 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, { AnyAttribute } from '@hcengineering/core'
import presentation, { Card, getAttributePresenterClass, getClient } from '@hcengineering/presentation'
import { Process, ProcessFunction } from '@hcengineering/process'
import { Button, SelectPopup, eventToHTMLElement, showPopup } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { getCirteriaEditor } from '../../utils'
import CriteriasEditor from '../criterias/CriteriasEditor.svelte'
export let func: ProcessFunction
export let process: Process
export let attribute: AnyAttribute
export let props: Record<string, any> = {}
const dispatch = createEventDispatcher()
const client = getClient()
const hierarchy = client.getHierarchy()
const presenterClass = getAttributePresenterClass(hierarchy, attribute.type)
function save (): void {
dispatch('close', {
...props,
_class: presenterClass.attrClass
})
}
function getKeys (): AnyAttribute[] {
const attributes = hierarchy.getAllAttributes(presenterClass.attrClass, core.class.Doc)
const res: AnyAttribute[] = []
for (const [key, attr] of attributes) {
if (key === '_id' || key === '_class') continue
if (attr.hidden === true) 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 keys = Object.keys(props).filter((p) => p !== '_class')
const allAttrs = getKeys()
$: possibleAttrs = allAttrs.filter((attr) => !keys.includes(attr.name))
function onAdd (e: MouseEvent): void {
showPopup(
SelectPopup,
{
value: possibleAttrs.map((p) => {
return { id: p.name, label: p.label }
})
},
eventToHTMLElement(e),
(res) => {
if (res != null) {
addKey(res)
}
}
)
}
function addKey (key: string): void {
keys = [...keys, key]
}
function remove (e: CustomEvent<any>): void {
if (e.detail !== undefined) {
const key = e.detail.key
keys = keys.filter((k) => k !== key)
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete (props as any)[key]
}
}
function change (e: CustomEvent<any>): void {
if (e.detail == null) return
props = e.detail
}
</script>
<Card on:close width={'x-small'} label={func.label} canSave okAction={save} okLabel={presentation.string.Save}>
<CriteriasEditor
_class={presenterClass.attrClass}
readonly={false}
{keys}
{process}
params={props}
on:remove={remove}
on:change={change}
/>
{#if possibleAttrs.length > 0}
<div class="flex-center mt-4">
<Button label={presentation.string.Add} width={'100%'} kind={'link-bordered'} size={'large'} on:click={onAdd} />
</div>
{/if}
</Card>
+6 -2
View File
@@ -47,6 +47,8 @@ import ArrayElementEditor from './components/transformEditors/ArrayElementEditor
import MultiArrayElementEditor from './components/transformEditors/MultiArrayElementEditor.svelte'
import DateOffsetEditor from './components/transformEditors/DateOffsetEditor.svelte'
import NumberEditor from './components/transformEditors/NumberEditor.svelte'
import FilterEditor from './components/transformEditors/FilterEditor.svelte'
import FunctionSubmenu from './components/settings/FunctionSubmenu.svelte'
import ArraySizeCriteria from './components/criterias/ArraySizeCriteria.svelte'
import BaseCriteria from './components/criterias/BaseCriteria.svelte'
@@ -135,7 +137,8 @@ export default async (): Promise<Resources> => ({
AddTagEditor,
AddTagPresenter,
ExecutionMyToDos,
FieldChangesEditor
FieldChangesEditor,
FunctionSubmenu
},
criteriaEditor: {
BaseCriteria,
@@ -154,7 +157,8 @@ export default async (): Promise<Resources> => ({
AppendEditor,
ReplaceEditor,
SplitEditor,
CutEditor
CutEditor,
FilterEditor
},
triggerCheck: {
MatchCheck: matchCardCheck,
+6 -2
View File
@@ -70,7 +70,8 @@ export default mergeIds(processId, process, {
TimeEditor: '' as AnyComponent,
TimePresenter: '' as AnyComponent,
AddTagEditor: '' as AnyComponent,
AddTagPresenter: '' as AnyComponent
AddTagPresenter: '' as AnyComponent,
FunctionSubmenu: '' as AnyComponent
},
criteriaEditor: {
BaseCriteria: '' as AnyComponent,
@@ -85,7 +86,8 @@ export default mergeIds(processId, process, {
AppendEditor: '' as AnyComponent,
ReplaceEditor: '' as AnyComponent,
SplitEditor: '' as AnyComponent,
CutEditor: '' as AnyComponent
CutEditor: '' as AnyComponent,
FilterEditor: '' as AnyComponent
},
transformPresenter: {
NumberPresenter: '' as AnyComponent,
@@ -127,6 +129,8 @@ export default mergeIds(processId, process, {
ReplaceAll: '' as IntlString,
Split: '' as IntlString,
Cut: '' as IntlString,
Filter: '' as IntlString,
FirstMatchValue: '' as IntlString,
FirstValue: '' as IntlString,
LastValue: '' as IntlString,
Random: '' as IntlString,
+18 -6
View File
@@ -90,12 +90,10 @@ function encodeValue (val: any): string {
return val ? 'true' : 'false'
case 'string':
return `"${val.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
case 'object':
try {
return JSON.stringify(val)
} catch {
return String(val)
}
case 'object': {
const entries = Object.entries(val).map(([k, v]) => `${k}=${encodeValue(v)}`)
return `{${entries.join(',')}}`
}
}
return String(val)
@@ -384,6 +382,20 @@ function decodeValue (s: string): any {
if (inner === '') return []
return inner.split(',').map(decodeValue)
}
if (str.startsWith('{') && str.endsWith('}')) {
const inner = str.slice(1, -1).trim()
if (inner === '') return {}
const obj: Record<string, any> = {}
const entries = splitTopLevel(inner, ',')
for (const entry of entries) {
const eq = entry.indexOf('=')
if (eq === -1) continue
const key = entry.slice(0, eq).trim()
const val = entry.slice(eq + 1).trim()
obj[key] = decodeValue(val)
}
return obj
}
return str
}
+2
View File
@@ -277,6 +277,8 @@ export default plugin(processId, {
Time: '' as Asset
},
function: {
FirstMatchValue: '' as Ref<ProcessFunction>,
Filter: '' as Ref<ProcessFunction>,
FirstValue: '' as Ref<ProcessFunction>,
LastValue: '' as Ref<ProcessFunction>,
Random: '' as Ref<ProcessFunction>,
@@ -78,7 +78,9 @@ import {
UpperCase,
EmptyArray,
ExecutionInitiator,
ExecutionStarted
ExecutionStarted,
FirstMatchValue,
Filter
} from './transform'
import {
RunSubProcess,
@@ -434,7 +436,9 @@ export default async () => ({
RemoveLast,
EmptyArray,
ExecutionInitiator,
ExecutionStarted
ExecutionStarted,
FirstMatchValue,
Filter
},
rollbacks: {
ToDoCloseRollback,
@@ -14,20 +14,21 @@
//
import contact, { Employee, Person } from '@hcengineering/contact'
import { Doc, Ref, Timestamp } from '@hcengineering/core'
import core, { Doc, matchQuery, Ref, Timestamp } from '@hcengineering/core'
import { Execution, parseContext } from '@hcengineering/process'
import { ProcessControl } from '@hcengineering/server-process'
import { getContextValue } from './utils'
// #region ArrayReduce
export function FirstValue (value: Doc[]): Doc {
export function FirstValue (value: Doc[]): Doc | undefined {
if (!Array.isArray(value)) return value
return value[0]
}
export function LastValue (value: Doc[]): Doc {
export function LastValue (value: Doc[]): Doc | undefined {
if (!Array.isArray(value)) return value
if (value.length === 0) return
return value[value.length - 1]
}
@@ -40,16 +41,36 @@ export function All (value: Doc[]): Doc[] {
return value
}
export async function FirstMatchValue (
value: any[],
props: Record<string, any>,
control: ProcessControl
): Promise<any | undefined> {
if (value == null) {
return
}
if (!Array.isArray(value)) return value
const { _class, ...otherProps } = props
if (_class == null) return
if (value.length === 0) return
if (typeof value[0] === 'string') {
const docs = await control.client.findAll(_class, { _id: { $in: value } })
return matchQuery(docs, otherProps, core.class.Doc, control.client.getHierarchy(), true)[0]?._id
} else if (typeof value[0] === 'object') {
return matchQuery(value, otherProps, core.class.Doc, control.client.getHierarchy(), true)[0]
}
}
// #endregion
// #region Array
export async function Insert (
value: Doc[],
value: any[],
props: Record<string, any>,
control: ProcessControl,
execution: Execution
): Promise<Doc[]> {
): Promise<any[]> {
if (value == null) {
value = []
}
@@ -66,11 +87,11 @@ export async function Insert (
}
export async function Remove (
value: Doc[],
value: any[],
props: Record<string, any>,
control: ProcessControl,
execution: Execution
): Promise<Doc[]> {
): Promise<any[]> {
if (value == null) {
value = []
}
@@ -85,7 +106,7 @@ export async function Remove (
}
}
export function RemoveFirst (value: Doc[], props: Record<string, any>): Doc[] {
export function RemoveFirst (value: any[]): any[] {
if (value == null) {
value = []
}
@@ -93,7 +114,7 @@ export function RemoveFirst (value: Doc[], props: Record<string, any>): Doc[] {
return value.slice(1)
}
export function RemoveLast (value: Doc[], props: Record<string, any>): Doc[] {
export function RemoveLast (value: any[]): any[] {
if (value == null) {
value = []
}
@@ -101,6 +122,23 @@ export function RemoveLast (value: Doc[], props: Record<string, any>): Doc[] {
return value.slice(0, -1)
}
export async function Filter (value: any[], props: Record<string, any>, control: ProcessControl): Promise<any[]> {
if (value == null) {
return []
}
if (!Array.isArray(value)) return value
const { _class, ...otherProps } = props
if (_class == null) return value
if (value.length === 0) return value
if (typeof value[0] === 'string') {
const docs = await control.client.findAll(_class, { _id: { $in: value } })
return matchQuery(docs, otherProps, core.class.Doc, control.client.getHierarchy(), true).map((p) => p._id)
} else if (typeof value[0] === 'object') {
return matchQuery(value, otherProps, core.class.Doc, control.client.getHierarchy(), true)
}
return value
}
// #endregion
// #region String
+2
View File
@@ -96,6 +96,8 @@ export default plugin(serverProcessId, {
CurrentUser: '' as Resource<TransformFunc>,
CurrentDate: '' as Resource<TransformFunc>,
EmptyArray: '' as Resource<TransformFunc>,
Filter: '' as Resource<TransformFunc>,
FirstMatchValue: '' as Resource<TransformFunc>,
ExecutionInitiator: '' as Resource<TransformFunc>,
ExecutionStarted: '' as Resource<TransformFunc>
},