diff --git a/packages/plugins/@tachybase/plugin-workflow/package.json b/packages/plugins/@tachybase/plugin-workflow/package.json index 7299172ab..8e0a4eca7 100644 --- a/packages/plugins/@tachybase/plugin-workflow/package.json +++ b/packages/plugins/@tachybase/plugin-workflow/package.json @@ -12,6 +12,9 @@ "./client": "./dist/client/index.js" }, "main": "./dist/server/index.js", + "dependencies": { + "jsonata": "^2.0.5" + }, "devDependencies": { "@ant-design/icons": "~5.3.6", "@tachybase/components": "workspace:*", diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/json-parse/JSONParse.instruction.ts b/packages/plugins/@tachybase/plugin-workflow/src/client/features/json-parse/JSONParse.instruction.ts new file mode 100644 index 000000000..f4456201d --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/features/json-parse/JSONParse.instruction.ts @@ -0,0 +1,128 @@ +import { css } from '@tachybase/client'; +import { ArrayTable } from '@tachybase/components'; +import { Instruction, WorkflowVariableInput } from '@tachybase/plugin-workflow/client'; + +import { NAMESPACE_INSTRUCTION_JSON_PARSE } from '../../../common/constants'; +import { tval } from '../../locale'; + +export class JSONParseInstruction extends Instruction { + title = tval('JSON Parse'); + type = NAMESPACE_INSTRUCTION_JSON_PARSE; + // XXX: 这里应该定义在 workflow 里的一个统一的地方. workflow 本身没处理好这块, 先这样直接写了. + group = 'extended'; + description = tval('Get specific data from JSON result of any node BY jsonata; https://jsonata.org/'); + fieldset = { + source: { + type: 'string', + title: tval('Data source'), + 'x-decorator': 'FormItem', + 'x-component': 'WorkflowVariableInput', + 'x-component-props': { + changeOnSelect: true, + }, + required: true, + }, + expression: { + type: 'string', + title: tval('Query expression'), + 'x-decorator': 'FormItem', + 'x-component': 'Input', + 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.', + ), + 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, + }; +} diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/features/json-parse/index.tsx b/packages/plugins/@tachybase/plugin-workflow/src/client/features/json-parse/index.tsx new file mode 100644 index 000000000..b98c281ef --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/features/json-parse/index.tsx @@ -0,0 +1,14 @@ +import { Plugin } from '@tachybase/client'; + +import PluginWorkflow from '../..'; +import { NAMESPACE_INSTRUCTION_JSON_PARSE } from '../../../common/constants'; +import { JSONParseInstruction } from './JSONParse.instruction'; + +export class PluginWorkflowJsonParseClient extends Plugin { + async load() { + const pluginWorkflow = this.app.pm.get(PluginWorkflow); + pluginWorkflow.registerInstruction(NAMESPACE_INSTRUCTION_JSON_PARSE, JSONParseInstruction); + } +} + +export default PluginWorkflowJsonParseClient; diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/index.tsx b/packages/plugins/@tachybase/plugin-workflow/src/client/index.tsx index 90ddd6fd4..d149778f2 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/client/index.tsx +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/index.tsx @@ -1,34 +1,34 @@ import React from 'react'; - import { Plugin } from '@tachybase/client'; import { Registry } from '@tachybase/utils/client'; import { ExecutionPage } from './ExecutionPage'; -import { WorkflowPage } from './WorkflowPage'; -import { WorkflowPane } from './WorkflowPane'; -import { Trigger } from './triggers'; -import CollectionTrigger from './triggers/collection'; -import ScheduleTrigger from './triggers/schedule'; +import { PluginActionTrigger } from './features/action-trigger'; +import { PluginAggregate } from './features/aggregate'; +import { PluginDelay } from './features/delay'; +import { PluginDaynamicCalculation } from './features/dynamic-calculation'; +import PluginWorkflowJsonParseClient from './features/json-parse'; +import { PluginLoop } from './features/loop'; +import { PluginManual } from './features/manual'; +import { PluginParallel } from './features/parallel'; +import { PluginRequest } from './features/request'; +import { PluginSql } from './features/sql'; +import { NAMESPACE } from './locale'; import { Instruction } from './nodes'; import CalculationInstruction from './nodes/calculation'; import ConditionInstruction from './nodes/condition'; +import CreateInstruction from './nodes/create'; +import DestroyInstruction from './nodes/destroy'; import EndInstruction from './nodes/end'; import QueryInstruction from './nodes/query'; -import CreateInstruction from './nodes/create'; import UpdateInstruction from './nodes/update'; -import DestroyInstruction from './nodes/destroy'; -import { getWorkflowDetailPath, getWorkflowExecutionsPath } from './utils'; -import { NAMESPACE } from './locale'; import { customizeSubmitToWorkflowActionSettings } from './settings/customizeSubmitToWorkflowActionSettings'; -import { PluginSql } from './features/sql'; -import { PluginRequest } from './features/request'; -import { PluginParallel } from './features/parallel'; -import { PluginLoop } from './features/loop'; -import { PluginDaynamicCalculation } from './features/dynamic-calculation'; -import { PluginDelay } from './features/delay'; -import { PluginAggregate } from './features/aggregate'; -import { PluginActionTrigger } from './features/action-trigger'; -import { PluginManual } from './features/manual'; +import { Trigger } from './triggers'; +import CollectionTrigger from './triggers/collection'; +import ScheduleTrigger from './triggers/schedule'; +import { getWorkflowDetailPath, getWorkflowExecutionsPath } from './utils'; +import { WorkflowPage } from './WorkflowPage'; +import { WorkflowPane } from './WorkflowPane'; export default class PluginWorkflowClient extends Plugin { triggers = new Registry(); @@ -76,6 +76,7 @@ export default class PluginWorkflowClient extends Plugin { await this.pm.add(PluginDelay); await this.pm.add(PluginAggregate); await this.pm.add(PluginActionTrigger); + await this.pm.add(PluginWorkflowJsonParseClient); } async load() { diff --git a/packages/plugins/@tachybase/plugin-workflow/src/client/locale/index.ts b/packages/plugins/@tachybase/plugin-workflow/src/client/locale/index.ts index e7dd9888c..fecc25b3a 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/client/locale/index.ts +++ b/packages/plugins/@tachybase/plugin-workflow/src/client/locale/index.ts @@ -1,4 +1,4 @@ -import { i18n } from '@tachybase/client'; +import { i18n, tval as nTval } from '@tachybase/client'; import { useTranslation } from 'react-i18next'; export const NAMESPACE = 'workflow'; @@ -10,3 +10,6 @@ export function lang(key: string, options = {}) { export function useWorkflowTranslation() { return useTranslation(NAMESPACE); } + +export const tval = (key: string) => nTval(key, { ns: NAMESPACE }); + diff --git a/packages/plugins/@tachybase/plugin-workflow/src/common/constants.ts b/packages/plugins/@tachybase/plugin-workflow/src/common/constants.ts new file mode 100644 index 000000000..d51c7b73f --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow/src/common/constants.ts @@ -0,0 +1 @@ +export const NAMESPACE_INSTRUCTION_JSON_PARSE = 'json-parse'; diff --git a/packages/plugins/@tachybase/plugin-workflow/src/server/Plugin.ts b/packages/plugins/@tachybase/plugin-workflow/src/server/Plugin.ts index 8c8d1505f..ce50a0de8 100644 --- a/packages/plugins/@tachybase/plugin-workflow/src/server/Plugin.ts +++ b/packages/plugins/@tachybase/plugin-workflow/src/server/Plugin.ts @@ -1,38 +1,37 @@ import path from 'path'; - -import LRUCache from 'lru-cache'; - import { Op, Transactionable } from '@tachybase/database'; +import { Logger, LoggerOptions } from '@tachybase/logger'; import Application, { Plugin, PluginOptions } from '@tachybase/server'; import { Registry } from '@tachybase/utils'; -import { Logger, LoggerOptions } from '@tachybase/logger'; -import Processor from './Processor'; +import LRUCache from 'lru-cache'; + import initActions from './actions'; import { EXECUTION_STATUS } from './constants'; +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 PluginWorkflowJSONParseServer from './features/json-parse/plugin'; +import { PluginLoop } from './features/loop/Plugin'; +import { PluginManual } from './features/manual/Plugin'; +import { PluginParallel } from './features/parallel/Plugin'; +import { PluginRequest } from './features/request/Plugin'; +import { PluginSql } from './features/sql/Plugin'; import initFunctions, { CustomFunction } from './functions'; -import Trigger from './triggers'; -import CollectionTrigger from './triggers/CollectionTrigger'; -import ScheduleTrigger from './triggers/ScheduleTrigger'; import { Instruction, InstructionInterface } from './instructions'; import CalculationInstruction from './instructions/CalculationInstruction'; import ConditionInstruction from './instructions/ConditionInstruction'; -import EndInstruction from './instructions/EndInstruction'; import CreateInstruction from './instructions/CreateInstruction'; import DestroyInstruction from './instructions/DestroyInstruction'; +import EndInstruction from './instructions/EndInstruction'; import QueryInstruction from './instructions/QueryInstruction'; import UpdateInstruction from './instructions/UpdateInstruction'; - +import Processor from './Processor'; +import Trigger from './triggers'; +import CollectionTrigger from './triggers/CollectionTrigger'; +import ScheduleTrigger from './triggers/ScheduleTrigger'; import type { ExecutionModel, JobModel, WorkflowModel } from './types'; -import { PluginSql } from './features/sql/Plugin'; -import { PluginRequest } from './features/request/Plugin'; -import { PluginParallel } from './features/parallel/Plugin'; -import { PluginManual } from './features/manual/Plugin'; -import { PluginLoop } from './features/loop/Plugin'; -import { PluginDynamicCalculation } from './features/dynamic-calculation/Plugin'; -import { PluginDelay } from './features/delay/Plugin'; -import { PluginAggregate } from './features/aggregate/Plugin'; -import { PluginActionTrigger } from './features/action-trigger/Plugin'; type ID = number | string; @@ -64,6 +63,7 @@ export default class PluginWorkflowServer extends Plugin { pluginDelay: PluginDelay; pluginAggregate: PluginAggregate; pluginActionTrigger: PluginActionTrigger; + pluginJSONParse: PluginWorkflowJSONParseServer; constructor(app: Application, options?: PluginOptions) { super(app, options); @@ -76,6 +76,7 @@ export default class PluginWorkflowServer extends Plugin { this.pluginDelay = new PluginDelay(app, options); this.pluginAggregate = new PluginAggregate(app, options); this.pluginActionTrigger = new PluginActionTrigger(app, options); + this.pluginJSONParse = new PluginWorkflowJSONParseServer(app, options); } getLogger(workflowId: ID): Logger { @@ -299,6 +300,7 @@ export default class PluginWorkflowServer extends Plugin { await this.pluginManual.load(); await this.pluginParallel.load(); await this.pluginRequest.load(); + await this.pluginJSONParse.load(); } toggle(workflow: WorkflowModel, enable?: boolean) { diff --git a/packages/plugins/@tachybase/plugin-workflow/src/server/features/json-parse/JSONParse.instruction.ts b/packages/plugins/@tachybase/plugin-workflow/src/server/features/json-parse/JSONParse.instruction.ts new file mode 100644 index 000000000..528f33e2b --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow/src/server/features/json-parse/JSONParse.instruction.ts @@ -0,0 +1,50 @@ +import jsonata from 'jsonata'; +import _ from 'lodash'; + +import { FlowNodeModel, Instruction, JOB_STATUS, Processor } from '../..'; + +export class JSONParseInstruction extends Instruction { + engine = (expression, scope) => jsonata(expression).evaluate(scope); + async run(node: FlowNodeModel, input: any, processor: Processor) { + const { source = '', expression = '', model } = node.config; + const data = processor.getParsedValue(source, node.id); + const query = this.engine; + try { + let result = query ? await query(expression, data) : 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; +} diff --git a/packages/plugins/@tachybase/plugin-workflow/src/server/features/json-parse/index.ts b/packages/plugins/@tachybase/plugin-workflow/src/server/features/json-parse/index.ts new file mode 100644 index 000000000..b68aea57f --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow/src/server/features/json-parse/index.ts @@ -0,0 +1 @@ +export { default } from './plugin'; diff --git a/packages/plugins/@tachybase/plugin-workflow/src/server/features/json-parse/plugin.ts b/packages/plugins/@tachybase/plugin-workflow/src/server/features/json-parse/plugin.ts new file mode 100644 index 000000000..1e8b6e12a --- /dev/null +++ b/packages/plugins/@tachybase/plugin-workflow/src/server/features/json-parse/plugin.ts @@ -0,0 +1,14 @@ +import { Plugin } from '@tachybase/server'; + +import WorkflowPlugin from '../..'; +import { NAMESPACE_INSTRUCTION_JSON_PARSE } from '../../../common/constants'; +import { JSONParseInstruction } from './JSONParse.instruction'; + +export class PluginWorkflowJSONParseServer extends Plugin { + async load() { + const pluginWorkflow: any = this.app.pm.get(WorkflowPlugin); + pluginWorkflow.registerInstruction(NAMESPACE_INSTRUCTION_JSON_PARSE, JSONParseInstruction); + } +} + +export default PluginWorkflowJSONParseServer; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c89ef10b5..09cb8b997 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3946,6 +3946,9 @@ importers: '@tachybase/utils': specifier: workspace:* version: link:../../../core/utils + jsonata: + specifier: ^2.0.5 + version: 2.0.5 devDependencies: '@ant-design/icons': specifier: ~5.3.6 @@ -20859,6 +20862,11 @@ packages: engines: {node: '>=6'} hasBin: true + /jsonata@2.0.5: + resolution: {integrity: sha512-wEse9+QLIIU5IaCgtJCPsFi/H4F3qcikWzF4bAELZiRz08ohfx3Q6CjDRf4ZPF5P/92RI3KIHtb7u3jqPaHXdQ==} + engines: {node: '>= 8'} + dev: false + /jsonc-parser@3.2.0: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}