import { useCollectionManager, useCompile } from '@nocobase/client'; import { useFlowContext } from './FlowContext'; import { NAMESPACE } from './locale'; import { instructions, useAvailableUpstreams, useNodeContext } from './nodes'; import { triggers } from './triggers'; export type VariableOption = { key: string; value: string; label: string; children?: VariableOption[] | null; }; const VariableTypes = [ { title: `{{t("Node result", { ns: "${NAMESPACE}" })}}`, value: '$jobsMapByNodeId', options(types) { const current = useNodeContext(); const upstreams = useAvailableUpstreams(current); const options: VariableOption[] = []; upstreams.forEach((node) => { const instruction = instructions.get(node.type); const subOptions = instruction.getOptions?.(node.config, types); if (subOptions) { options.push({ key: node.id.toString(), value: node.id.toString(), label: node.title ?? `#${node.id}`, children: subOptions, }); } }); return options; }, }, { title: `{{t("Trigger variables", { ns: "${NAMESPACE}" })}}`, value: '$context', options(types) { const { workflow } = useFlowContext(); const trigger = triggers.get(workflow.type); return trigger?.getOptions?.(workflow.config, types) ?? null; }, }, { title: `{{t("System variables", { ns: "${NAMESPACE}" })}}`, value: '$system', options: [ { key: 'now', value: 'now', label: `{{t("Now")}}`, }, ], }, ]; export const TypeSets = { boolean: new Set(['boolean']), number: new Set(['integer', 'bigInt', 'float', 'double', 'real', 'decimal']), string: new Set(['string', 'text', 'password']), date: new Set(['date', 'time']), }; function matchFieldType(field, type): Boolean { if (typeof type === 'string') { return Boolean(TypeSets[type]?.has(field.type)); } if (typeof type === 'object' && type.type === 'reference') { return ( (field.collectionName === type.options?.collection && field.name === 'id') || (field.type === 'belongsTo' && field.target === type.options?.collection) ); } return false; } export function filterTypedFields(fields, types) { return types ? fields.filter((field) => types.some((type) => matchFieldType(field, type))) : fields; } export function useWorkflowVariableOptions() { const compile = useCompile(); const options = VariableTypes.map((item: any) => { const options = typeof item.options === 'function' ? item.options().filter(Boolean) : item.options; return { label: compile(item.title), value: item.value, key: item.value, children: compile(options), disabled: options && !options.length, }; }); return options; } function useCollectionNormalFields(collection) { const { getCollectionFields } = useCollectionManager(); const fields = getCollectionFields(collection); return fields.filter(field => field.interface); } export function useCollectionFieldOptions(options, depth = 1): VariableOption[] { const { fields, collection, types } = options; const compile = useCompile(); const result: VariableOption[] = []; filterTypedFields((fields ?? useCollectionNormalFields(collection)), types) .forEach(field => { const label = compile(field.uiSchema?.title || field.name); if (field.type === 'belongsTo') { result.push({ label: `${label} ID`, key: field.foreignKey, value: field.foreignKey, }); } result.push({ label, key: field.name, value: field.name, children: ['linkTo', 'belongsTo', 'hasOne', 'hasMany', 'belongsToMany'].includes(field.type) && depth > 0 ? useCollectionFieldOptions({ collection: field.target, types }, depth - 1) : null }); }); return result; }