mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-22 01:25:00 +02:00
Process improvements (#9801)
This commit is contained in:
@@ -14,7 +14,17 @@
|
||||
//
|
||||
|
||||
import cardPlugin, { Card } from '@hcengineering/card'
|
||||
import core, { Tx, TxCreateDoc, TxCUD, TxProcessor, TxRemoveDoc, TxUpdateDoc } from '@hcengineering/core'
|
||||
import core, {
|
||||
ArrOf,
|
||||
Doc,
|
||||
RefTo,
|
||||
Tx,
|
||||
TxCreateDoc,
|
||||
TxCUD,
|
||||
TxProcessor,
|
||||
TxRemoveDoc,
|
||||
TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import process, {
|
||||
ContextId,
|
||||
Execution,
|
||||
@@ -33,6 +43,8 @@ import {
|
||||
All,
|
||||
Append,
|
||||
Ceil,
|
||||
CurrentDate,
|
||||
CurrentUser,
|
||||
Cut,
|
||||
Divide,
|
||||
FirstValue,
|
||||
@@ -276,11 +288,16 @@ async function syncContext (control: TriggerControl, _process: Process): Promise
|
||||
id: transition.result._id,
|
||||
key: ''
|
||||
}
|
||||
const parentType =
|
||||
transition.result.type._class === core.class.ArrOf
|
||||
? (transition.result.type as ArrOf<Doc>).of
|
||||
: transition.result.type
|
||||
const _class = parentType._class === core.class.RefTo ? (parentType as RefTo<Doc>).to : parentType._class
|
||||
_process.context[transition.result._id] = {
|
||||
name: context?.name ?? transition.result.name,
|
||||
isResult: true,
|
||||
type: transition.result.type,
|
||||
_class: transition.result.type._class,
|
||||
_class,
|
||||
index: index++,
|
||||
producer: transition._id,
|
||||
value: ctx
|
||||
@@ -325,6 +342,8 @@ async function syncContext (control: TriggerControl, _process: Process): Promise
|
||||
}
|
||||
}
|
||||
|
||||
export * from './utils'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
func: {
|
||||
@@ -338,6 +357,8 @@ export default async () => ({
|
||||
CheckTime
|
||||
},
|
||||
transform: {
|
||||
CurrentDate,
|
||||
CurrentUser,
|
||||
FirstValue,
|
||||
LastValue,
|
||||
Random,
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import contact, { Employee } from '@hcengineering/contact'
|
||||
import contact, { Employee, Person } from '@hcengineering/contact'
|
||||
import { Doc, Ref, Timestamp } from '@hcengineering/core'
|
||||
import { Execution, parseContext } from '@hcengineering/process'
|
||||
import { ProcessControl } from '@hcengineering/server-process'
|
||||
import { getAttributeValue } from './utils'
|
||||
import { getContextValue } from './utils'
|
||||
|
||||
// #region ArrayReduce
|
||||
|
||||
@@ -54,10 +54,8 @@ export async function Insert (
|
||||
if (props.value == null) return value
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const addition = getAttributeValue(control, execution, context)
|
||||
value.push(addition)
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
Array.isArray(val) ? value.push(...val) : value.push(val)
|
||||
} else {
|
||||
value.push(props.value)
|
||||
}
|
||||
@@ -74,14 +72,11 @@ export async function Remove (
|
||||
if (props.value == null) return value
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const addition = getAttributeValue(control, execution, context)
|
||||
return value.filter((item) => item !== addition)
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
return value.filter((item) => item !== val)
|
||||
} else {
|
||||
return value.filter((item) => item !== props.value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
export function RemoveFirst (value: Doc[], props: Record<string, any>): Doc[] {
|
||||
@@ -121,11 +116,9 @@ export async function Prepend (
|
||||
): Promise<string> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const addition = getAttributeValue(control, execution, context)
|
||||
if (typeof addition !== 'string') return value
|
||||
return addition + value
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (typeof val !== 'string') return value
|
||||
return val + value
|
||||
} else if (typeof value === 'string') {
|
||||
return props.value + value
|
||||
}
|
||||
@@ -140,11 +133,9 @@ export async function Append (
|
||||
): Promise<string> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const addition = getAttributeValue(control, execution, context)
|
||||
if (typeof addition !== 'string') return value
|
||||
return value + addition
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (typeof val !== 'string') return value
|
||||
return value + val
|
||||
} else if (typeof value === 'string') {
|
||||
return value + props.value
|
||||
}
|
||||
@@ -216,10 +207,9 @@ export async function Add (
|
||||
): Promise<number> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const offset = getAttributeValue(control, execution, context)
|
||||
return value + offset
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (typeof val !== 'number') return value
|
||||
return value + val
|
||||
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
||||
return value + props.value
|
||||
}
|
||||
@@ -234,10 +224,9 @@ export async function Subtract (
|
||||
): Promise<number> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const offset = getAttributeValue(control, execution, context)
|
||||
return value - offset
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (typeof val !== 'number') return value
|
||||
return value - val
|
||||
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
||||
return value - props.value
|
||||
}
|
||||
@@ -252,10 +241,9 @@ export async function Multiply (
|
||||
): Promise<number> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const val = getAttributeValue(control, execution, context)
|
||||
return value * val
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (typeof val !== 'number') return value
|
||||
return value * val
|
||||
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
||||
return value * props.value
|
||||
}
|
||||
@@ -270,13 +258,11 @@ export async function Divide (
|
||||
): Promise<number> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const val = getAttributeValue(control, execution, context)
|
||||
if (val === 0) {
|
||||
return value // Avoid division by zero
|
||||
}
|
||||
return value / val
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (val === 0 || typeof val !== 'number') {
|
||||
return value // Avoid division by zero
|
||||
}
|
||||
return value / val
|
||||
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
||||
if (props.value === 0) {
|
||||
return value // Avoid division by zero
|
||||
@@ -294,13 +280,11 @@ export async function Modulo (
|
||||
): Promise<number> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const val = getAttributeValue(control, execution, context)
|
||||
if (val === 0) {
|
||||
return value // Avoid division by zero
|
||||
}
|
||||
return value % val
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (val === 0 || typeof val !== 'number') {
|
||||
return value // Avoid division by zero
|
||||
}
|
||||
return value % val
|
||||
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
||||
if (props.value === 0) {
|
||||
return value // Avoid division by zero
|
||||
@@ -318,10 +302,9 @@ export async function Power (
|
||||
): Promise<number> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const val = getAttributeValue(control, execution, context)
|
||||
return Math.pow(value, val)
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (typeof val !== 'number') return value
|
||||
return Math.pow(value, val)
|
||||
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
||||
return Math.pow(value, props.value)
|
||||
}
|
||||
@@ -372,4 +355,17 @@ export async function RoleContext (
|
||||
return users.map((it) => it.user)
|
||||
}
|
||||
|
||||
export async function CurrentUser (
|
||||
value: null,
|
||||
props: Record<string, any>,
|
||||
control: ProcessControl
|
||||
): Promise<Ref<Person> | undefined> {
|
||||
const socialId = await control.client.findOne(contact.class.SocialIdentity, { _id: control.client.user as any })
|
||||
return socialId?.attachedTo
|
||||
}
|
||||
|
||||
export async function CurrentDate (): Promise<Timestamp> {
|
||||
return Date.now()
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
@@ -13,22 +13,266 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { getObjectValue } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import process, { Execution, SelectedContext, processError } from '@hcengineering/process'
|
||||
import { ProcessControl } from '@hcengineering/server-process'
|
||||
import core, { ArrOf, Doc, getObjectValue, RefTo } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel, getResource } from '@hcengineering/platform'
|
||||
import process, {
|
||||
Execution,
|
||||
ExecutionError,
|
||||
parseContext,
|
||||
ProcessError,
|
||||
processError,
|
||||
SelectedContext,
|
||||
SelectedContextFunc,
|
||||
SelectedExecutionContext,
|
||||
SelectedNested,
|
||||
SelectedRelation,
|
||||
SelectedUserRequest
|
||||
} from '@hcengineering/process'
|
||||
import serverProcess, { ProcessControl } from '@hcengineering/server-process'
|
||||
|
||||
export function getAttributeValue (control: ProcessControl, execution: Execution, context: SelectedContext): any {
|
||||
export async function getContextValue (value: any, control: ProcessControl, execution: Execution): Promise<any> {
|
||||
const context = parseContext(value)
|
||||
if (context !== undefined) {
|
||||
let value: any | undefined
|
||||
try {
|
||||
if (context.type === 'attribute') {
|
||||
value = getAttributeValue(control, execution, context)
|
||||
} else if (context.type === 'relation') {
|
||||
value = await getRelationValue(control, execution, context)
|
||||
} else if (context.type === 'nested') {
|
||||
value = await getNestedValue(control, execution, context)
|
||||
} else if (context.type === 'userRequest') {
|
||||
value = getUserRequestValue(control, execution, context)
|
||||
} else if (context.type === 'function') {
|
||||
value = await getFunctionValue(control, execution, context)
|
||||
} else if (context.type === 'context') {
|
||||
value = await getExecutionContextValue(control, execution, context)
|
||||
}
|
||||
return await fillValue(value, context, control, execution)
|
||||
} catch (err: any) {
|
||||
if (err instanceof ProcessError && context.fallbackValue !== undefined) {
|
||||
return await fillValue(context.fallbackValue, context, control, execution)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
for (const key in value) {
|
||||
value[key] = await getContextValue(value[key], control, execution)
|
||||
}
|
||||
return value
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
function getAttributeValue (control: ProcessControl, execution: Execution, context: SelectedContext): any {
|
||||
const card = control.cache.get(execution.card)
|
||||
if (card !== undefined) {
|
||||
const val = getObjectValue(context.key, card)
|
||||
if (val == null) {
|
||||
const attr = control.client.getHierarchy().findAttribute(card._class, context.key)
|
||||
throw processError(
|
||||
process.error.EmptyAttributeContextValue,
|
||||
{},
|
||||
{ attr: attr?.label ?? getEmbeddedLabel(context.key) }
|
||||
)
|
||||
}
|
||||
return val
|
||||
} else {
|
||||
throw processError(process.error.ObjectNotFound, { _id: execution.card }, {}, true)
|
||||
}
|
||||
}
|
||||
|
||||
async function fillValue (
|
||||
value: any,
|
||||
context: SelectedContext,
|
||||
control: ProcessControl,
|
||||
execution: Execution
|
||||
): Promise<any> {
|
||||
for (const func of context.functions ?? []) {
|
||||
const transform = control.client.getModel().findObject(func.func)
|
||||
if (transform === undefined) throw processError(process.error.MethodNotFound, { methodId: func.func }, {}, true)
|
||||
if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: func.func }, {}, true)
|
||||
}
|
||||
const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl)
|
||||
const f = await getResource(funcImpl.func)
|
||||
value = await f(value, func.props, control, execution)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
async function getNestedValue (
|
||||
control: ProcessControl,
|
||||
execution: Execution,
|
||||
context: SelectedNested
|
||||
): Promise<any | ExecutionError> {
|
||||
const card = control.cache.get(execution.card)
|
||||
if (card === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.card }, {}, true)
|
||||
const val = getObjectValue(context.key, card)
|
||||
const attr = control.client.getHierarchy().findAttribute(card._class, context.path)
|
||||
if (attr === undefined) throw processError(process.error.AttributeNotExists, { key: context.path })
|
||||
const nestedValue = getObjectValue(context.path, card)
|
||||
if (nestedValue === undefined) throw processError(process.error.EmptyAttributeContextValue, {}, { attr: attr.label })
|
||||
const parentType = attr.type._class === core.class.ArrOf ? (attr.type as ArrOf<Doc>).of : attr.type
|
||||
const targetClass = parentType._class === core.class.RefTo ? (parentType as RefTo<Doc>).to : parentType._class
|
||||
const target = await control.client.findAll(targetClass, {
|
||||
_id: { $in: Array.isArray(nestedValue) ? nestedValue : [nestedValue] }
|
||||
})
|
||||
if (target.length === 0) throw processError(process.error.RelatedObjectNotFound, {}, { attr: attr.label })
|
||||
const nested = control.client.getHierarchy().findAttribute(targetClass, context.key)
|
||||
if (context.sourceFunction !== undefined) {
|
||||
const transform = control.client.getModel().findObject(context.sourceFunction)
|
||||
if (transform === undefined) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl)
|
||||
const f = await getResource(funcImpl.func)
|
||||
const reduced = await f(target, {}, control, execution)
|
||||
const val = Array.isArray(reduced)
|
||||
? reduced.map((v) => getObjectValue(context.key, v))
|
||||
: getObjectValue(context.key, reduced)
|
||||
if (val == null) {
|
||||
throw processError(
|
||||
process.error.EmptyRelatedObjectValue,
|
||||
{},
|
||||
{ parent: attr.label, attr: nested?.label ?? getEmbeddedLabel(context.key) }
|
||||
)
|
||||
}
|
||||
return val
|
||||
}
|
||||
const val =
|
||||
Array.isArray(target) && target.length > 1
|
||||
? target.map((v) => getObjectValue(context.key, v))
|
||||
: getObjectValue(context.key, target[0])
|
||||
if (val == null) {
|
||||
const attr = control.client.getHierarchy().findAttribute(card._class, context.key)
|
||||
throw processError(
|
||||
process.error.EmptyAttributeContextValue,
|
||||
process.error.EmptyRelatedObjectValue,
|
||||
{},
|
||||
{ attr: attr?.label ?? getEmbeddedLabel(context.key) }
|
||||
{ parent: attr.label, attr: nested?.label ?? getEmbeddedLabel(context.key) }
|
||||
)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
async function getRelationValue (
|
||||
control: ProcessControl,
|
||||
execution: Execution,
|
||||
context: SelectedRelation
|
||||
): Promise<any> {
|
||||
const assoc = control.client.getModel().findObject(context.association)
|
||||
if (assoc === undefined) throw processError(process.error.RelationNotExists, {})
|
||||
const targetClass = context.direction === 'A' ? assoc.classA : assoc.classB
|
||||
const q = context.direction === 'A' ? { docB: execution.card } : { docA: execution.card }
|
||||
const relations = await control.client.findAll(core.class.Relation, { association: assoc._id, ...q })
|
||||
const name = context.direction === 'A' ? assoc.nameA : assoc.nameB
|
||||
if (relations.length === 0) throw processError(process.error.RelatedObjectNotFound, { attr: name })
|
||||
const ids = relations.map((it) => {
|
||||
return context.direction === 'A' ? it.docA : it.docB
|
||||
})
|
||||
const target = await control.client.findAll(targetClass, { _id: { $in: ids } })
|
||||
if (target.length === 0) throw processError(process.error.RelatedObjectNotFound, { attr: context.name })
|
||||
const attr = context.key !== '' ? control.client.getHierarchy().findAttribute(targetClass, context.key) : undefined
|
||||
if (context.sourceFunction !== undefined) {
|
||||
const transform = control.client.getModel().findObject(context.sourceFunction)
|
||||
if (transform === undefined) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl)
|
||||
const f = await getResource(funcImpl.func)
|
||||
const reduced = await f(target, {}, control, execution)
|
||||
const val = Array.isArray(reduced)
|
||||
? reduced.map((v) => getObjectValue(context.key, v))
|
||||
: getObjectValue(context.key, reduced)
|
||||
if (val == null) {
|
||||
throw processError(
|
||||
process.error.EmptyRelatedObjectValue,
|
||||
{ parent: name },
|
||||
{ attr: attr?.label ?? getEmbeddedLabel(context.name) }
|
||||
)
|
||||
}
|
||||
return val
|
||||
}
|
||||
const val =
|
||||
Array.isArray(target) && target.length > 1
|
||||
? target.map((v) => getObjectValue(context.key, v))
|
||||
: getObjectValue(context.key, target[0])
|
||||
if (val == null) {
|
||||
throw processError(
|
||||
process.error.EmptyRelatedObjectValue,
|
||||
{ parent: name },
|
||||
{ attr: attr?.label ?? getEmbeddedLabel(context.name) }
|
||||
)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
async function getFunctionValue (
|
||||
control: ProcessControl,
|
||||
execution: Execution,
|
||||
context: SelectedContextFunc
|
||||
): Promise<any> {
|
||||
const func = control.client.getModel().findObject(context.func)
|
||||
if (func === undefined) throw processError(process.error.MethodNotFound, { methodId: context.func }, {}, true)
|
||||
const impl = control.client.getHierarchy().as(func, serverProcess.mixin.FuncImpl)
|
||||
if (impl === undefined) throw processError(process.error.MethodNotFound, { methodId: context.func }, {}, true)
|
||||
const f = await getResource(impl.func)
|
||||
const res = await f(null, context.props, control, execution)
|
||||
if (context.sourceFunction !== undefined) {
|
||||
const transform = control.client.getModel().findObject(context.sourceFunction)
|
||||
if (transform === undefined) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl)
|
||||
const f = await getResource(funcImpl.func)
|
||||
const val = await f(res, {}, control, execution)
|
||||
if (val == null) {
|
||||
throw processError(process.error.EmptyFunctionResult, {}, { func: func.label })
|
||||
}
|
||||
return val
|
||||
}
|
||||
if (res == null) {
|
||||
throw processError(process.error.EmptyFunctionResult, {}, { func: func.label })
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function getUserRequestValue (control: ProcessControl, execution: Execution, context: SelectedUserRequest): any {
|
||||
const userContext = execution.context[context.id]
|
||||
if (userContext !== undefined) return userContext
|
||||
const attr = control.client.getHierarchy().findAttribute(context._class, context.key)
|
||||
throw processError(
|
||||
process.error.UserRequestedValueNotProvided,
|
||||
{},
|
||||
{ attr: attr?.label ?? getEmbeddedLabel(context.key) }
|
||||
)
|
||||
}
|
||||
|
||||
async function getExecutionContextValue (
|
||||
control: ProcessControl,
|
||||
execution: Execution,
|
||||
context: SelectedExecutionContext
|
||||
): Promise<any> {
|
||||
const userContext = execution.context[context.id]
|
||||
const _process = control.client.getModel().findObject(execution.process)
|
||||
const processContext = _process?.context?.[context.id]
|
||||
if (userContext !== undefined) {
|
||||
if (context.key === '' || context.key === '_id') return userContext
|
||||
if (processContext !== undefined) {
|
||||
const contextVal = await control.client.findOne(processContext?._class, { _id: userContext })
|
||||
if (contextVal !== undefined) {
|
||||
const val = getObjectValue(context.key, contextVal)
|
||||
return val
|
||||
}
|
||||
}
|
||||
}
|
||||
throw processError(process.error.ContextValueNotProvided, { name: processContext?.name ?? context.id })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user