Merge branch 'develop' into 2.0

This commit is contained in:
chenos 2025-07-01 07:16:28 +08:00 committed by GitHub
commit 407408b36b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 245 additions and 121 deletions

View File

@ -143,6 +143,17 @@ class Package {
}); });
console.log(chalk.greenBright(`Downloaded: ${this.packageName}@${version}`)); console.log(chalk.greenBright(`Downloaded: ${this.packageName}@${version}`));
} catch (error) { } catch (error) {
if (error.response.data && typeof error.response.data.pipe === 'function') {
let errorMessageBuffer = '';
error.response.data.on('data', (chunk) => {
errorMessageBuffer += chunk.toString('utf8'); // 收集错误信息
});
error.response.data.on('end', () => {
if (error.response.status === 403) {
console.error(chalk.redBright('You do not have permission to download this package version.'));
}
});
}
console.log(chalk.redBright(`Download failed: ${this.packageName}`)); console.log(chalk.redBright(`Download failed: ${this.packageName}`));
} }
} }
@ -252,7 +263,13 @@ module.exports = (cli) => {
NOCOBASE_PKG_USERNAME, NOCOBASE_PKG_USERNAME,
NOCOBASE_PKG_PASSWORD, NOCOBASE_PKG_PASSWORD,
} = process.env; } = process.env;
const { accessKeyId, accessKeySecret } = await getAccessKeyPair(); let accessKeyId;
let accessKeySecret;
try {
({ accessKeyId, accessKeySecret } = await getAccessKeyPair());
} catch (e) {
return;
}
if (!(NOCOBASE_PKG_USERNAME && NOCOBASE_PKG_PASSWORD) && !(accessKeyId && accessKeySecret)) { if (!(NOCOBASE_PKG_USERNAME && NOCOBASE_PKG_PASSWORD) && !(accessKeyId && accessKeySecret)) {
return; return;
} }

View File

