Compare commits

...

11 Commits

Author SHA1 Message Date
Junyi
3387366f1e
fix(plugin-workflow): except json field for load performance (#7138) 2025-07-01 10:59:30 +08:00
Junyi
88cb9884f6
refactor(database): add pool options from env (#7133)
* refactor(database): add pool options from env

* Update packages/core/database/src/helpers.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix(database): only inject env configured

* fix(server): simplify database options

* revert(server): revert create database api

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-01 10:40:15 +08:00
Zeke Zhang
22cd39552f
fix: update collection name handling in useCurrentFormVariable (#7125) 2025-07-01 10:01:50 +08:00
Katherine
026baab241
fix: filtering error on DateOnly or Datetime (without time zone) using Exact day variable (#7113)
* fix: filtering error on DateOnly or Datetime (without time zone) fields using Exact day variable

* fix: bug

* fix: bug

* fix: bug
2025-06-30 22:41:20 +11:00
ajie
d1ff280d9f
fix: setting field displayName in connected view does not take effect (#7130) 2025-06-30 17:34:17 +08:00
Junyi
88591454ec
fix(plugin-workflow): fix cycling import (#7134) 2025-06-30 15:25:57 +08:00
Junyi
3c555e9fc0
refactor(plugin-workflow): add log for node testing (#7129) 2025-06-30 11:45:39 +08:00
Junyi
6afcbffdec
fix(client): fault tolerance for settings based on x-acl-action (#7128) 2025-06-29 09:34:21 +08:00
Katherine
8d91e1fded
fix: association field default value overrides existing data in sub-table (#7120)
* fix: association field default value overrides existing data in sub-table

* fix: bug

* fix: bug
2025-06-27 14:15:56 +08:00
Katherine
940002a876
refactor: add filter support to multi-app management (#7124) 2025-06-27 14:15:34 +08:00
nocobase[bot]
a4eb026ea4 docs: update changelogs 2025-06-26 14:00:06 +00:00
23 changed files with 355 additions and 113 deletions

View File

@ -48,6 +48,14 @@ DB_PASSWORD=nocobase
# DB_LOGGING=on
# DB_UNDERSCORED=false
# @see https://sequelize.org/api/v6/class/src/sequelize.js~sequelize#instance-constructor-constructor
# DB_POOL_MAX=5
# DB_POOL_MIN=0
# DB_POOL_IDLE=10000
# DB_POOL_ACQUIRE=60000
# DB_POOL_EVICT=1000
# DB_POOL_MAX_USES=0
# sqlite only
# DB_STORAGE=storage/db/nocobase.sqlite

View File

@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v1.7.18](https://github.com/nocobase/nocobase/compare/v1.7.17...v1.7.18) - 2025-06-26
### 🚀 Improvements
- **[Workflow]** Optimize mobile style ([#7040](https://github.com/nocobase/nocobase/pull/7040)) by @mytharcher
- **[Public forms]** Optimize the performance of date components in public forms ([#7117](https://github.com/nocobase/nocobase/pull/7117)) by @zhangzhonghe
### 🐛 Bug Fixes
- **[Workflow]** Fix params of loading record in tasks ([#7123](https://github.com/nocobase/nocobase/pull/7123)) by @mytharcher
- **[WEB client]** Fix issue where blocks under pages were not displayed after setting role menu permissions ([#7112](https://github.com/nocobase/nocobase/pull/7112)) by @aaaaaajie
- **[Workflow: Approval]**
- Fix applicant variable name in trigger by @mytharcher
- Fix mobile styles by @mytharcher
- Fix error thrown when approval related collection deleted by @mytharcher
## [v1.7.17](https://github.com/nocobase/nocobase/compare/v1.7.16...v1.7.17) - 2025-06-23
### 🐛 Bug Fixes

View File

@ -5,6 +5,27 @@
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
## [v1.7.18](https://github.com/nocobase/nocobase/compare/v1.7.17...v1.7.18) - 2025-06-26
### 🚀 优化
- **[工作流]** 优化移动端样式 ([#7040](https://github.com/nocobase/nocobase/pull/7040)) by @mytharcher
- **[公开表单]** 优化公开表单中日期组件的性能 ([#7117](https://github.com/nocobase/nocobase/pull/7117)) by @zhangzhonghe
### 🐛 修复
- **[工作流]** 修复待办中心加载记录的参数 ([#7123](https://github.com/nocobase/nocobase/pull/7123)) by @mytharcher
- **[WEB 客户端]** 修复设置角色菜单权限后页面下区块不显示的问题 ([#7112](https://github.com/nocobase/nocobase/pull/7112)) by @aaaaaajie
- **[工作流:审批]**
- 修复审批触发器中申请人变量名的问题 by @mytharcher
- 修复移动端样式 by @mytharcher
- 修复审批关联表被删除后的报错 by @mytharcher
## [v1.7.17](https://github.com/nocobase/nocobase/compare/v1.7.16...v1.7.17) - 2025-06-23
### 🐛 修复

View File

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

View File

@ -9,7 +9,7 @@
import { useField, useForm } from '@formily/react';
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 { useTranslation } from 'react-i18next';
import { ResourceActionContext, useCompile } from '../../../';
@ -120,9 +120,10 @@ const PreviewCom = (props) => {
}, [databaseView]);
const handleFieldChange = (record, index) => {
dataSource.splice(index, 1, record);
setDataSource(dataSource);
field.value = dataSource.map((v) => {
const newDataSource = _.cloneDeep(dataSource);
newDataSource[index] = record;
setDataSource(newDataSource);
field.value = newDataSource.map((v) => {
const source = typeof v.source === 'string' ? v.source : v.source?.filter?.(Boolean)?.join('.');
return {
...v,
@ -198,8 +199,7 @@ const PreviewCom = (props) => {
style={{ width: '100%' }}
popupMatchSelectWidth={false}
onChange={(value) => {
const interfaceConfig = getInterface(value);
handleFieldChange({ ...item, interface: value, uiSchema: interfaceConfig?.default?.uiSchema }, index);
handleFieldChange({ ...item, interface: value }, index);
}}
>
{data.map((group) => (

View File

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

View File

@ -19,6 +19,7 @@ import { useKeepAlive } from '../../../route-switch/antd/admin-layout/KeepAlive'
import { useSchemaComponentContext } from '../../hooks';
import { AssociationFieldContext } from './context';
import { FormItem, useSchemaOptionsContext } from '../../../schema-component';
import { useCollectionRecord } from '../../../data-source';
export const AssociationFieldProvider = observer(
(props) => {
@ -28,6 +29,7 @@ export const AssociationFieldProvider = observer(
const api = useAPIClient();
const option = useSchemaOptionsContext();
const rootRef = useRef<HTMLDivElement>(null);
const record = useCollectionRecord();
// 这里有点奇怪,在 Table 切换显示的组件时,这个组件并不会触发重新渲染,所以增加这个 Hooks 让其重新渲染
useSchemaComponentContext();
@ -71,7 +73,9 @@ export const AssociationFieldProvider = observer(
if (_.isUndefined(ids) || _.isNil(ids) || _.isNaN(ids)) {
return Promise.reject(null);
}
if (record && !record.isNew) {
return Promise.reject(null);
}
return api.request({
resource: collectionField.target,
action: Array.isArray(ids) ? 'list' : 'get',

View File

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

View File

@ -64,7 +64,7 @@ export const SchemaSettingsDefaultValue = function DefaultValueConfigure(props:
const localVariables = useLocalVariables();
const collection = useCollection_deprecated();
const record = useRecord();
const { form } = useFormBlockContext();
const { form, type } = useFormBlockContext();
const { getFields } = useCollectionFilterOptionsV2(collection);
const { isInSubForm, isInSubTable } = useFlag() || {};
@ -219,7 +219,6 @@ export const SchemaSettingsDefaultValue = function DefaultValueConfigure(props:
targetField,
variables,
]);
const handleSubmit: (values: any) => void = useCallback(
(v) => {
const schema: ISchema = {
@ -227,7 +226,7 @@ export const SchemaSettingsDefaultValue = function DefaultValueConfigure(props:
};
fieldSchema.default = v.default ?? null;
if (!isVariable(v.default)) {
field.setInitialValue?.(v.default);
(record.__isNewRecord__ || type === 'create') && field.setInitialValue?.(v.default);
}
schema.default = v.default ?? null;
dn.emit('patch', {

View File

@ -13,7 +13,7 @@ import { useTranslation } from 'react-i18next';
import { useBlockContext } from '../../../block-provider';
import { useFormBlockContext } from '../../../block-provider/FormBlockProvider';
import { CollectionFieldOptions_deprecated } from '../../../collection-manager';
import { useDataBlockRequestData, useDataSource } from '../../../data-source';
import { useCollection, useDataSource } from '../../../data-source';
import { useFlag } from '../../../flag-provider/hooks/useFlag';
import { useBaseVariable } from './useBaseVariable';
@ -100,6 +100,7 @@ export const useCurrentFormVariable = ({
const { currentFormCtx, shouldDisplayCurrentForm } = useCurrentFormContext({ form: _form });
const { t } = useTranslation();
const { collectionName } = useFormBlockContext();
const collection = useCollection();
const dataSource = useDataSource();
const currentFormSettings = useBaseVariable({
collectionField,
@ -108,7 +109,7 @@ export const useCurrentFormVariable = ({
maxDepth: 4,
name: '$nForm',
title: t('Current form'),
collectionName: collectionName,
collectionName: collectionName || collection?.name,
noDisabled,
dataSource: dataSource?.key,
returnFields: (fields, option) => {

View File

@ -0,0 +1,40 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { parseDatabaseOptionsFromEnv } from '@nocobase/database';
describe('database helpers', () => {
describe('parseDatabaseOptionsFromEnv()', () => {
it('undefined pool options', async () => {
const options1 = await parseDatabaseOptionsFromEnv();
expect(options1).toMatchObject({
pool: {},
});
});
it('custom pool options', async () => {
process.env.DB_POOL_MAX = '10';
process.env.DB_POOL_MIN = '1';
process.env.DB_POOL_IDLE = '5000';
process.env.DB_POOL_ACQUIRE = '30000';
process.env.DB_POOL_EVICT = '2000';
process.env.DB_POOL_MAX_USES = '0'; // Set to 0 to test default behavior
const options2 = await parseDatabaseOptionsFromEnv();
expect(options2.pool).toMatchObject({
max: 10,
min: 1,
idle: 5000,
acquire: 30000,
evict: 2000,
maxUses: Number.POSITIVE_INFINITY, // Default value
});
});
});
});

View File

@ -15,6 +15,7 @@ import { MysqlDialect } from './dialects/mysql-dialect';
import { SqliteDialect } from './dialects/sqlite-dialect';
import { MariadbDialect } from './dialects/mariadb-dialect';
import { PostgresDialect } from './dialects/postgres-dialect';
import { PoolOptions } from 'sequelize';
function getEnvValue(key, defaultValue?) {
return process.env[key] || defaultValue;
@ -73,6 +74,29 @@ function extractSSLOptionsFromEnv() {
});
}
function getPoolOptions(): PoolOptions {
const options: PoolOptions = {};
if (process.env.DB_POOL_MAX) {
options.max = Number.parseInt(process.env.DB_POOL_MAX, 10);
}
if (process.env.DB_POOL_MIN) {
options.min = Number.parseInt(process.env.DB_POOL_MIN, 10);
}
if (process.env.DB_POOL_IDLE) {
options.idle = Number.parseInt(process.env.DB_POOL_IDLE, 10);
}
if (process.env.DB_POOL_ACQUIRE) {
options.acquire = Number.parseInt(process.env.DB_POOL_ACQUIRE, 10);
}
if (process.env.DB_POOL_EVICT) {
options.evict = Number.parseInt(process.env.DB_POOL_EVICT, 10);
}
if (process.env.DB_POOL_MAX_USES) {
options.maxUses = Number.parseInt(process.env.DB_POOL_MAX_USES, 10) || Number.POSITIVE_INFINITY;
}
return options;
}
export async function parseDatabaseOptionsFromEnv(): Promise<IDatabaseOptions> {
const databaseOptions: IDatabaseOptions = {
logging: process.env.DB_LOGGING == 'on' ? customLogger : false,
@ -87,6 +111,7 @@ export async function parseDatabaseOptionsFromEnv(): Promise<IDatabaseOptions> {
tablePrefix: process.env.DB_TABLE_PREFIX,
schema: process.env.DB_SCHEMA,
underscored: process.env.DB_UNDERSCORED === 'true',
pool: getPoolOptions(),
};
const sslOptions = await extractSSLOptionsFromEnv();

View File

@ -33,7 +33,7 @@ const toDate = (date, options: any = {}) => {
}
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 = {
@ -69,7 +69,6 @@ export default {
const r = parseDate(value, {
timezone: parseDateTimezone(ctx),
});
if (typeof r === 'string') {
return {
[Op.eq]: toDate(r, { ctx }),
@ -77,6 +76,9 @@ export default {
}
if (Array.isArray(r)) {
console.log(11111111, {
[Op.and]: [{ [Op.gte]: toDate(r[0], { ctx }) }, { [Op.lt]: toDate(r[1], { ctx }) }],
});
return {
[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';
utcOffset?: number;
utc?: boolean;
dateOnly?: boolean;
}
export type Str2momentValue = string | string[] | dayjs.Dayjs | dayjs.Dayjs[];
@ -83,10 +84,14 @@ const toMoment = (val: any, options?: Str2momentOptions) => {
return;
}
const offset = options.utcOffset;
const { gmt, picker, utc = true } = options;
const { gmt, picker, utc = true, dateOnly } = options;
if (dayjs(val).isValid()) {
if (dateOnly) {
return dayjs.utc(val, 'YYYY-MM-DD');
}
if (!utc) {
return dayjs(val);
return dayjs.utc(val);
}
if (dayjs.isDayjs(val)) {

View File

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

View File

@ -17,6 +17,8 @@ import {
useRequest,
useResourceActionContext,
useResourceContext,
useFilterFieldProps,
useFilterFieldOptions,
} from '@nocobase/client';
import React from 'react';
import { i18nText } from '../../utils';
@ -212,6 +214,16 @@ export const tableActionColumnSchema: ISchema = {
},
};
export const useFilterActionProps = () => {
const { collection } = useResourceContext();
const options = useFilterFieldOptions(collection.fields);
const service = useResourceActionContext();
return useFilterFieldProps({
options: options,
params: service.state?.params?.[0] || service.params,
service,
});
};
export const schema: ISchema = {
type: 'object',
properties: {
@ -245,6 +257,18 @@ export const schema: ISchema = {
},
},
properties: {
filter: {
'x-component': 'Filter.Action',
'x-use-component-props': useFilterActionProps,
default: {
$and: [{ displayName: { $includes: '' } }, { name: { $includes: '' } }],
},
title: "{{t('Filter')}}",
'x-component-props': {
icon: 'FilterOutlined',
},
'x-align': 'left',
},
delete: {
type: 'void',
title: '{{ t("Delete") }}',

View File

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

View File

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

View File

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

View File

@ -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,20 +397,90 @@ 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() {
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 (
<NodeContext.Provider value={{ ...node, config: values }}>
<VariableKeysContext.Provider value={keys}>
<ActionContextProvider value={{ visible, setVisible: setModalVisible }}>
<Button icon={<CaretRightOutlined />} onClick={onOpen}>
{lang('Test run')}
</Button>
<SchemaComponent
components={{
Alert,
TestFormFieldset,
LogCollapse,
}}
scope={{
useCancelAction,
@ -418,16 +488,7 @@ function TestButton() {
}}
schema={{
type: 'void',
name: 'testButton',
title: '{{t("Test run")}}',
'x-component': 'Action',
'x-component-props': {
icon: 'CaretRightOutlined',
// openSize: 'small',
},
properties: {
modal: {
type: 'void',
name: 'modal',
'x-decorator': 'FormV2',
'x-decorator-props': {
form,
@ -485,25 +546,14 @@ function TestButton() {
},
'x-pattern': 'disabled',
},
footer: {
type: 'void',
'x-component': 'Action.Modal.Footer',
properties: {
cancel: {
type: 'void',
title: '{{t("Close")}}',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCancelAction }}',
},
},
},
},
},
log: {
type: 'string',
'x-component': 'LogCollapse',
},
},
}}
/>
</ActionContextProvider>
</VariableKeysContext.Provider>
</NodeContext.Provider>
);

View File

@ -86,6 +86,7 @@ export const executionSchema = {
appends: ['workflow.id', 'workflow.title'],
pageSize: 20,
sort: ['-createdAt'],
except: ['context', 'output'],
filter: {},
},
},

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;

View File

@ -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 = <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;