From 7f15916a194c885b57bf3d92ccc2e82f87697036 Mon Sep 17 00:00:00 2001 From: "bai.zixv" Date: Fri, 31 May 2024 20:27:27 +0800 Subject: [PATCH] feat/js-parse (#1097) Reviewed-on: https://git.daoyoucloud.com/daoyoucloud/tachybase/pulls/1097 Co-authored-by: bai.zixv Co-committed-by: bai.zixv --- .../features/js-parse/JSParse.instruction.ts | 146 ++++++++++++++++++ .../src/client/features/js-parse/index.tsx | 14 ++ .../plugin-workflow/src/client/index.tsx | 2 + .../plugin-workflow/src/common/constants.ts | 1 + .../plugin-workflow/src/server/Plugin.ts | 4 + .../features/js-parse/JSParse.instruction.ts | 56 +++++++ .../src/server/features/js-parse/index.ts | 1 + .../src/server/features/js-parse/plugin.ts | 14 ++ 8 files changed, 238 insertions(+) create mode 100644 packages/plugins/@tachybase/plugin-workflow/src/client/features/js-parse/JSParse.instruction.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow/src/client/features/js-parse/index.tsx create mode 100644 packages/plugins/@tachybase/plugin-workflow/src/server/features/js-parse/JSParse.instruction.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow/src/server/features/js-parse/index.ts create mode 100644 packages/plugins/@tachybase/plugin-workflow/src/server/features/js-parse/plugin.ts diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/js-parse/JSParse.instruction.ts b/packages/plugins/@tachybase/plugin-workflow/src/client/features/js-parse/JSParse.instruction.ts new file mode 100644 index 000000000..b7e17e463 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/features/js-parse/JSParse.instruction.ts @@ -0,0 +1,146 @@ +import { css } from '@tachybase/client'; +import { ArrayTable } from '@tachybase/components'; + +import { VariableOption, WorkflowVariableInput } from '../..'; +import { NAMESPACE_INSTRUCTION_JS_PARSE } from '../../../common/constants'; +import { tval } from '../../locale'; +import { Instruction } from '../../nodes'; + +export class JSParseInstruction extends Instruction { + title = tval('JS Parse'); + type = NAMESPACE_INSTRUCTION_JS_PARSE; + // XXX: 这里应该定义在 workflow 里的一个统一的地方. workflow 本身没处理好这块, 先这样直接写了. + group = 'extended'; + description = tval('Get specific data from JSON result of any node BY js code;'); + fieldset = { + source: { + type: 'string', + title: tval('Data source'), + 'x-decorator': 'FormItem', + 'x-component': 'WorkflowVariableInput', + 'x-component-props': { + changeOnSelect: true, + }, + required: true, + }, + JSCode: { + type: 'string', + title: tval('JSCode expression'), + 'x-decorator': 'FormItem', + 'x-component': 'Input.TextArea', + required: true, + }, + model: { + type: 'array', + title: tval('Properties mapping'), + description: tval( + 'If the type of query result is object or array of object, could map the properties which to be accessed in subsequent nodes.', + ), + 'x-decorator': 'FormItem', + 'x-component': 'ArrayTable', + items: { + type: 'object', + properties: { + path: { + type: 'void', + 'x-component': 'ArrayTable.Column', + 'x-component-props': { + title: tval('Property path'), + }, + properties: { + path: { + type: 'string', + name: 'path', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + }, + }, + alias: { + type: 'void', + 'x-component': 'ArrayTable.Column', + 'x-component-props': { + title: tval('Alias'), + }, + properties: { + alias: { + type: 'string', + name: 'alias', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + }, + }, + label: { + type: 'void', + 'x-component': 'ArrayTable.Column', + 'x-component-props': { + title: tval('Label'), + }, + properties: { + label: { + type: 'string', + name: 'label', + required: true, + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + }, + }, + operations: { + type: 'void', + 'x-component': 'ArrayTable.Column', + 'x-component-props': { + dataIndex: 'operations', + fixed: 'right', + className: css` + > *:not(:last-child) { + margin-right: 0.5em; + } + button { + padding: 0; + } + `, + }, + properties: { + remove: { + type: 'void', + 'x-component': 'ArrayTable.Remove', + }, + }, + }, + }, + }, + properties: { + add: { + type: 'void', + title: tval('Add property'), + 'x-component': 'ArrayTable.Addition', + 'x-component-props': { + defaultValue: {}, + }, + }, + }, + }, + }; + components = { + ArrayTable, + WorkflowVariableInput, + }; + + useVariables(node, options): VariableOption { + const { key, title, config } = node; + const { types, fieldNames } = options; + const model = config.model || []; + const result = { + [fieldNames.label]: title, + [fieldNames.value]: key, + [fieldNames.children]: model.map((item) => ({ + [fieldNames.label]: item.label, + [fieldNames.value]: item.alias || item.path, + })), + }; + return result; + } +} diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/js-parse/index.tsx b/packages/plugins/@tachybase/plugin-workflow/src/client/features/js-parse/index.tsx new file mode 100644 index 000000000..7d5f1b74f --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/features/js-parse/index.tsx @@ -0,0 +1,14 @@ +import { Plugin } from '@tachybase/client'; + +import PluginWorkflow from '../..'; +import { NAMESPACE_INSTRUCTION_JS_PARSE } from '../../../common/constants'; +import { JSParseInstruction } from './JSParse.instruction'; + +export class PluginWorkflowJSParseClient extends Plugin { + async load() { + const pluginWorkflow = this.app.pm.get(PluginWorkflow); + pluginWorkflow.registerInstruction(NAMESPACE_INSTRUCTION_JS_PARSE, JSParseInstruction); + } +} + +export default PluginWorkflowJSParseClient; diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/index.tsx b/packages/plugins/@tachybase/plugin-workflow/src/client/index.tsx index d149778f2..efa7fcaf4 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/client/index.tsx +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/index.tsx @@ -7,6 +7,7 @@ import { PluginActionTrigger } from './features/action-trigger'; import { PluginAggregate } from './features/aggregate'; import { PluginDelay } from './features/delay'; import { PluginDaynamicCalculation } from './features/dynamic-calculation'; +import PluginWorkflowJSParseClient from './features/js-parse'; import PluginWorkflowJsonParseClient from './features/json-parse'; import { PluginLoop } from './features/loop'; import { PluginManual } from './features/manual'; @@ -77,6 +78,7 @@ export default class PluginWorkflowClient extends Plugin { await this.pm.add(PluginAggregate); await this.pm.add(PluginActionTrigger); await this.pm.add(PluginWorkflowJsonParseClient); + await this.pm.add(PluginWorkflowJSParseClient); } async load() { diff --git a/packages/plugins/@tachybase/plugin-workflow/src/common/constants.ts b/packages/plugins/@tachybase/plugin-workflow/src/common/constants.ts index d51c7b73f..c811a64f3 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/common/constants.ts +++ b/packages/plugins/@tachybase/plugin-workflow/src/common/constants.ts @@ -1 +1,2 @@ export const NAMESPACE_INSTRUCTION_JSON_PARSE = 'json-parse'; +export const NAMESPACE_INSTRUCTION_JS_PARSE = 'js-parse'; diff --git a/packages/plugins/@tachybase/plugin-workflow/src/server/Plugin.ts b/packages/plugins/@tachybase/plugin-workflow/src/server/Plugin.ts index ce50a0de8..27b2cbdeb 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/server/Plugin.ts +++ b/packages/plugins/@tachybase/plugin-workflow/src/server/Plugin.ts @@ -12,6 +12,7 @@ import { PluginActionTrigger } from './features/action-trigger/Plugin'; import { PluginAggregate } from './features/aggregate/Plugin'; import { PluginDelay } from './features/delay/Plugin'; import { PluginDynamicCalculation } from './features/dynamic-calculation/Plugin'; +import PluginWorkflowJSParseServer from './features/js-parse/plugin'; import PluginWorkflowJSONParseServer from './features/json-parse/plugin'; import { PluginLoop } from './features/loop/Plugin'; import { PluginManual } from './features/manual/Plugin'; @@ -64,6 +65,7 @@ export default class PluginWorkflowServer extends Plugin { pluginAggregate: PluginAggregate; pluginActionTrigger: PluginActionTrigger; pluginJSONParse: PluginWorkflowJSONParseServer; + pluginJSParse: PluginWorkflowJSParseServer; constructor(app: Application, options?: PluginOptions) { super(app, options); @@ -77,6 +79,7 @@ export default class PluginWorkflowServer extends Plugin { this.pluginAggregate = new PluginAggregate(app, options); this.pluginActionTrigger = new PluginActionTrigger(app, options); this.pluginJSONParse = new PluginWorkflowJSONParseServer(app, options); + this.pluginJSParse = new PluginWorkflowJSParseServer(app, options); } getLogger(workflowId: ID): Logger { @@ -301,6 +304,7 @@ export default class PluginWorkflowServer extends Plugin { await this.pluginParallel.load(); await this.pluginRequest.load(); await this.pluginJSONParse.load(); + await this.pluginJSParse.load(); } toggle(workflow: WorkflowModel, enable?: boolean) { diff --git a/packages/plugins/@tachybase/plugin-workflow/src/server/features/js-parse/JSParse.instruction.ts b/packages/plugins/@tachybase/plugin-workflow/src/server/features/js-parse/JSParse.instruction.ts new file mode 100644 index 000000000..f82ddf4fa --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow/src/server/features/js-parse/JSParse.instruction.ts @@ -0,0 +1,56 @@ +import _ from 'lodash'; + +import { FlowNodeModel, Instruction, JOB_STATUS, Processor } from '../..'; + +export class JSParseInstruction extends Instruction { + async run(node: FlowNodeModel, input: any, processor: Processor) { + const { source = '', JSCode = '', model } = node.config; + const data = processor.getParsedValue(source, node.id); + const query = evalSimulate; + try { + let result = query ? await query(JSCode, { scopes: data, handlers: {}, modules: {} }) : data; + + if (typeof result === 'object' && result && model?.length) { + if (Array.isArray(result)) { + result = result.map((item) => mapModel(item, model)); + } else { + result = mapModel(result, model); + } + } + + return { + result, + status: JOB_STATUS.RESOLVED, + }; + } catch (err) { + return { + result: err.toString(), + status: JOB_STATUS.ERROR, + }; + } + } +} + +function mapModel(data, model) { + if (typeof data !== 'object' || data === null) { + throw new Error('Invalid data: data should be a non-null object'); + } + + const result = model.reduce((acc, { path, alias }) => { + const key = alias ?? path.replace(/\./g, '_'); + const value = _.get(data, path); + acc[key] = value; + + return acc; + }, {}); + + return result; +} + +async function evalSimulate(jsCode, { scopes, handlers, modules }) { + try { + return new Function('$root', `with($root) { ${jsCode}; }`)({ scopes, handlers, modules }); + } catch (err) { + console.log('err', err); + } +} diff --git a/packages/plugins/@tachybase/plugin-workflow/src/server/features/js-parse/index.ts b/packages/plugins/@tachybase/plugin-workflow/src/server/features/js-parse/index.ts new file mode 100644 index 000000000..b68aea57f --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow/src/server/features/js-parse/index.ts @@ -0,0 +1 @@ +export { default } from './plugin'; diff --git a/packages/plugins/@tachybase/plugin-workflow/src/server/features/js-parse/plugin.ts b/packages/plugins/@tachybase/plugin-workflow/src/server/features/js-parse/plugin.ts new file mode 100644 index 000000000..100b4ee9f --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow/src/server/features/js-parse/plugin.ts @@ -0,0 +1,14 @@ +import { Plugin } from '@tachybase/server'; + +import WorkflowPlugin from '../..'; +import { NAMESPACE_INSTRUCTION_JS_PARSE } from '../../../common/constants'; +import { JSParseInstruction } from './JSParse.instruction'; + +export class PluginWorkflowJSParseServer extends Plugin { + async load() { + const pluginWorkflow: any = this.app.pm.get(WorkflowPlugin); + pluginWorkflow.registerInstruction(NAMESPACE_INSTRUCTION_JS_PARSE, JSParseInstruction); + } +} + +export default PluginWorkflowJSParseServer;