diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/calculation.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/calculation.tsx
index 28c9fd676e..cb4d7e2f48 100644
--- a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/calculation.tsx
+++ b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/calculation.tsx
@@ -100,4 +100,5 @@ export default class extends Instruction {
resultTitle: lang('Calculation result'),
};
}
+ testable = true;
}
diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/condition.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/condition.tsx
index c0b39d58a8..259f93896b 100644
--- a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/condition.tsx
+++ b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/condition.tsx
@@ -194,4 +194,5 @@ export default class extends Instruction {
);
}
+ testable = true;
}
diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/index.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/index.tsx
index 5f1d3ddf89..98a5c0f133 100644
--- a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/index.tsx
+++ b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/index.tsx
@@ -7,11 +7,11 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { CloseOutlined, DeleteOutlined } from '@ant-design/icons';
+import { CaretRightOutlined, CloseOutlined, DeleteOutlined } from '@ant-design/icons';
import { createForm, Field } from '@formily/core';
import { toJS } from '@formily/reactive';
import { ISchema, observer, useField, useForm } from '@formily/react';
-import { Alert, App, Button, Dropdown, Empty, Input, Space, Tag, Tooltip, message } from 'antd';
+import { Alert, App, Button, Collapse, Dropdown, Empty, Input, Space, Tag, Tooltip, message } from 'antd';
import { cloneDeep, get, set } from 'lodash';
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -26,7 +26,6 @@ import {
cx,
useAPIClient,
useActionContext,
- useCancelAction,
useCompile,
usePlugin,
useResourceActionContext,
@@ -330,6 +329,7 @@ const useRunAction = () => {
async run() {
const template = parse(node.config);
const config = template(toJS(values.config));
+ const logField = query('log').take() as Field;
const resultField = query('result').take() as Field;
resultField.setValue(null);
resultField.setFeedback({});
@@ -352,6 +352,7 @@ const useRunAction = () => {
messages: data.status > 0 ? [lang('Resolved')] : [lang('Failed')],
});
resultField.setValue(data.result);
+ logField.setValue(data.log || '');
} catch (err) {
resultField.setFeedback({
type: 'error',
@@ -359,7 +360,6 @@ const useRunAction = () => {
});
}
field.data.loading = false;
- ctx.setFormValueChanged(false);
},
};
};
@@ -397,113 +397,163 @@ function TestFormFieldset({ value, onChange }) {
);
}
+function LogCollapse({ value }) {
+ return value ? (
+
+ ),
+ },
+ ]}
+ className={css`
+ .ant-collapse-item > .ant-collapse-header {
+ padding: 0;
+ }
+ .ant-collapse-content > .ant-collapse-content-box {
+ padding: 0;
+ }
+ `}
+ />
+ ) : null;
+}
+
+function useCancelAction() {
+ const form = useForm();
+ const ctx = useActionContext();
+ return {
+ async run() {
+ const resultField = form.query('result').take() as Field;
+ resultField.setFeedback();
+ form.setValues({ result: null, log: null });
+ form.clearFormGraph('*');
+ form.reset();
+ ctx.setVisible(false);
+ },
+ };
+}
+
function TestButton() {
const node = useNodeContext();
const { values } = useForm();
+ const [visible, setVisible] = useState(false);
const template = parse(values);
const keys = template.parameters.map((item) => item.key);
const form = useMemo(() => createForm(), []);
+ const onOpen = useCallback(() => {
+ setVisible(true);
+ }, []);
+ const setModalVisible = useCallback(
+ (v: boolean) => {
+ if (v) {
+ setVisible(true);
+ return;
+ }
+ const resultField = form.query('result').take() as Field;
+ resultField?.setFeedback();
+ form.setValues({ result: null, log: null });
+ form.clearFormGraph('*');
+ form.reset();
+ setVisible(false);
+ },
+ [form],
+ );
+
return (
-
+ } onClick={onOpen}>
+ {lang('Test run')}
+
+
+ }}
+ />
+
);
diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/CalculationInstruction.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/CalculationInstruction.ts
index 6512cc4a3b..9b1afcea4c 100644
--- a/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/CalculationInstruction.ts
+++ b/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/CalculationInstruction.ts
@@ -39,6 +39,22 @@ export class CalculationInstruction extends Instruction {
};
}
}
+
+ async test({ engine = 'math.js', expression = '' }) {
+ const evaluator = evaluators.get(engine);
+ try {
+ const result = evaluator && expression ? evaluator(expression) : null;
+ return {
+ result,
+ status: JOB_STATUS.RESOLVED,
+ };
+ } catch (e) {
+ return {
+ result: e.toString(),
+ status: JOB_STATUS.ERROR,
+ };
+ }
+ }
}
export default CalculationInstruction;
diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/ConditionInstruction.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/ConditionInstruction.ts
index f9eaecd846..ce4df84bc8 100644
--- a/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/ConditionInstruction.ts
+++ b/packages/plugins/@nocobase/plugin-workflow/src/server/instructions/ConditionInstruction.ts
@@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
-import { evaluators } from '@nocobase/evaluators';
+import { Evaluator, evaluators } from '@nocobase/evaluators';
import { Instruction } from '.';
import type Processor from '../Processor';
import { JOB_STATUS } from '../constants';
@@ -80,6 +80,22 @@ export class ConditionInstruction extends Instruction {
// pass control to upper scope by ending current scope
return processor.exit(branchJob.status);
}
+
+ async test({ engine, calculation, expression = '' }) {
+ const evaluator = evaluators.get(engine);
+ try {
+ const result = evaluator ? evaluator(expression) : logicCalculate(calculation);
+ return {
+ result,
+ status: JOB_STATUS.RESOLVED,
+ };
+ } catch (e) {
+ return {
+ result: e.toString(),
+ status: JOB_STATUS.ERROR,
+ };
+ }
+ }
}
export default ConditionInstruction;