feat/js-parse (#1097)
Reviewed-on: daoyoucloud/tachybase#1097 Co-authored-by: bai.zixv <bai.zixv@foxmail.com> Co-committed-by: bai.zixv <bai.zixv@foxmail.com>
This commit is contained in:
		
							parent
							
								
									c8b5005bd7
								
							
						
					
					
						commit
						7f15916a19
					
				| @ -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; | ||||
|   } | ||||
| } | ||||
| @ -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; | ||||
| @ -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() { | ||||
|  | ||||
| @ -1 +1,2 @@ | ||||
| export const NAMESPACE_INSTRUCTION_JSON_PARSE = 'json-parse'; | ||||
| export const NAMESPACE_INSTRUCTION_JS_PARSE = 'js-parse'; | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -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); | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| export { default } from './plugin'; | ||||
| @ -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; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user