mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-10 11:47:42 +02:00
Add FirstMatchValue and Filter functions (#10042)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -116,7 +116,9 @@
|
||||
"NewProcessToDo": "新しいプロセスアクション",
|
||||
"EmptyArray": "空の配列",
|
||||
"ExecutionInitiator": "実行の発起人",
|
||||
"ExecutionStarted": "実行が開始されました"
|
||||
"ExecutionStarted": "実行が開始されました",
|
||||
"Filter": "フィルター",
|
||||
"FirstMatchValue": "最初の一致する値"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "メソッドが見つかりません: {methodId}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -117,7 +117,9 @@
|
||||
"NewProcessToDo": "Новый Action Item процесса",
|
||||
"EmptyArray": "Пустой массив",
|
||||
"ExecutionInitiator": "Инициатор выполнения",
|
||||
"ExecutionStarted": "Выполнение начато"
|
||||
"ExecutionStarted": "Выполнение начато",
|
||||
"Filter": "Фильтр",
|
||||
"FirstMatchValue": "Первое подходящее значение"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Метод не найден: {methodId}",
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user