feat(plugin-workflow): support context variables from model trigger (#284)

This commit is contained in:
Junyi 2022-04-11 22:06:41 +08:00 committed by GitHub
parent 2f6aeacbd8
commit d0e524a1a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 83 additions and 23 deletions

View File

@ -40,7 +40,7 @@ export function WorkflowCanvas() {
return <div></div>; return <div></div>;
} }
const { nodes = [] } = data?.data ?? {}; const { nodes = [], ...workflow } = data?.data ?? {};
makeNodes(nodes); makeNodes(nodes);
@ -48,6 +48,7 @@ export function WorkflowCanvas() {
return ( return (
<FlowContext.Provider value={{ <FlowContext.Provider value={{
workflow,
nodes, nodes,
onNodeAdded: refresh, onNodeAdded: refresh,
onNodeRemoved: refresh onNodeRemoved: refresh

View File

@ -4,6 +4,7 @@ import { css } from "@emotion/css";
import { instructions, useNodeContext } from "./nodes"; import { instructions, useNodeContext } from "./nodes";
import { useFlowContext } from "./WorkflowCanvas"; import { useFlowContext } from "./WorkflowCanvas";
import { triggers } from "./triggers";
function NullRender() { function NullRender() {
return null; return null;
@ -68,6 +69,8 @@ export function parseStringValue(value: string, Types) {
}; };
} }
export const BaseTypeSet = new Set(['boolean', 'number', 'string', 'date']);
const ConstantTypes = { const ConstantTypes = {
string: { string: {
title: '字符串', title: '字符串',
@ -189,7 +192,25 @@ export const VariableTypes = {
return `{{${stack.join('.')}}}`; return `{{${stack.join('.')}}}`;
} }
}, },
// context: ContextSelect, context: {
title: '触发数据',
value: 'context',
component() {
const { workflow } = useFlowContext();
const trigger = triggers.get(workflow.type);
return trigger?.getter ?? NullRender;
},
parse([prefix, ...path]) {
return { path: path.join('.') };
},
stringify({ options }) {
const stack = ['context'];
if (options?.path) {
stack.push(options.path);
}
return `{{${stack.join('.')}}}`;
}
},
// calculation: Calculation // calculation: Calculation
}; };
@ -205,10 +226,15 @@ interface OperandProps {
value?: any; value?: any;
options?: any; options?: any;
}; };
onChange(v: any): void onChange(v: any): void;
children?: React.ReactNode;
} }
export function Operand({ onChange, value: operand = { type: 'constant', value: '', options: { type: 'string' } } }: OperandProps) { export function Operand({
value: operand = { type: 'constant', value: '', options: { type: 'string' } },
onChange,
children
}: OperandProps) {
const Types = useVariableTypes(); const Types = useVariableTypes();
const { type } = operand; const { type } = operand;
@ -226,13 +252,13 @@ export function Operand({ onChange, value: operand = { type: 'constant', value:
allowClear={false} allowClear={false}
value={[type, ...(appendTypeValue ? appendTypeValue(operand) : [])]} value={[type, ...(appendTypeValue ? appendTypeValue(operand) : [])]}
options={Object.values(Types).map((item: any) => { options={Object.values(Types).map((item: any) => {
const children = typeof item.options === 'function' ? item.options() : item.options; const options = typeof item.options === 'function' ? item.options() : item.options;
return { return {
label: item.title, label: item.title,
value: item.value, value: item.value,
children, children: options,
disabled: children && !children.length, disabled: options && !options.length,
isLeaf: !children isLeaf: !options
}; };
})} })}
onChange={(next: Array<string | number>) => { onChange={(next: Array<string | number>) => {
@ -246,7 +272,7 @@ export function Operand({ onChange, value: operand = { type: 'constant', value:
} }
}} }}
/> />
<VariableComponent {...operand} onChange={op => onChange({ ...op })} /> {children ?? <VariableComponent {...operand} onChange={op => onChange({ ...op })} />}
</div> </div>
); );
} }

View File

@ -161,7 +161,7 @@ export default {
{ label: '分支模式', key: 'branch', value: { rejectOnFalse: false } } { label: '分支模式', key: 'branch', value: { rejectOnFalse: false } }
], ],
render(data) { render(data) {
const { id, config: { calculation, rejectOnFalse } } = data; const { id, config: { rejectOnFalse } } = data;
const { nodes } = useFlowContext(); const { nodes } = useFlowContext();
const trueEntry = nodes.find(item => item.upstreamId === id && item.branchIndex === 1); const trueEntry = nodes.find(item => item.upstreamId === id && item.branchIndex === 1);
const falseEntry = nodes.find(item => item.upstreamId === id && item.branchIndex === 0); const falseEntry = nodes.find(item => item.upstreamId === id && item.branchIndex === 0);

View File

@ -8,9 +8,7 @@ import { css } from '@emotion/css';
import { useCollectionManager } from '../..'; import { useCollectionManager } from '../..';
import { useCollectionFilterOptions } from '../../collection-manager/action-hooks'; import { useCollectionFilterOptions } from '../../collection-manager/action-hooks';
import { useFlowContext } from '../WorkflowCanvas'; import { useFlowContext } from '../WorkflowCanvas';
import { Operand, parseStringValue, VariableTypes, VariableTypesContext } from '../calculators'; import { Operand, parseStringValue, VariableTypes, VariableTypesContext, BaseTypeSet } from '../calculators';
const BaseTypeSet = new Set(['boolean', 'number', 'string', 'date']);
export default { export default {
title: '数据查询', title: '数据查询',
@ -89,10 +87,7 @@ export default {
constant: { constant: {
title: '常量', title: '常量',
value: 'constant', value: 'constant',
options: undefined, options: undefined
component() {
return renderSchemaComponent;
}
} }
}; };
@ -112,7 +107,9 @@ export default {
onChange(stringify(next)); onChange(stringify(next));
} }
}} }}
/> >
{operand.type === 'constant' ? renderSchemaComponent() : null}
</Operand>
</VariableTypesContext.Provider> </VariableTypesContext.Provider>
); );
} }