@ -19,7 +19,7 @@ const fs = require('fs-extra');
const os = require('os'); const os = require('os');
const moment = require('moment-timezone'); const moment = require('moment-timezone');
const { keyDecrypt, getEnvAsync } = require('@nocobase/license-kit'); const { keyDecrypt, getEnvAsync } = require('@nocobase/license-kit');
const _ = require('lodash'); const omit = require('lodash/omit');
exports.isPackageValid = (pkg) => { exports.isPackageValid = (pkg) => {
try { try {
@ -491,10 +491,20 @@ exports.generatePlugins = function () {
} }
}; };
async function isEnvMatch(keyData) {
const env = await getEnvAsync();
if (env?.container?.id && keyData?.instanceData?.container?.id) {
return (
JSON.stringify(omit(env, ['timestamp', 'container', 'hostname'])) ===
JSON.stringify(omit(keyData?.instanceData, ['timestamp', 'container', 'hostname']))
);
}
return JSON.stringify(omit(env, ['timestamp'])) === JSON.stringify(omit(keyData?.instanceData, ['timestamp']));
}
exports.getAccessKeyPair = async function () { exports.getAccessKeyPair = async function () {
const keyFile = resolve(process.cwd(), 'storage/.license/license-key'); const keyFile = resolve(process.cwd(), 'storage/.license/license-key');
if (!fs.existsSync(keyFile)) { if (!fs.existsSync(keyFile)) {
// showLicenseInfo(LicenseKeyError.notExist);
return {}; return {};
} }
@ -505,13 +515,13 @@ exports.getAccessKeyPair = async function () {
keyData = JSON.parse(keyDataStr); keyData = JSON.parse(keyDataStr);
} catch (error) { } catch (error) {
showLicenseInfo(LicenseKeyError.parseFailed); showLicenseInfo(LicenseKeyError.parseFailed);
return {}; throw new Error(LicenseKeyError.parseFailed.title);
} }
const currentEnv = await getEnvAsync(); const isEnvMatched = await isEnvMatch(keyData);
if (!_.isEqual(_.omit(keyData?.instanceData, ['timestamp']), _.omit(currentEnv, ['timestamp']))) { if (!isEnvMatched) {
showLicenseInfo(LicenseKeyError.notMatch); showLicenseInfo(LicenseKeyError.notMatch);
return {}; throw new Error(LicenseKeyError.notMatch.title);
} }
const { accessKeyId, accessKeySecret } = keyData; const { accessKeyId, accessKeySecret } = keyData;
@ -520,21 +530,21 @@ exports.getAccessKeyPair = async function () {
const LicenseKeyError = { const LicenseKeyError = {
notExist: { notExist: {
title: 'License key not exist', title: 'License key not found',
content: content:
'Please go to the license settings page to obtain the Instance ID for the current environment, and then generate the license key on the service platform.', 'Please go to the license settings page to obtain the Instance ID for the current environment, and then generate the license key on the service platform.',
}, },
parseFailed: { parseFailed: {
title: 'License key parse failed', title: 'Invalid license key format',
content: 'Please check your license key, or regenerate the license key on the service platform.', content: 'Please check your license key, or regenerate the license key on the service platform.',
}, },
notMatch: { notMatch: {
title: 'License key not matched', title: 'License key mismatch',
content: content:
'Please go to the license settings page to obtain the Instance ID for the current environment, and then regenerate the license key on the service platform.', 'Please go to the license settings page to obtain the Instance ID for the current environment, and then regenerate the license key on the service platform.',
}, },
notValid: { notValid: {
title: 'License key not valid', title: 'Invalid license key',
content: content:
'Please go to the license settings page to obtain the Instance ID for the current environment, and then regenerate the license key on the service platform.', 'Please go to the license settings page to obtain the Instance ID for the current environment, and then regenerate the license key on the service platform.',
}, },

View File

@ -24,6 +24,7 @@ export class DateFieldInterface extends CollectionFieldInterface {
'x-component': 'DatePicker', 'x-component': 'DatePicker',
'x-component-props': { 'x-component-props': {
dateOnly: true, dateOnly: true,
showTime: false,
}, },
}, },
}; };

View File

@ -9,7 +9,7 @@
import { useField, useForm } from '@formily/react'; import { useField, useForm } from '@formily/react';
import { Cascader, Input, Select, Spin, Table, Tag } from 'antd'; import { Cascader, Input, Select, Spin, Table, Tag } from 'antd';
import { last, omit } from 'lodash'; import _, { last, omit } from 'lodash';
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ResourceActionContext, useCompile } from '../../../'; import { ResourceActionContext, useCompile } from '../../../';
@ -120,9 +120,10 @@ const PreviewCom = (props) => {
}, [databaseView]); }, [databaseView]);
const handleFieldChange = (record, index) => { const handleFieldChange = (record, index) => {
dataSource.splice(index, 1, record); const newDataSource = _.cloneDeep(dataSource);
setDataSource(dataSource); newDataSource[index] = record;
field.value = dataSource.map((v) => { setDataSource(newDataSource);
field.value = newDataSource.map((v) => {
const source = typeof v.source === 'string' ? v.source : v.source?.filter?.(Boolean)?.join('.'); const source = typeof v.source === 'string' ? v.source : v.source?.filter?.(Boolean)?.join('.');
return { return {
...v, ...v,
@ -198,8 +199,7 @@ const PreviewCom = (props) => {
style={{ width: '100%' }} style={{ width: '100%' }}
popupMatchSelectWidth={false} popupMatchSelectWidth={false}
onChange={(value) => { onChange={(value) => {
const interfaceConfig = getInterface(value); handleFieldChange({ ...item, interface: value }, index);
handleFieldChange({ ...item, interface: value, uiSchema: interfaceConfig?.default?.uiSchema }, index);
}} }}
> >
{data.map((group) => ( {data.map((group) => (

View File

@ -66,7 +66,7 @@ export const createFormBlockSettings = new SchemaSettings({
useVisible() { useVisible() {
const { action } = useFormBlockContext(); const { action } = useFormBlockContext();
const schema = useFieldSchema(); const schema = useFieldSchema();
return !action && schema?.['x-acl-action'].includes('create'); return !action && schema?.['x-acl-action']?.includes('create');
}, },
useComponentProps() { useComponentProps() {
const { name } = useCollection_deprecated(); const { name } = useCollection_deprecated();

View File

@ -10,6 +10,7 @@
import { getDefaultFormat, str2moment, toGmt, toLocal, getPickerFormat } from '@nocobase/utils/client'; import { getDefaultFormat, str2moment, toGmt, toLocal, getPickerFormat } from '@nocobase/utils/client';
import type { Dayjs } from 'dayjs'; import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { dayjsable, formatDayjsValue } from '@formily/antd-v5/esm/__builtins__';
const toStringByPicker = (value, picker = 'date', timezone: 'gmt' | 'local') => { const toStringByPicker = (value, picker = 'date', timezone: 'gmt' | 'local') => {
if (!dayjs.isDayjs(value)) return value; if (!dayjs.isDayjs(value)) return value;
@ -89,7 +90,7 @@ export const handleDateChangeOnForm = (value, dateOnly, utc, picker, showTime, g
return value; return value;
} }
if (dateOnly) { if (dateOnly) {
return dayjs(value).startOf(picker).format('YYYY-MM-DD'); return formatDayjsValue(value, 'YYYY-MM-DD');
} }
if (utc) { if (utc) {
if (gmt) { if (gmt) {
@ -114,6 +115,7 @@ export const mapDatePicker = function () {
const { dateOnly, showTime, picker = 'date', utc, gmt, underFilter } = props; const { dateOnly, showTime, picker = 'date', utc, gmt, underFilter } = props;
const format = getDefaultFormat(props); const format = getDefaultFormat(props);
const onChange = props.onChange; const onChange = props.onChange;
return { return {
...props, ...props,
inputReadOnly: isMobileMedia, inputReadOnly: isMobileMedia,

View File

@ -33,7 +33,7 @@ const toDate = (date, options: any = {}) => {
} }
if (field.constructor.name === 'DateOnlyField') { if (field.constructor.name === 'DateOnlyField') {
val = moment(val).format('YYYY-MM-DD HH:mm:ss'); val = moment.utc(val).format('YYYY-MM-DD HH:mm:ss');
} }
const eventObj = { const eventObj = {
@ -69,7 +69,6 @@ export default {
const r = parseDate(value, { const r = parseDate(value, {
timezone: parseDateTimezone(ctx), timezone: parseDateTimezone(ctx),
}); });
if (typeof r === 'string') { if (typeof r === 'string') {
return { return {
[Op.eq]: toDate(r, { ctx }), [Op.eq]: toDate(r, { ctx }),
@ -77,6 +76,9 @@ export default {
} }
if (Array.isArray(r)) { if (Array.isArray(r)) {
console.log(11111111, {
[Op.and]: [{ [Op.gte]: toDate(r[0], { ctx }) }, { [Op.lt]: toDate(r[1], { ctx }) }],
});
return { return {
[Op.and]: [{ [Op.gte]: toDate(r[0], { ctx }) }, { [Op.lt]: toDate(r[1], { ctx }) }], [Op.and]: [{ [Op.gte]: toDate(r[0], { ctx }) }, { [Op.lt]: toDate(r[1], { ctx }) }],
}; };

View File

@ -15,6 +15,7 @@ export interface Str2momentOptions {
picker?: 'year' | 'month' | 'week' | 'quarter'; picker?: 'year' | 'month' | 'week' | 'quarter';
utcOffset?: number; utcOffset?: number;
utc?: boolean; utc?: boolean;
dateOnly?: boolean;
} }
export type Str2momentValue = string | string[] | dayjs.Dayjs | dayjs.Dayjs[]; export type Str2momentValue = string | string[] | dayjs.Dayjs | dayjs.Dayjs[];
@ -83,10 +84,13 @@ const toMoment = (val: any, options?: Str2momentOptions) => {
return; return;
} }
const offset = options.utcOffset; const offset = options.utcOffset;
const { gmt, picker, utc = true } = options; const { gmt, picker, utc = true, dateOnly } = options;
if (dayjs(val).isValid()) { if (dayjs(val).isValid()) {
if (dateOnly) {
return dayjs.utc(val, 'YYYY-MM-DD');
}
if (!utc) { if (!utc) {
console.log(888);
return dayjs(val); return dayjs(val);
} }

View File

@ -188,9 +188,11 @@ export const parseFilter = async (filter: any, opts: ParseFilterOptions = {}) =>
const field = getField?.(path); const field = getField?.(path);
if (field?.constructor.name === 'DateOnlyField' || field?.constructor.name === 'DatetimeNoTzField') { if (field?.constructor.name === 'DateOnlyField' || field?.constructor.name === 'DatetimeNoTzField') {
if (value.type) {
return getDayRangeByParams({ ...value, timezone: field?.timezone || timezone });
}
return value; return value;
} }
return dateValueWrapper(value, field?.timezone || timezone); return dateValueWrapper(value, field?.timezone || timezone);
} }
return value; return value;

View File

@ -63,7 +63,7 @@ const useSubmitProps = () => {
}, },
}); });
setLoading(false); setLoading(false);
message.success(t('License key saved successfully, please restart the server')); message.success(t('License key saved successfully, please re-run the plugin installation.'));
} catch (e) { } catch (e) {
setLoading(false); setLoading(false);
} }

View File

@ -1,5 +1,5 @@
{ {
"License key saved successfully, please restart the server": "License key saved successfully, please restart the server", "License key saved successfully, please re-run the plugin installation.": "License key saved successfully, please re-run the plugin installation.",
"License settings": "License settings", "License settings": "License settings",
"Instance ID": "Instance ID", "Instance ID": "Instance ID",
"License key": "License key", "License key": "License key",

View File

@ -1,5 +1,5 @@
{ {
"License key saved successfully, please restart the server": "授权密钥保存成功,请重启服务器", "License key saved successfully, please re-run the plugin installation.": "授权密钥保存成功,请重新执行插件安装操作",
"License settings": "授权设置", "License settings": "授权设置",
"Instance ID": "实例 ID", "Instance ID": "实例 ID",
"License key": "授权密钥", "License key": "授权密钥",

View File

@ -30,13 +30,15 @@ import { WorkflowLink } from './WorkflowLink';
import OpenDrawer from './components/OpenDrawer'; import OpenDrawer from './components/OpenDrawer';
import { workflowSchema } from './schemas/workflows'; import { workflowSchema } from './schemas/workflows';
import { ExecutionStatusSelect, ExecutionStatusColumn } from './components/ExecutionStatus'; import { ExecutionStatusSelect, ExecutionStatusColumn } from './components/ExecutionStatus';
import WorkflowPlugin, { ExecutionStatusOptions, RadioWithTooltip } from '.'; import WorkflowPlugin from '.';
import { RadioWithTooltip } from './components';
import { useRefreshActionProps } from './hooks/useRefreshActionProps'; import { useRefreshActionProps } from './hooks/useRefreshActionProps';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { TriggerOptionRender } from './components/TriggerOptionRender'; import { TriggerOptionRender } from './components/TriggerOptionRender';
import { CategoryTabs } from './WorkflowCategoryTabs'; import { CategoryTabs } from './WorkflowCategoryTabs';
import { EnumerationField } from './components/EmunerationField'; import { EnumerationField } from './components/EmunerationField';
import { useWorkflowFilterActionProps } from './hooks/useWorkflowFilterActionProps'; import { useWorkflowFilterActionProps } from './hooks/useWorkflowFilterActionProps';
import { ExecutionStatusOptions } from './constants';
function SyncOptionSelect(props) { function SyncOptionSelect(props) {
const field = useField<any>(); const field = useField<any>();

View File

@ -100,4 +100,5 @@ export default class extends Instruction {
resultTitle: lang('Calculation result'), resultTitle: lang('Calculation result'),
}; };
} }
testable = true;
} }

View File

@ -194,4 +194,5 @@ export default class extends Instruction {
</NodeDefaultView> </NodeDefaultView>
); );
} }
testable = true;
} }

View File

@ -7,11 +7,11 @@
* For more information, please refer to: https://www.nocobase.com/agreement. * 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 { createForm, Field } from '@formily/core';
import { toJS } from '@formily/reactive'; import { toJS } from '@formily/reactive';
import { ISchema, observer, useField, useForm } from '@formily/react'; 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 { cloneDeep, get, set } from 'lodash';
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -26,7 +26,6 @@ import {
cx, cx,
useAPIClient, useAPIClient,
useActionContext, useActionContext,
useCancelAction,
useCompile, useCompile,
usePlugin, usePlugin,
useResourceActionContext, useResourceActionContext,
@ -330,6 +329,7 @@ const useRunAction = () => {
async run() { async run() {
const template = parse(node.config); const template = parse(node.config);
const config = template(toJS(values.config)); const config = template(toJS(values.config));
const logField = query('log').take() as Field;
const resultField = query('result').take() as Field; const resultField = query('result').take() as Field;
resultField.setValue(null); resultField.setValue(null);
resultField.setFeedback({}); resultField.setFeedback({});
@ -352,6 +352,7 @@ const useRunAction = () => {
messages: data.status > 0 ? [lang('Resolved')] : [lang('Failed')], messages: data.status > 0 ? [lang('Resolved')] : [lang('Failed')],
}); });
resultField.setValue(data.result); resultField.setValue(data.result);
logField.setValue(data.log || '');
} catch (err) { } catch (err) {
resultField.setFeedback({ resultField.setFeedback({
type: 'error', type: 'error',
@ -359,7 +360,6 @@ const useRunAction = () => {
}); });
} }
field.data.loading = false; field.data.loading = false;
ctx.setFormValueChanged(false);
}, },
}; };
}; };
@ -397,113 +397,163 @@ function TestFormFieldset({ value, onChange }) {
); );
} }
function LogCollapse({ value }) {
return value ? (
<Collapse
ghost
items={[
{
key: 'log',
label: lang('Log'),
children: (
<Input.TextArea
value={value}
autoSize={{ minRows: 5, maxRows: 20 }}
style={{ whiteSpace: 'pre', cursor: 'text', fontFamily: 'monospace', fontSize: '80%' }}
disabled
/>
),
},
]}
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() { function TestButton() {
const node = useNodeContext(); const node = useNodeContext();
const { values } = useForm(); const { values } = useForm();
const [visible, setVisible] = useState(false);
const template = parse(values); const template = parse(values);
const keys = template.parameters.map((item) => item.key); const keys = template.parameters.map((item) => item.key);
const form = useMemo(() => createForm(), []); 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 ( return (
<NodeContext.Provider value={{ ...node, config: values }}> <NodeContext.Provider value={{ ...node, config: values }}>
<VariableKeysContext.Provider value={keys}> <VariableKeysContext.Provider value={keys}>
<SchemaComponent <ActionContextProvider value={{ visible, setVisible: setModalVisible }}>
components={{ <Button icon={<CaretRightOutlined />} onClick={onOpen}>
Alert, {lang('Test run')}
TestFormFieldset, </Button>
}} <SchemaComponent
scope={{ components={{
useCancelAction, Alert,
useRunAction, TestFormFieldset,
}} LogCollapse,
schema={{ }}
type: 'void', scope={{
name: 'testButton', useCancelAction,
title: '{{t("Test run")}}', useRunAction,
'x-component': 'Action', }}
'x-component-props': { schema={{
icon: 'CaretRightOutlined', type: 'void',
// openSize: 'small', name: 'modal',
}, 'x-decorator': 'FormV2',
properties: { 'x-decorator-props': {
modal: { form,
type: 'void', },
'x-decorator': 'FormV2', 'x-component': 'Action.Modal',
'x-decorator-props': { title: `{{t("Test run", { ns: "workflow" })}}`,
form, properties: {
alert: {
type: 'void',
'x-component': 'Alert',
'x-component-props': {
message: `{{t("Test run will do the actual data manipulating or API calling, please use with caution.", { ns: "workflow" })}}`,
type: 'warning',
showIcon: true,
className: css`
margin-bottom: 1em;
`,
},
}, },
'x-component': 'Action.Modal', config: {
title: `{{t("Test run", { ns: "workflow" })}}`, type: 'object',
properties: { title: '{{t("Replace variables", { ns: "workflow" })}}',
alert: { 'x-decorator': 'FormItem',
type: 'void', 'x-component': 'TestFormFieldset',
'x-component': 'Alert', },
'x-component-props': { actions: {
message: `{{t("Test run will do the actual data manipulating or API calling, please use with caution.", { ns: "workflow" })}}`, type: 'void',
type: 'warning', 'x-component': 'ActionBar',
showIcon: true, properties: {
className: css` submit: {
margin-bottom: 1em; type: 'void',
`, title: '{{t("Run")}}',
}, 'x-component': 'Action',
}, 'x-component-props': {
config: { type: 'primary',
type: 'object', useAction: '{{ useRunAction }}',
title: '{{t("Replace variables", { ns: "workflow" })}}',
'x-decorator': 'FormItem',
'x-component': 'TestFormFieldset',
},
actions: {
type: 'void',
'x-component': 'ActionBar',
properties: {
submit: {
type: 'void',
title: '{{t("Run")}}',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ useRunAction }}',
},
}, },
}, },
}, },
result: { },
type: 'string', result: {
title: `{{t("Result", { ns: "workflow" })}}`, type: 'string',
'x-decorator': 'FormItem', title: `{{t("Result", { ns: "workflow" })}}`,
'x-component': 'Input.JSON', 'x-decorator': 'FormItem',
'x-component-props': { 'x-component': 'Input.JSON',
autoSize: { 'x-component-props': {
minRows: 5, autoSize: {
maxRows: 20, minRows: 5,
}, maxRows: 20,
style: {
whiteSpace: 'pre',
cursor: 'text',
},
}, },
'x-pattern': 'disabled', style: {
}, whiteSpace: 'pre',
footer: { cursor: 'text',
type: 'void',
'x-component': 'Action.Modal.Footer',
properties: {
cancel: {
type: 'void',
title: '{{t("Close")}}',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCancelAction }}',
},
},
}, },
}, },
'x-pattern': 'disabled',
},
log: {
type: 'string',
'x-component': 'LogCollapse',
}, },
}, },
}, }}
}} />
/> </ActionContextProvider>
</VariableKeysContext.Provider> </VariableKeysContext.Provider>
</NodeContext.Provider> </NodeContext.Provider>
); );

View File

@ -39,6 +39,22 @@ export class CalculationInstruction extends Instruction {
}; };
} }
} }
async test({ engine = 'math.js', expression = '' }) {
const evaluator = <Evaluator | undefined>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; export default CalculationInstruction;

View File

@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement. * 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 { Instruction } from '.';
import type Processor from '../Processor'; import type Processor from '../Processor';
import { JOB_STATUS } from '../constants'; import { JOB_STATUS } from '../constants';
@ -80,6 +80,22 @@ export class ConditionInstruction extends Instruction {
// pass control to upper scope by ending current scope // pass control to upper scope by ending current scope
return processor.exit(branchJob.status); return processor.exit(branchJob.status);
} }
async test({ engine, calculation, expression = '' }) {
const evaluator = <Evaluator | undefined>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; export default ConditionInstruction;