2023-03-30 23:49:57 +08:00
|
|
|
import { useCollectionManager, useCompile } from '@nocobase/client';
|
|
|
|
import { useFlowContext } from './FlowContext';
|
|
|
|
import { NAMESPACE } from './locale';
|
|
|
|
import { instructions, useAvailableUpstreams, useNodeContext } from './nodes';
|
|
|
|
import { triggers } from './triggers';
|
2023-02-20 11:52:06 +08:00
|
|
|
|
2023-03-10 16:36:58 +08:00
|
|
|
export type VariableOption = {
|
2023-04-08 10:52:31 +08:00
|
|
|
key?: string;
|
2023-03-10 16:36:58 +08:00
|
|
|
value: string;
|
|
|
|
label: string;
|
2023-04-08 10:52:31 +08:00
|
|
|
children?: VariableOptions;
|
2023-03-10 16:36:58 +08:00
|
|
|
};
|
2023-02-20 11:52:06 +08:00
|
|
|
|
2023-04-08 10:52:31 +08:00
|
|
|
export type VariableOptions = VariableOption[] | null;
|
|
|
|
|
2023-02-20 11:52:06 +08:00
|
|
|
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',
|
2023-04-08 10:52:31 +08:00
|
|
|
options(types) {
|
|
|
|
return [
|
|
|
|
...(!types || types.includes('date') ? [
|
|
|
|
{
|
|
|
|
key: 'now',
|
|
|
|
value: 'now',
|
|
|
|
label: `{{t("System time")}}`,
|
|
|
|
}
|
|
|
|
] : [])
|
|
|
|
];
|
|
|
|
}
|
2023-03-30 23:49:57 +08:00
|
|
|
},
|
2023-02-20 11:52:06 +08:00
|
|
|
];
|
|
|
|
|
2023-04-08 10:52:31 +08:00
|
|
|
export const BaseTypeSets = {
|
|
|
|
boolean: new Set(['checkbox']),
|
|
|
|
number: new Set(['number', 'percent']),
|
|
|
|
string: new Set(['input', 'password', 'email', 'phone', 'select', 'radioGroup', 'text', 'markdown', 'richText', 'expression', 'time']),
|
|
|
|
date: new Set(['date', 'createdAt', 'updatedAt']),
|
2023-03-30 23:49:57 +08:00
|
|
|
};
|
2023-02-20 11:52:06 +08:00
|
|
|
|
2023-04-08 10:52:31 +08:00
|
|
|
// { type: 'reference', options: { collection: 'users', multiple: false } }
|
|
|
|
// { type: 'reference', options: { collection: 'attachments', multiple: false } }
|
|
|
|
// { type: 'reference', options: { collection: 'myExpressions', entity: false } }
|
|
|
|
|
2023-02-20 11:52:06 +08:00
|
|
|
function matchFieldType(field, type): Boolean {
|
2023-04-08 10:52:31 +08:00
|
|
|
const inputType = typeof type;
|
|
|
|
if (inputType === 'string') {
|
|
|
|
return Boolean(BaseTypeSets[type]?.has(field.interface));
|
2023-02-20 11:52:06 +08:00
|
|
|
}
|
|
|
|
|
2023-04-08 10:52:31 +08:00
|
|
|
if (inputType === 'object' && type.type === 'reference') {
|
|
|
|
if (isAssociationField(field)) {
|
|
|
|
return type.options?.entity && (field.collectionName === type.options?.collection || type.options?.collection === '*');
|
|
|
|
} else if (field.isForeignKey) {
|
|
|
|
return (
|
|
|
|
(field.collectionName === type.options?.collection && field.name === 'id') ||
|
|
|
|
(field.target === type.options?.collection)
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inputType === 'function') {
|
|
|
|
return type(field);
|
2023-02-20 11:52:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-04-08 10:52:31 +08:00
|
|
|
function isAssociationField(field): boolean {
|
|
|
|
return ['belongsTo', 'hasOne', 'hasMany', 'belongsToMany'].includes(field.type);
|
2023-02-20 11:52:06 +08:00
|
|
|
}
|
|
|
|
|
2023-04-08 10:52:31 +08:00
|
|
|
export function filterTypedFields(fields, types, depth = 1) {
|
|
|
|
if (!types) {
|
|
|
|
return fields;
|
|
|
|
}
|
|
|
|
return fields.filter((field) => {
|
|
|
|
if (isAssociationField(field) && depth && filterTypedFields(useNormallizedFields(field.target), types, depth - 1).length) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return types.some((type) => matchFieldType(field, type));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export function useWorkflowVariableOptions(types?) {
|
2023-02-20 11:52:06 +08:00
|
|
|
const compile = useCompile();
|
|
|
|
const options = VariableTypes.map((item: any) => {
|
2023-04-08 10:52:31 +08:00
|
|
|
const opts = typeof item.options === 'function' ? item.options(types).filter(Boolean) : item.options;
|
2023-02-20 11:52:06 +08:00
|
|
|
return {
|
|
|
|
label: compile(item.title),
|
|
|
|
value: item.value,
|
|
|
|
key: item.value,
|
2023-04-08 10:52:31 +08:00
|
|
|
children: compile(opts),
|
|
|
|
disabled: opts && !opts.length,
|
2023-02-20 11:52:06 +08:00
|
|
|
};
|
|
|
|
});
|
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
2023-04-08 10:52:31 +08:00
|
|
|
function useNormallizedFields(collection) {
|
|
|
|
const compile = useCompile();
|
|
|
|
const { getCollection } = useCollectionManager();
|
|
|
|
if (!collection) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
const { fields } = getCollection(collection);
|
|
|
|
const foreignKeyFields: any[] = [];
|
|
|
|
const otherFields: any[] = [];
|
|
|
|
fields.forEach(field => {
|
|
|
|
if (field.isForeignKey) {
|
|
|
|
foreignKeyFields.push(field);
|
|
|
|
} else {
|
|
|
|
otherFields.push(field);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
for (let i = otherFields.length - 1; i >= 0; i--) {
|
|
|
|
const field = otherFields[i];
|
|
|
|
if (field.type === 'belongsTo') {
|
|
|
|
const foreignKeyField = foreignKeyFields.find(f => f.name === field.foreignKey);
|
|
|
|
if (foreignKeyField) {
|
|
|
|
otherFields.splice(i, 0, {
|
|
|
|
...foreignKeyField,
|
|
|
|
uiSchema: {
|
|
|
|
...field.uiSchema,
|
|
|
|
title: field.uiSchema?.title ? `${compile(field.uiSchema?.title)} ID` : foreignKeyField.name,
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
otherFields.splice(i, 0, {
|
|
|
|
...field,
|
|
|
|
name: field.foreignKey,
|
|
|
|
type: 'bigInt',
|
|
|
|
isForeignKey: true,
|
|
|
|
interface: field.interface,
|
|
|
|
uiSchema: {
|
|
|
|
...field.uiSchema,
|
|
|
|
title: field.uiSchema?.title ? `${compile(field.uiSchema?.title)} ID` : field.name,
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (field.type === 'context' && field.collectionName === 'users') {
|
|
|
|
const belongsToField = otherFields.find(f => f.type === 'belongsTo' && f.target === 'users' && f.foreignKey === field.name) ?? {};
|
|
|
|
otherFields.splice(i, 0, {
|
|
|
|
...field,
|
|
|
|
type: field.dataType,
|
|
|
|
interface: belongsToField.interface,
|
|
|
|
uiSchema: {
|
|
|
|
...belongsToField.uiSchema,
|
|
|
|
title: belongsToField.uiSchema?.title ? `${compile(belongsToField.uiSchema?.title)} ID` : field.name
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return otherFields.filter(field => field.interface && !field.hidden);
|
2023-04-02 13:24:25 +08:00
|
|
|
}
|
|
|
|
|
2023-04-08 10:52:31 +08:00
|
|
|
export function useCollectionFieldOptions(options): VariableOption[] {
|
|
|
|
const { fields, collection, types, depth = 1 } = options;
|
2023-04-02 13:24:25 +08:00
|
|
|
const compile = useCompile();
|
2023-04-08 10:52:31 +08:00
|
|
|
const normalizedFields = fields ?? useNormallizedFields(collection);
|
|
|
|
const result: VariableOption[] = filterTypedFields(normalizedFields, types, depth)
|
|
|
|
.filter(field => !isAssociationField(field) || depth)
|
|
|
|
.map(field => {
|
2023-04-02 13:24:25 +08:00
|
|
|
const label = compile(field.uiSchema?.title || field.name);
|
2023-04-08 10:52:31 +08:00
|
|
|
return {
|
2023-04-02 13:24:25 +08:00
|
|
|
label,
|
|
|
|
key: field.name,
|
|
|
|
value: field.name,
|
2023-04-08 10:52:31 +08:00
|
|
|
children: isAssociationField(field) && depth
|
|
|
|
? useCollectionFieldOptions({ collection: field.target, types, depth: depth - 1 })
|
2023-04-02 13:24:25 +08:00
|
|
|
: null
|
2023-04-08 10:52:31 +08:00
|
|
|
};
|
2023-04-02 13:24:25 +08:00
|
|
|
});
|
2023-03-10 16:36:58 +08:00
|
|
|
|
|
|
|
return result;
|
2023-02-20 11:52:06 +08:00
|
|
|
}
|