View File

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import { useForm } from "@formily/react"; import { ISchema, useForm } from "@formily/react";
import { cx } from "@emotion/css"; import { cx } from "@emotion/css";
import { Registry } from "@nocobase/utils";
import { SchemaComponent, useActionContext, useAPIClient, useRecord, useResourceActionContext } from '../../'; import { SchemaComponent, useActionContext, useAPIClient, useRecord, useResourceActionContext } from '../../';
import model from './model'; import model from './model';
@ -30,18 +31,30 @@ function useUpdateConfigAction() {
}; };
}; };
export interface Trigger {
const triggerTypes = { title: string;
model type: string;
// group: string;
options?: { label: string; value: any; key: string }[];
fieldset: { [key: string]: ISchema };
view?: ISchema;
scope?: { [key: string]: any };
components?: { [key: string]: any };
render?(props): React.ReactElement;
getter?(node: any): React.ReactElement;
}; };
export const triggers = new Registry<Trigger>();
triggers.register('model', model);
export const TriggerConfig = () => { export const TriggerConfig = () => {
const { data } = useResourceActionContext(); const { data } = useResourceActionContext();
if (!data) { if (!data) {
return null; return null;
} }
const { type, config } = data.data; const { type, config } = data.data;
const { title, fieldset, scope } = triggerTypes[type]; const { title, fieldset, scope } = triggers.get(type);
return ( return (
<div className={cx(nodeCardClass)}> <div className={cx(nodeCardClass)}>
<h4>{title}</h4> <h4>{title}</h4>

View File

@ -1,9 +1,15 @@
import React from 'react';
import { action } from '@formily/reactive'; import { action } from '@formily/reactive';
import { t } from 'i18next'; import { t } from 'i18next';
import { Select } from 'antd';
import { useCollectionManager } from '../../collection-manager'; import { useCollectionManager } from '../../collection-manager';
import { useFlowContext } from '../WorkflowCanvas';
import { BaseTypeSet } from '../calculators';
export default { export default {
title: '数据表事件', title: '数据表事件',
type: 'model',
fieldset: { fieldset: {
collection: { collection: {
type: 'string', type: 'string',
@ -32,5 +38,22 @@ export default {
})(collections); })(collections);
} }
} }
},
getter({ type, options, onChange }) {
const { collections = [] } = useCollectionManager();
const { workflow } = useFlowContext();
const collection = collections.find(item => item.name === workflow.config.collection) ?? { fields: [] };
return (
<Select value={options.path} placeholder="选择字段" onChange={path => {
onChange({ type, options: { ...options, path: `data.${path}` } });
}}>
{collection.fields
.filter(field => BaseTypeSet.has(field.uiSchema.type))
.map(field => (
<Select.Option key={field.name} value={field.name}>{t(field.uiSchema.title)}</Select.Option>
))}
</Select>
);
} }
}; };