From 5ee278557ddaf851a4d9abd8d0c88ab93480d29c Mon Sep 17 00:00:00 2001 From: ChengLei Shao Date: Sun, 24 Mar 2024 09:37:52 +0800 Subject: [PATCH 01/36] chore: set default association reference on delete action to no action (#3722) * chore: tmp commit * chore: build association reference * fix: test --- .../__tests__/associations/references.test.ts | 52 +++++++++++++++++++ .../__tests__/fields/belongs-to-field.test.ts | 30 +++++++++++ packages/core/database/src/database.ts | 2 +- .../{ReferencesMap.ts => references-map.ts} | 51 +++++++++++++----- .../features/referential-integrity-check.ts | 5 ++ .../database/src/fields/belongs-to-field.ts | 16 ++++-- .../src/fields/belongs-to-many-field.ts | 12 ++--- .../database/src/fields/has-many-field.ts | 6 +-- .../core/database/src/fields/has-one-field.ts | 6 +-- 9 files changed, 149 insertions(+), 31 deletions(-) create mode 100644 packages/core/database/src/__tests__/associations/references.test.ts rename packages/core/database/src/features/{ReferencesMap.ts => references-map.ts} (57%) diff --git a/packages/core/database/src/__tests__/associations/references.test.ts b/packages/core/database/src/__tests__/associations/references.test.ts new file mode 100644 index 000000000..7fe4baabc --- /dev/null +++ b/packages/core/database/src/__tests__/associations/references.test.ts @@ -0,0 +1,52 @@ +import { Database } from '../../database'; +import { mockDatabase } from '../index'; +describe('association references', () => { + let db: Database; + + beforeEach(async () => { + db = mockDatabase(); + + await db.clean({ drop: true }); + }); + + afterEach(async () => { + await db.close(); + }); + + it('should add reference with default priority', async () => { + const User = db.collection({ + name: 'users', + fields: [{ type: 'hasOne', name: 'profile' }], + }); + + const Profile = db.collection({ + name: 'profiles', + fields: [{ type: 'belongsTo', name: 'user' }], + }); + + await db.sync(); + + const references = db.referenceMap.getReferences('users'); + + expect(references[0].priority).toBe('default'); + }); + + it('should add reference with user defined priority', async () => { + const User = db.collection({ + name: 'users', + fields: [{ type: 'hasOne', name: 'profile', onDelete: 'CASCADE' }], + }); + + const Profile = db.collection({ + name: 'profiles', + fields: [{ type: 'belongsTo', name: 'user' }], + }); + + await db.sync(); + + const references = db.referenceMap.getReferences('users'); + + expect(references.length).toBe(1); + expect(references[0].priority).toBe('user'); + }); +}); diff --git a/packages/core/database/src/__tests__/fields/belongs-to-field.test.ts b/packages/core/database/src/__tests__/fields/belongs-to-field.test.ts index abb81b887..dedcce013 100644 --- a/packages/core/database/src/__tests__/fields/belongs-to-field.test.ts +++ b/packages/core/database/src/__tests__/fields/belongs-to-field.test.ts @@ -14,6 +14,36 @@ describe('belongs to field', () => { await db.close(); }); + it('should load with no action', async () => { + const User = db.collection({ + name: 'users', + fields: [{ type: 'string', name: 'name', unique: true }], + }); + + const Post = db.collection({ + name: 'posts', + fields: [ + { type: 'string', name: 'title' }, + { type: 'belongsTo', name: 'user', onDelete: 'NO ACTION' }, + ], + }); + + await db.sync(); + + const u1 = await User.repository.create({ values: { name: 'u1' } }); + const p1 = await Post.repository.create({ values: { title: 'p1', user: u1.id } }); + + // delete u1 + await User.repository.destroy({ filterByTk: u1.id }); + + // list posts with user + const post = await Post.repository.findOne({ + appends: ['user'], + }); + + expect(post.user).toBeNull(); + }); + it('should throw error when associated with item that null with target key', async () => { const User = db.collection({ name: 'users', diff --git a/packages/core/database/src/database.ts b/packages/core/database/src/database.ts index e0726dfdd..106bb8828 100644 --- a/packages/core/database/src/database.ts +++ b/packages/core/database/src/database.ts @@ -27,7 +27,7 @@ import { CollectionFactory } from './collection-factory'; import { CollectionGroupManager } from './collection-group-manager'; import { ImporterReader, ImportFileExtension } from './collection-importer'; import DatabaseUtils from './database-utils'; -import ReferencesMap from './features/ReferencesMap'; +import ReferencesMap from './features/references-map'; import { referentialIntegrityCheck } from './features/referential-integrity-check'; import { ArrayFieldRepository } from './field-repository/array-field-repository'; import * as FieldTypes from './fields'; diff --git a/packages/core/database/src/features/ReferencesMap.ts b/packages/core/database/src/features/references-map.ts similarity index 57% rename from packages/core/database/src/features/ReferencesMap.ts rename to packages/core/database/src/features/references-map.ts index ca4ff7364..a78c6b95c 100644 --- a/packages/core/database/src/features/ReferencesMap.ts +++ b/packages/core/database/src/features/references-map.ts @@ -1,30 +1,57 @@ +export type ReferencePriority = 'default' | 'user'; + export interface Reference { sourceCollectionName: string; sourceField: string; targetField: string; targetCollectionName: string; onDelete: string; + priority: ReferencePriority; } +const DEFAULT_ON_DELETE = 'NO ACTION'; + +export function buildReference(options: Partial): Reference { + const { sourceCollectionName, sourceField, targetField, targetCollectionName, onDelete, priority } = options; + + return { + sourceCollectionName, + sourceField, + targetField, + targetCollectionName, + onDelete: (onDelete || DEFAULT_ON_DELETE).toUpperCase(), + priority: assignPriority(priority, onDelete), + }; +} + +function assignPriority(priority: string | undefined, onDelete: string | undefined): ReferencePriority { + if (priority) { + return priority as ReferencePriority; + } + + return onDelete ? 'user' : 'default'; +} + +const PRIORITY_MAP = { + default: 1, + user: 2, +}; + class ReferencesMap { protected map: Map = new Map(); addReference(reference: Reference) { - if (!reference.onDelete) { - reference.onDelete = 'SET NULL'; - } - - reference.onDelete = reference.onDelete.toUpperCase(); - const existReference = this.existReference(reference); if (existReference && existReference.onDelete !== reference.onDelete) { - if (reference.onDelete === 'SET NULL') { - // using existing reference - return; - } else if (existReference.onDelete === 'SET NULL') { + // check two references onDelete priority, using the higher priority, if both are the same, throw error + const existPriority = PRIORITY_MAP[existReference.priority]; + const newPriority = PRIORITY_MAP[reference.priority]; + + if (newPriority > existPriority) { existReference.onDelete = reference.onDelete; - } else { + existReference.priority = reference.priority; + } else if (newPriority === existPriority && newPriority === PRIORITY_MAP['user']) { throw new Error( `On Delete Conflict, exist reference ${JSON.stringify(existReference)}, new reference ${JSON.stringify( reference, @@ -52,7 +79,7 @@ class ReferencesMap { return null; } - const keys = Object.keys(reference).filter((k) => k !== 'onDelete'); + const keys = Object.keys(reference).filter((k) => k !== 'onDelete' && k !== 'priority'); return references.find((ref) => keys.every((key) => ref[key] === reference[key])); } diff --git a/packages/core/database/src/features/referential-integrity-check.ts b/packages/core/database/src/features/referential-integrity-check.ts index b1b1b278e..039806276 100644 --- a/packages/core/database/src/features/referential-integrity-check.ts +++ b/packages/core/database/src/features/referential-integrity-check.ts @@ -21,6 +21,11 @@ export async function referentialIntegrityCheck(options: ReferentialIntegrityChe for (const reference of references) { const { sourceCollectionName, sourceField, targetField, onDelete } = reference; + + if (onDelete === 'NO ACTION') { + continue; + } + const sourceCollection = db.collections.get(sourceCollectionName); const sourceRepository = sourceCollection.repository; diff --git a/packages/core/database/src/fields/belongs-to-field.ts b/packages/core/database/src/fields/belongs-to-field.ts index 38eab8ef9..586607340 100644 --- a/packages/core/database/src/fields/belongs-to-field.ts +++ b/packages/core/database/src/fields/belongs-to-field.ts @@ -1,6 +1,6 @@ import lodash, { omit } from 'lodash'; import { BelongsToOptions as SequelizeBelongsToOptions, Utils } from 'sequelize'; -import { Reference } from '../features/ReferencesMap'; +import { buildReference, Reference, ReferencePriority } from '../features/references-map'; import { checkIdentifier } from '../utils'; import { BaseRelationFieldOptions, RelationField } from './relation-field'; @@ -16,20 +16,26 @@ export class BelongsToField extends RelationField { return target || Utils.pluralize(name); } - static toReference(db, association, onDelete) { + static toReference(db, association, onDelete, priority: ReferencePriority = 'default'): Reference { const targetKey = association.targetKey; - return { + return buildReference({ sourceCollectionName: db.modelCollection.get(association.source).name, sourceField: association.foreignKey, targetField: targetKey, targetCollectionName: db.modelCollection.get(association.target).name, onDelete: onDelete, - }; + priority: priority, + }); } reference(association): Reference { - return BelongsToField.toReference(this.database, association, this.options.onDelete); + return BelongsToField.toReference( + this.database, + association, + this.options.onDelete, + this.options.onDelete ? 'user' : 'default', + ); } checkAssociationKeys() { diff --git a/packages/core/database/src/fields/belongs-to-many-field.ts b/packages/core/database/src/fields/belongs-to-many-field.ts index b69a68e7a..f82d50520 100644 --- a/packages/core/database/src/fields/belongs-to-many-field.ts +++ b/packages/core/database/src/fields/belongs-to-many-field.ts @@ -1,7 +1,7 @@ import { omit } from 'lodash'; import { AssociationScope, BelongsToManyOptions as SequelizeBelongsToManyOptions, Utils } from 'sequelize'; import { Collection } from '../collection'; -import { Reference } from '../features/ReferencesMap'; +import { Reference } from '../features/references-map'; import { checkIdentifier } from '../utils'; import { BelongsToField } from './belongs-to-field'; import { MultipleRelationFieldOptions, RelationField } from './relation-field'; @@ -32,6 +32,8 @@ export class BelongsToManyField extends RelationField { const onDelete = this.options.onDelete || 'CASCADE'; + const priority = this.options.onDelete ? 'user' : 'default'; + const targetAssociation = association.toTarget; if (association.targetKey) { @@ -45,8 +47,8 @@ export class BelongsToManyField extends RelationField { } return [ - BelongsToField.toReference(db, targetAssociation, onDelete), - BelongsToField.toReference(db, sourceAssociation, onDelete), + BelongsToField.toReference(db, targetAssociation, onDelete, priority), + BelongsToField.toReference(db, sourceAssociation, onDelete, priority), ]; } @@ -149,10 +151,6 @@ export class BelongsToManyField extends RelationField { Object.defineProperty(Through.model, 'isThrough', { value: true }); } - if (!this.options.onDelete) { - this.options.onDelete = 'CASCADE'; - } - const belongsToManyOptions = { constraints: false, ...omit(this.options, ['name', 'type', 'target']), diff --git a/packages/core/database/src/fields/has-many-field.ts b/packages/core/database/src/fields/has-many-field.ts index f266af9f6..28a3f038b 100644 --- a/packages/core/database/src/fields/has-many-field.ts +++ b/packages/core/database/src/fields/has-many-field.ts @@ -8,7 +8,7 @@ import { Utils, } from 'sequelize'; import { Collection } from '../collection'; -import { Reference } from '../features/ReferencesMap'; +import { buildReference, Reference } from '../features/references-map'; import { checkIdentifier } from '../utils'; import { MultipleRelationFieldOptions, RelationField } from './relation-field'; @@ -89,13 +89,13 @@ export class HasManyField extends RelationField { reference(association): Reference { const sourceKey = association.sourceKey; - return { + return buildReference({ sourceCollectionName: this.database.modelCollection.get(association.target).name, sourceField: association.foreignKey, targetField: sourceKey, targetCollectionName: this.database.modelCollection.get(association.source).name, onDelete: this.options.onDelete, - }; + }); } checkAssociationKeys() { diff --git a/packages/core/database/src/fields/has-one-field.ts b/packages/core/database/src/fields/has-one-field.ts index b6ef32c1f..f2673daca 100644 --- a/packages/core/database/src/fields/has-one-field.ts +++ b/packages/core/database/src/fields/has-one-field.ts @@ -8,7 +8,7 @@ import { Utils, } from 'sequelize'; import { Collection } from '../collection'; -import { Reference } from '../features/ReferencesMap'; +import { buildReference, Reference } from '../features/references-map'; import { checkIdentifier } from '../utils'; import { BaseRelationFieldOptions, RelationField } from './relation-field'; @@ -98,13 +98,13 @@ export class HasOneField extends RelationField { reference(association): Reference { const sourceKey = association.sourceKey; - return { + return buildReference({ sourceCollectionName: this.database.modelCollection.get(association.target).name, sourceField: association.foreignKey, targetField: sourceKey, targetCollectionName: this.database.modelCollection.get(association.source).name, onDelete: this.options.onDelete, - }; + }); } checkAssociationKeys() { From 921696f40b027fae43a175cd034b1502fdd57253 Mon Sep 17 00:00:00 2001 From: chenos Date: Sun, 24 Mar 2024 12:51:41 +0800 Subject: [PATCH 02/36] fix: useExpressionScope (#3805) --- .../client/src/application/hoc/withDynamicSchemaProps.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core/client/src/application/hoc/withDynamicSchemaProps.tsx b/packages/core/client/src/application/hoc/withDynamicSchemaProps.tsx index fc3d918ad..1fabb2dde 100644 --- a/packages/core/client/src/application/hoc/withDynamicSchemaProps.tsx +++ b/packages/core/client/src/application/hoc/withDynamicSchemaProps.tsx @@ -1,6 +1,7 @@ +import { useExpressionScope } from '@formily/react'; import { merge, omit } from 'lodash'; import React, { ComponentType, useMemo } from 'react'; -import { useDesignable, useSchemaComponentContext } from '../../schema-component'; +import { useDesignable } from '../../schema-component'; const useDefaultSchemaProps = () => undefined; @@ -12,7 +13,7 @@ export function withDynamicSchemaProps(Component: ComponentType, opt const displayName = options.displayName || Component.displayName || Component.name; const ComponentWithProps: ComponentType = (props) => { const { dn, findComponent } = useDesignable(); - const { scope } = useSchemaComponentContext(); + const scope = useExpressionScope(); const useComponentPropsStr = useMemo(() => { const xComponent = dn.getSchemaAttribute('x-component'); const xDecorator = dn.getSchemaAttribute('x-decorator'); From e410dece222f1e64cbf8b9b60769022de1977b3f Mon Sep 17 00:00:00 2001 From: ChengLei Shao Date: Sun, 24 Mar 2024 19:08:53 +0800 Subject: [PATCH 03/36] fix: create attachments middleware (#3794) --- .../@nocobase/plugin-file-manager/src/server/actions/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/index.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/index.ts index 05a6e3175..f6f6fb137 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/index.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/index.ts @@ -2,7 +2,7 @@ import actions from '@nocobase/actions'; import { createMiddleware, destroyMiddleware } from './attachments'; export default function ({ app }) { - app.resourcer.use(createMiddleware); + app.resourcer.use(createMiddleware, { tag: 'createMiddleware', after: 'auth' }); app.resourcer.registerActionHandler('upload', actions.create); app.resourcer.use(destroyMiddleware); From 88d1bdbefbc70553ad4dd0367466280246c2bb2e Mon Sep 17 00:00:00 2001 From: katherinehhh Date: Sun, 24 Mar 2024 19:44:39 +0800 Subject: [PATCH 04/36] fix: expand action and add new action should support drag & sort (#3808) --- .../antd/expand-action/Expand.Action.tsx | 13 ++- .../components/CreateRecordAction.tsx | 101 +++++++++--------- 2 files changed, 63 insertions(+), 51 deletions(-) diff --git a/packages/core/client/src/schema-component/antd/expand-action/Expand.Action.tsx b/packages/core/client/src/schema-component/antd/expand-action/Expand.Action.tsx index 19a1a3aa5..2744ba40a 100644 --- a/packages/core/client/src/schema-component/antd/expand-action/Expand.Action.tsx +++ b/packages/core/client/src/schema-component/antd/expand-action/Expand.Action.tsx @@ -1,7 +1,8 @@ import { css } from '@emotion/css'; import { useFieldSchema } from '@formily/react'; import { Button } from 'antd'; -import React from 'react'; +import React, { forwardRef, createRef } from 'react'; +import { composeRef } from 'rc-util/lib/ref'; import { useCompile } from '../../hooks'; import { useTableBlockContext, useTableSelectorContext } from '../../../block-provider'; import { Icon } from '../../../icon'; @@ -46,7 +47,7 @@ const actionDesignerCss = css` } `; -export const ExpandAction = (props) => { +const InternalExpandAction = (props, ref) => { const schema = useFieldSchema(); const ctxSelector = useTableSelectorContext(); const ctxBlock = useTableBlockContext(); @@ -54,8 +55,11 @@ export const ExpandAction = (props) => { const ctx = isTableSelector ? ctxSelector : ctxBlock; const { titleExpand, titleCollapse, iconExpand, iconCollapse } = schema['x-component-props'] || {}; const compile = useCompile(); + const internalRef = createRef(); + const buttonRef = composeRef(ref, internalRef); return ( -
+ //@ts-ignore +
}> {ctx?.params['tree'] && (
); }; + +export const ExpandAction = forwardRef(InternalExpandAction); diff --git a/packages/core/client/src/schema-initializer/components/CreateRecordAction.tsx b/packages/core/client/src/schema-initializer/components/CreateRecordAction.tsx index 70387ce7c..097945644 100644 --- a/packages/core/client/src/schema-initializer/components/CreateRecordAction.tsx +++ b/packages/core/client/src/schema-initializer/components/CreateRecordAction.tsx @@ -2,7 +2,8 @@ import { DownOutlined } from '@ant-design/icons'; import { css } from '@emotion/css'; import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react'; import { Button, Dropdown, MenuProps } from 'antd'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState, forwardRef, createRef } from 'react'; +import { composeRef } from 'rc-util/lib/ref'; import { useDesignable } from '../../'; import { useACLRolesCheck, useRecordPkValue } from '../../acl/ACLProvider'; import { @@ -96,55 +97,55 @@ function useAclCheckFn() { return actionAclCheck; } -export const CreateRecordAction = observer( - (props: any) => { - const [visible, setVisible] = useState(false); - const collection = useCollection_deprecated(); - const fieldSchema = useFieldSchema(); - const field: any = useField(); - const [currentCollection, setCurrentCollection] = useState(collection.name); - const [currentCollectionDataSource, setCurrentCollectionDataSource] = useState(collection.dataSource); - const linkageRules: any[] = fieldSchema?.['x-linkage-rules'] || []; - const values = useRecord(); - const ctx = useActionContext(); - const variables = useVariables(); - const localVariables = useLocalVariables({ currentForm: { values } as any }); - useEffect(() => { - field.stateOfLinkageRules = {}; - linkageRules - .filter((k) => !k.disabled) - .forEach((v) => { - v.actions?.forEach((h) => { - linkageAction({ - operator: h.operator, - field, - condition: v.condition, - variables, - localVariables, - }); +const InternalCreateRecordAction = (props: any, ref) => { + const [visible, setVisible] = useState(false); + const collection = useCollection_deprecated(); + const fieldSchema = useFieldSchema(); + const field: any = useField(); + const [currentCollection, setCurrentCollection] = useState(collection.name); + const [currentCollectionDataSource, setCurrentCollectionDataSource] = useState(collection.dataSource); + const linkageRules: any[] = fieldSchema?.['x-linkage-rules'] || []; + const values = useRecord(); + const ctx = useActionContext(); + const variables = useVariables(); + const localVariables = useLocalVariables({ currentForm: { values } as any }); + useEffect(() => { + field.stateOfLinkageRules = {}; + linkageRules + .filter((k) => !k.disabled) + .forEach((v) => { + v.actions?.forEach((h) => { + linkageAction({ + operator: h.operator, + field, + condition: v.condition, + variables, + localVariables, }); }); - }, [field, linkageRules, localVariables, variables]); - return ( -
- - { - setVisible(true); - setCurrentCollection(collectionData.name); - setCurrentCollectionDataSource(collectionData.dataSource); - }} - /> - - - - -
- ); - }, - { displayName: 'CreateRecordAction' }, -); + }); + }, [field, linkageRules, localVariables, variables]); + const internalRef = createRef(); + const buttonRef = composeRef(ref, internalRef); + return ( + //@ts-ignore +
}> + + { + setVisible(true); + setCurrentCollection(collectionData.name); + setCurrentCollectionDataSource(collectionData.dataSource); + }} + /> + + + + +
+ ); +}; function getLinkageCollection(str, form, field) { const variablesCtx = { $form: form.values, $iteration: form.values }; @@ -283,6 +284,7 @@ function FinallyButton({ designable: boolean; }) { const { getCollection } = useCollectionManager_deprecated(); + if (inheritsCollections?.length > 0) { if (!linkageFromForm) { return allowAddToCurrent === undefined || allowAddToCurrent ? ( @@ -360,6 +362,7 @@ function FinallyButton({ onClick?.(collection); }} style={{ + ...props?.style, display: !designable && field?.data?.hidden && 'none', opacity: designable && field?.data?.hidden && 0.1, }} @@ -368,3 +371,5 @@ function FinallyButton({ ); } + +export const CreateRecordAction = forwardRef(InternalCreateRecordAction); From aa6e753b3520acd208d8d718362d5ac7aaa773bb Mon Sep 17 00:00:00 2001 From: chenos Date: Mon, 25 Mar 2024 09:46:45 +0800 Subject: [PATCH 05/36] fix: update the locale file requires a reboot --- packages/core/server/src/locale/locale.ts | 3 +++ packages/core/server/src/locale/resource.ts | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/server/src/locale/locale.ts b/packages/core/server/src/locale/locale.ts index d605ba5a4..0cf80f285 100644 --- a/packages/core/server/src/locale/locale.ts +++ b/packages/core/server/src/locale/locale.ts @@ -67,6 +67,9 @@ export class Locale { async getCacheResources(lang: string) { this.resourceCached.set(lang, true); + if (process.env.APP_ENV !== 'production') { + await this.cache.reset(); + } return await this.wrapCache(`resources:${lang}`, () => this.getResources(lang)); } diff --git a/packages/core/server/src/locale/resource.ts b/packages/core/server/src/locale/resource.ts index f9d7a56f7..43351be0a 100644 --- a/packages/core/server/src/locale/resource.ts +++ b/packages/core/server/src/locale/resource.ts @@ -25,7 +25,10 @@ export const getResource = (packageName: string, lang: string, isPlugin = true) for (const prefix of prefixes) { try { const file = `${packageName}/${prefix}/locale/${lang}`; - require.resolve(file); + const f = require.resolve(file); + if (process.env.APP_ENV !== 'production') { + delete require.cache[f]; + } const resource = requireModule(file); resources.push(resource); } catch (error) { From 0dc0d329f80c268672bd80fc6cb0190c3cef964d Mon Sep 17 00:00:00 2001 From: chenos Date: Mon, 25 Mar 2024 09:48:08 +0800 Subject: [PATCH 06/36] fix: description --- .../EditCollectionAction.tsx | 35 +++++++++++-------- .../src/locale/zh-CN.json | 3 +- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/EditCollectionAction.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/EditCollectionAction.tsx index 92e1c9745..7b5e44671 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/EditCollectionAction.tsx +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/EditCollectionAction.tsx @@ -1,26 +1,27 @@ import { ArrayTable } from '@formily/antd-v5'; import { ISchema, useForm } from '@formily/react'; import { uid } from '@formily/shared'; +import { + ActionContextProvider, + IField, + RecordProvider, + SchemaComponent, + tval, + useAPIClient, + useActionContext, + useCancelAction, + useCollectionManager_deprecated, + useCompile, + useRecord, + useRequest, + useResourceActionContext, + useResourceContext, +} from '@nocobase/client'; import cloneDeep from 'lodash/cloneDeep'; import omit from 'lodash/omit'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; -import { - useAPIClient, - useRequest, - IField, - RecordProvider, - useCollectionManager_deprecated, - useRecord, - ActionContextProvider, - SchemaComponent, - useActionContext, - useCompile, - useResourceActionContext, - useResourceContext, - useCancelAction, -} from '@nocobase/client'; import { NAMESPACE } from '../../locale'; const getSchema = (schema: IField, record: any, compile, getContainer): ISchema => { @@ -60,6 +61,10 @@ const getSchema = (schema: IField, record: any, compile, getContainer): ISchema filterTargetKey: { title: `{{ t("Filter target key",{ ns: "${NAMESPACE}" }) }}`, type: 'single', + description: tval( + 'Filter data based on the specific field, with the requirement that the field value must be unique.', + { ns: NAMESPACE }, + ), 'x-decorator': 'FormItem', 'x-component': 'Select', enum: '{{filterTargetKeyOptions}}', diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-data-source-manager/src/locale/zh-CN.json index f94de0ba6..d0772ad43 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/locale/zh-CN.json +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/locale/zh-CN.json @@ -30,5 +30,6 @@ "Reloading": "加载中", "Data source synchronization in progress": "数据源同步中", "Data source synchronization successful": "数据源同步成功", - "Filter target key":"筛选目标键" + "Filter target key":"筛选目标键", + "Filter data based on the specific field, with the requirement that the field value must be unique.": "根据特定的字段筛选数据,字段值必须具备唯一性。" } From 66229cc92bf87f87a2d84620540fd5e04d9e59a6 Mon Sep 17 00:00:00 2001 From: katherinehhh Date: Mon, 25 Mar 2024 09:51:57 +0800 Subject: [PATCH 07/36] fix: plugin manager keywords (#3809) --- packages/core/client/src/pm/PluginManager.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/client/src/pm/PluginManager.tsx b/packages/core/client/src/pm/PluginManager.tsx index 0250e3f91..e097a62c8 100644 --- a/packages/core/client/src/pm/PluginManager.tsx +++ b/packages/core/client/src/pm/PluginManager.tsx @@ -92,7 +92,7 @@ const LocalPlugins = () => { const keyWordsfilterList = useMemo(() => { const list = keyWordlists.map((i) => { if (i === 'Others') { - const result = data?.data.filter((v) => !v.keywords); + const result = data?.data.filter((v) => !v.keywords || !v.keywords.every((k) => keyWordlists.includes(k))); return { key: i, list: result, From 570e5c1354b387433f6e2291edf5feb0bc13b00d Mon Sep 17 00:00:00 2001 From: jack zhang <1098626505@qq.com> Date: Mon, 25 Mar 2024 11:28:54 +0800 Subject: [PATCH 08/36] fix: reload when data source click refresh (#3804) --- .../src/client/component/DatabaseConnectionManager.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/DatabaseConnectionManager.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/DatabaseConnectionManager.tsx index 784459299..f93ae951a 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/DatabaseConnectionManager.tsx +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/DatabaseConnectionManager.tsx @@ -60,6 +60,13 @@ export const DatabaseConnectionManagerPane = () => { const service = useResourceActionContext(); return { async onClick() { + const needReloadDataSources = service?.data?.data.filter((item) => item.status !== 'loaded'); + if (needReloadDataSources?.length) { + const dataSources = dm.getDataSources(); + const needLoadDataSourceKeys = needReloadDataSources.map((item) => item.key); + const needLoadDataSourcesInstance = dataSources.filter((item) => needLoadDataSourceKeys.includes(item.key)); + await Promise.all(needLoadDataSourcesInstance.map((item) => item.reload())); + } service?.refresh?.(); }, }; From 00cf5fa02caf85ac9286b63b9fb5f95e26003f42 Mon Sep 17 00:00:00 2001 From: jack zhang <1098626505@qq.com> Date: Mon, 25 Mar 2024 11:30:38 +0800 Subject: [PATCH 09/36] fix: refresh collection name when update (#3797) --- .../component/CollectionsManager/EditCollectionAction.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/EditCollectionAction.tsx b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/EditCollectionAction.tsx index 7b5e44671..87a07cff5 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/EditCollectionAction.tsx +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/client/component/CollectionsManager/EditCollectionAction.tsx @@ -9,13 +9,14 @@ import { tval, useAPIClient, useActionContext, - useCancelAction, useCollectionManager_deprecated, useCompile, useRecord, useRequest, useResourceActionContext, useResourceContext, + useCancelAction, + useDataSourceManager, } from '@nocobase/client'; import cloneDeep from 'lodash/cloneDeep'; import omit from 'lodash/omit'; @@ -130,6 +131,7 @@ export const useUpdateCollectionActionAndRefreshCM = (options) => { const { resource, targetKey } = useResourceContext(); const { [targetKey]: filterByTk } = useRecord(); const api = useAPIClient(); + const dm = useDataSourceManager(); return { async run() { await form.submit(); @@ -140,6 +142,7 @@ export const useUpdateCollectionActionAndRefreshCM = (options) => { ...omit(form.values, ['fields', 'autoGenId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy', 'sortable']), }, }); + await dm.getDataSource(name).reload(); ctx.setVisible(false); await form.reset(); refresh(); From e9e116e476283e51204f0a0e1e5f240214dad1c8 Mon Sep 17 00:00:00 2001 From: Junyi Date: Mon, 25 Mar 2024 11:34:14 +0800 Subject: [PATCH 10/36] fix(plugin-workflow): remove string template in condition calculation (#3688) --- .../@nocobase/plugin-workflow/src/client/nodes/condition.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4100f86d7..97616a026 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/condition.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/nodes/condition.tsx @@ -355,7 +355,7 @@ export default class extends Instruction { 'x-component-props': { options: [ ['basic', { label: `{{t("Basic", { ns: "${NAMESPACE}" })}}` }], - ...Array.from(evaluators.getEntities()), + ...Array.from(evaluators.getEntities()).filter(([key]) => ['math.js', 'formula.js'].includes(key)), ].reduce((result: RadioWithTooltipOption[], [value, options]: any) => result.concat({ value, ...options }), []), }, required: true, From 5e22103e331909a13221823543a5e4055c6372cd Mon Sep 17 00:00:00 2001 From: katherinehhh Date: Mon, 25 Mar 2024 11:39:52 +0800 Subject: [PATCH 11/36] chore: file collection template preset fields should be disabled (#3810) --- .../collection-manager/templates/components/PresetFields.tsx | 4 ++++ .../plugin-file-manager/src/client/templates/file.ts | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/client/src/collection-manager/templates/components/PresetFields.tsx b/packages/core/client/src/collection-manager/templates/components/PresetFields.tsx index 81d6180d3..39b682323 100644 --- a/packages/core/client/src/collection-manager/templates/components/PresetFields.tsx +++ b/packages/core/client/src/collection-manager/templates/components/PresetFields.tsx @@ -193,6 +193,10 @@ export const PresetFields = observer( rowSelection={{ type: 'checkbox', selectedRowKeys, + getCheckboxProps: (record) => ({ + disabled: form.values.template === 'file', // Column configuration not to be checked + name: record.name, + }), onChange: (_, selectedRows) => { const fields = getDefaultCollectionFields(selectedRows, form.values); const config = { diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/templates/file.ts b/packages/plugins/@nocobase/plugin-file-manager/src/client/templates/file.ts index a028c17f8..efc374a05 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/templates/file.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/templates/file.ts @@ -150,7 +150,6 @@ export class FileCollectionTemplate extends CollectionTemplate { title: `{{t("File storage", { ns: "${NAMESPACE}" })}}`, type: 'hasOne', name: 'storage', - required: true, 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-reactions': ['{{useAsyncDataSource(loadStorages)}}'], From f2828cd8b0083af647d50514324f84b2c414687c Mon Sep 17 00:00:00 2001 From: Zeke Zhang <958414905@qq.com> Date: Mon, 25 Mar 2024 12:29:44 +0800 Subject: [PATCH 12/36] chore: add options for matching and ignoring test files in e2e and p-test commands (#3811) --- packages/core/cli/src/commands/e2e.js | 6 ++++++ packages/core/cli/src/commands/p-test.js | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/core/cli/src/commands/e2e.js b/packages/core/cli/src/commands/e2e.js index 361687b9b..8d70f553a 100644 --- a/packages/core/cli/src/commands/e2e.js +++ b/packages/core/cli/src/commands/e2e.js @@ -243,6 +243,12 @@ module.exports = (cli) => { .option('--stop-on-error') .option('--build') .option('--concurrency [concurrency]', '', os.cpus().length) + .option( + '--match [match]', + 'Only the files matching one of these patterns are executed as test files. Matching is performed against the absolute file path. Strings are treated as glob patterns.', + 'packages/**/__e2e__/**/*.test.ts', + ) + .option('--ignore [ignore]', 'Skip tests that match the pattern. Strings are treated as glob patterns.', undefined) .action(async (options) => { process.env.__E2E__ = true; if (options.build) { diff --git a/packages/core/cli/src/commands/p-test.js b/packages/core/cli/src/commands/p-test.js index 05bcaa06e..fb5b8a828 100644 --- a/packages/core/cli/src/commands/p-test.js +++ b/packages/core/cli/src/commands/p-test.js @@ -60,7 +60,8 @@ exports.pTest = async (options) => { fs.mkdirSync(dir, { recursive: true }); } - const files = glob.sync('packages/**/__e2e__/**/*.test.ts', { + const files = glob.sync(options.match, { + ignore: options.ignore, root: process.cwd(), }); From d691e4c7e6485ae16134f035e7689f37f95c01ef Mon Sep 17 00:00:00 2001 From: Junyi Date: Mon, 25 Mar 2024 14:46:22 +0800 Subject: [PATCH 13/36] feat(plugin-workflow): support multiple data source in workflow (#3739) * feat(plugin-workflow): support multiple data source in workflow * fix(plugin-workflow): fix test cases * test(plugin-workflow-sql): debug test case * fix(plugin-workflow): fix collection trigger creation without config * test(plugin-workflow-sql): debug test case * fix: workflow e2e test * chore(ci): disable console intercept in vitest * chore(ci): disable console intercept in vitest * chore(ci): disable console intercept in vitest * chore(ci): disable console intercept in vitest * test(plugin-workflow-sql): debug test case * test: approval e2e * fix: remove pro-plugins from packages * refactor(plugin-workflow): support pass collection from props to CollectionBlockInitializer * test(plugin-workflow): add test case * fix(plugin-workflow): disable modification of executed workflow * fix: e2ePageObjectModel * fix: load data source when data source load failed (#3793) * chore: console.log * fix(subTable): fix sorting rule setting (#3795) * fix: through collection support search (#3800) * fix(client): visible -> useVisible * fix(client): fix action designer error occured in custom form (#3801) * fix(client): fix action designer error occured in custom form * fix(client): fix from the source * chore(module): remove submodule * fix(plugin-workflow): fix client cycling import * fix(plugin-workflow): fix collection event name * fix(plugin-workflow): fix undefined ref --------- Co-authored-by: hongboji Co-authored-by: ChengLei Shao Co-authored-by: Zeke Zhang <958414905@qq.com> Co-authored-by: katherinehhh Co-authored-by: chenos --- packages/core/cli/src/commands/test.js | 1 + .../antd/action/Action.Designer.tsx | 84 +-- .../appends-tree-select/AppendsTreeSelect.tsx | 29 +- .../core/data-source-manager/src/utils.ts | 7 + packages/core/test/package.json | 2 +- packages/core/utils/src/json-templates.ts | 13 +- .../src/client/ActionTrigger.tsx | 16 +- .../src/client/__e2e__/configuration.test.ts | 22 +- .../src/server/ActionTrigger.ts | 19 +- .../src/server/__tests__/trigger.test.ts | 59 +- .../src/client/AggregateInstruction.tsx | 26 +- .../client/__e2e__/DataOfCollection.test.ts | 60 +- .../src/server/AggregateInstruction.ts | 10 +- .../src/server/__tests__/instruction.test.ts | 30 + .../src/client/WorkflowTodo.tsx | 2 +- .../client/__e2e__/createRecordForm.test.ts | 65 ++ .../client/__e2e__/updateRecordForm.test.ts | 53 ++ .../src/client/__e2e__/workflowTodo.test.ts | 9 + .../src/client/instruction/SchemaConfig.tsx | 23 +- .../src/client/instruction/forms/custom.tsx | 45 +- .../src/client/instruction/index.tsx | 2 +- .../src/server/__tests__/data-source.test.ts | 223 +++++++ .../{instruction.test.ts => form.test.ts} | 510 ---------------- .../src/server/__tests__/mode.test.ts | 561 ++++++++++++++++++ .../src/server/forms/create.ts | 13 +- .../src/server/forms/update.ts | 13 +- ...0240314163421-change-schema-data-source.ts | 76 +++ .../src/client/SQLInstruction.tsx | 15 + .../plugin-workflow-sql/src/locale/zh-CN.json | 1 + .../src/server/SQLInstruction.ts | 13 +- .../src/server/__tests__/instruction.test.ts | 51 ++ .../src/e2e/e2ePageObjectModel.ts | 140 +++-- .../plugin-workflow-test/src/e2e/e2eUtils.ts | 166 +++++- .../plugin-workflow-test/src/server/index.ts | 31 +- .../src/client/ExecutionCanvas.tsx | 3 +- .../src/client/ExecutionContextProvider.tsx | 33 ++ .../src/client/ExecutionLink.tsx | 3 +- .../{FlowContext.ts => FlowContext.tsx} | 0 .../src/client/WorkflowCanvas.tsx | 3 +- .../src/client/WorkflowLink.tsx | 3 +- .../configuration.test.ts | 23 +- .../createRecordNode/CreateRecord.test.ts | 36 +- .../deleteRecordNode/DeleteRecord.test.ts | 6 +- .../queryRecordNode/QueryRecord.test.ts | 27 +- .../updateRecordNode/updateInABatch.test.ts | 42 +- .../updateRecordNode/updateOneByOne.test.ts | 42 +- .../components/CollectionBlockInitializer.tsx | 31 +- .../client/components/CollectionFieldset.tsx | 12 +- .../components}/DetailsBlockProvider.tsx | 30 +- .../src/client/components/FieldsSelect.tsx | 5 +- .../src/client/components/SimpleDesigner.tsx | 27 + .../src/client/components/ValueBlock.tsx | 5 +- .../src/client/components/index.ts | 2 + .../plugin-workflow/src/client/constant.ts | 2 - .../plugin-workflow/src/client/hooks/index.ts | 3 + .../src/client/hooks/useWorkflowExecuted.ts | 11 + .../plugin-workflow/src/client/index.tsx | 28 +- .../src/client/nodes/create.tsx | 19 +- .../src/client/nodes/destroy.tsx | 24 +- .../src/client/nodes/query.tsx | 19 +- .../src/client/nodes/update.tsx | 33 +- .../src/client/schemas/collection.ts | 12 +- .../src/client/schemas/executions.tsx | 10 +- .../src/client/triggers/collection.tsx | 10 +- .../src/client/triggers/schedule/OnField.tsx | 3 +- .../triggers/schedule/ScheduleConfig.tsx | 7 + .../src/client/triggers/schedule/index.tsx | 2 +- .../plugin-workflow/src/client/utils.ts | 8 + .../plugin-workflow/src/client/variable.tsx | 30 +- .../__tests__/instructions/create.test.ts | 31 + .../__tests__/instructions/destroy.test.ts | 33 ++ .../__tests__/instructions/query.test.ts | 31 + .../__tests__/instructions/update.test.ts | 51 +- .../__tests__/triggers/collection.test.ts | 114 +++- .../server/instructions/CreateInstruction.ts | 7 +- .../server/instructions/DestroyInstruction.ts | 10 +- .../server/instructions/QueryInstruction.ts | 9 +- .../server/instructions/UpdateInstruction.ts | 10 +- .../src/server/triggers/CollectionTrigger.ts | 50 +- .../DateFieldScheduleTrigger.ts | 20 +- 80 files changed, 2423 insertions(+), 887 deletions(-) create mode 100644 packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/data-source.test.ts rename packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/{instruction.test.ts => form.test.ts} (56%) create mode 100644 packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/mode.test.ts create mode 100644 packages/plugins/@nocobase/plugin-workflow-manual/src/server/migrations/20240314163421-change-schema-data-source.ts create mode 100644 packages/plugins/@nocobase/plugin-workflow/src/client/ExecutionContextProvider.tsx rename packages/plugins/@nocobase/plugin-workflow/src/client/{FlowContext.ts => FlowContext.tsx} (100%) rename packages/plugins/@nocobase/{plugin-workflow-manual/src/client/instruction => plugin-workflow/src/client/components}/DetailsBlockProvider.tsx (71%) create mode 100644 packages/plugins/@nocobase/plugin-workflow/src/client/components/SimpleDesigner.tsx delete mode 100644 packages/plugins/@nocobase/plugin-workflow/src/client/constant.ts create mode 100644 packages/plugins/@nocobase/plugin-workflow/src/client/hooks/index.ts create mode 100644 packages/plugins/@nocobase/plugin-workflow/src/client/hooks/useWorkflowExecuted.ts diff --git a/packages/core/cli/src/commands/test.js b/packages/core/cli/src/commands/test.js index e592ee5b7..071e45d39 100644 --- a/packages/core/cli/src/commands/test.js +++ b/packages/core/cli/src/commands/test.js @@ -19,6 +19,7 @@ function addTestCommand(name, cli) { .arguments('[paths...]') .allowUnknownOption() .action(async (paths, opts) => { + process.argv.push('--disable-console-intercept'); if (name === 'test:server') { process.env.TEST_ENV = 'server-side'; } else if (name === 'test:client') { diff --git a/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx b/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx index 0f26e5bef..f73a0592b 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.Designer.tsx @@ -11,7 +11,11 @@ import { usePlugin } from '../../../application/hooks'; import { SchemaSettingOptions, SchemaSettings } from '../../../application/schema-settings'; import { useSchemaToolbar } from '../../../application/schema-toolbar'; import { useFormBlockContext } from '../../../block-provider'; -import { useCollectionManager_deprecated, useCollection_deprecated } from '../../../collection-manager'; +import { + joinCollectionName, + useCollectionManager_deprecated, + useCollection_deprecated, +} from '../../../collection-manager'; import { FlagProvider } from '../../../flag-provider'; import { SaveMode } from '../../../modules/actions/submit/createSubmitActionSettings'; import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items'; @@ -28,6 +32,7 @@ import { import { DefaultValueProvider } from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue'; import { useLinkageAction } from './hooks'; import { requestSettingsSchema } from './utils'; +import { DataSourceProvider, useDataSourceKey } from '../../../data-source'; const MenuGroup = (props) => { return props.children; @@ -327,7 +332,8 @@ function WorkflowSelect({ actionType, direct = false, ...props }) { const { setValuesIn } = useForm(); const baseCollection = useCollection_deprecated(); const { getCollection } = useCollectionManager_deprecated(); - const [workflowCollection, setWorkflowCollection] = useState(baseCollection.name); + const dataSourceKey = useDataSourceKey(); + const [workflowCollection, setWorkflowCollection] = useState(joinCollectionName(dataSourceKey, baseCollection.name)); const compile = useCompile(); const workflowPlugin = usePlugin('workflow') as any; @@ -351,11 +357,11 @@ function WorkflowSelect({ actionType, direct = false, ...props }) { const path = paths[i]; const associationField = collection.fields.find((f) => f.name === path); if (associationField) { - collection = getCollection(associationField.target); + collection = getCollection(associationField.target, dataSourceKey); } } } - setWorkflowCollection(collection.name); + setWorkflowCollection(joinCollectionName(dataSourceKey, collection.name)); setValuesIn(`group[${index}].workflowKey`, null); }); }); @@ -375,38 +381,40 @@ function WorkflowSelect({ actionType, direct = false, ...props }) { ); return ( - + { - const typeOption = workflowPlugin.getTriggersOptions().find((item) => item.value === data.type); - return typeOption ? ( - - {label} - {compile(typeOption.label)} - - ) : ( - label - ); - }} - {...props} - /> + }} + optionFilter={optionFilter} + optionRender={({ label, data }) => { + const typeOption = workflowPlugin.getTriggersOptions().find((item) => item.value === data.type); + return typeOption ? ( + + {label} + {compile(typeOption.label)} + + ) : ( + label + ); + }} + {...props} + /> + ); } @@ -414,7 +422,7 @@ export function WorkflowConfig() { const { dn } = useDesignable(); const { t } = useTranslation(); const fieldSchema = useFieldSchema(); - const { name: collection } = useCollection_deprecated(); + const collection = useCollection_deprecated(); // TODO(refactor): should refactor for getting certain action type, better from 'x-action'. const formBlock = useFormBlockContext(); const actionType = formBlock?.type || fieldSchema['x-action']; @@ -483,7 +491,9 @@ export function WorkflowConfig() { 'x-component-props': { placeholder: t('Select context', { ns: 'workflow' }), popupMatchSelectWidth: false, - collection, + collection: `${ + collection.dataSource && collection.dataSource !== 'main' ? `${collection.dataSource}:` : '' + }${collection.name}`, filter: '{{ fieldFilter }}', rootOption: { label: t('Full form data', { ns: 'workflow' }), diff --git a/packages/core/client/src/schema-component/antd/appends-tree-select/AppendsTreeSelect.tsx b/packages/core/client/src/schema-component/antd/appends-tree-select/AppendsTreeSelect.tsx index 812cf48de..4cef09c37 100644 --- a/packages/core/client/src/schema-component/antd/appends-tree-select/AppendsTreeSelect.tsx +++ b/packages/core/client/src/schema-component/antd/appends-tree-select/AppendsTreeSelect.tsx @@ -3,7 +3,12 @@ import { Tag, TreeSelect } from 'antd'; import type { DefaultOptionType, TreeSelectProps } from 'rc-tree-select/es/TreeSelect'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { CollectionFieldOptions_deprecated, useCollectionManager_deprecated, useCompile } from '../../..'; +import { + CollectionFieldOptions_deprecated, + parseCollectionName, + useCollectionManager_deprecated, + useCompile, +} from '../../..'; export type AppendsTreeSelectProps = { value: string[] | string; @@ -27,7 +32,7 @@ function usePropsCollection({ collection }) { type CallScope = { compile?(value: string): string; - getCollectionFields?(name: any): CollectionFieldOptions_deprecated[]; + getCollectionFields?(name: any, dataSource?: string): CollectionFieldOptions_deprecated[]; filter(field): boolean; }; @@ -52,7 +57,8 @@ function trueFilter(field) { } function getCollectionFieldOptions(this: CallScope, collection, parentNode?): TreeOptionType[] { - const fields = this.getCollectionFields(collection).filter(isAssociation); + const [dataSourceName, collectionName] = parseCollectionName(collection); + const fields = this.getCollectionFields(collectionName, dataSourceName).filter(isAssociation); const boundLoadChildren = loadChildren.bind(this); return fields.filter(this.filter).map((field) => { const key = parentNode ? `${parentNode.value ? `${parentNode.value}.` : ''}${field.name}` : field.name; @@ -84,11 +90,12 @@ export const AppendsTreeSelect: React.FC { if (props.multiple) { @@ -111,7 +118,7 @@ export const AppendsTreeSelect: React.FC { const parentNode = rootOption ? { @@ -123,17 +130,19 @@ export const AppendsTreeSelect: React.FC Object.assign(result, { [item.value]: item }), {}); + : getCollectionFieldOptions.call({ compile, getCollectionFields, filter }, collectionString, parentNode); + + const map = tData.reduce((result, item) => Object.assign(result, { [item.value]: item }), {}); if (parentNode) { map[parentNode.value] = parentNode; } setOptionsMap(map); - }, [collection, baseCollection, rootOption, filter, propsLoadData]); + }, [collectionString, rootOption, filter, propsLoadData]); + // NOTE: preload options in value useEffect(() => { const arr = (props.multiple ? propsValue : propsValue ? [propsValue] : []) as string[]; if (!arr?.length || arr.every((v) => Boolean(optionsMap[v]))) { diff --git a/packages/core/data-source-manager/src/utils.ts b/packages/core/data-source-manager/src/utils.ts index 79eec06f6..392a01bd2 100644 --- a/packages/core/data-source-manager/src/utils.ts +++ b/packages/core/data-source-manager/src/utils.ts @@ -7,3 +7,10 @@ export function parseCollectionName(collection: string) { const dataSourceName = dataSourceCollection[0] ?? 'main'; return [dataSourceName, collectionName]; } + +export function joinCollectionName(dataSourceName: string, collectionName: string) { + if (!dataSourceName || dataSourceName === 'main') { + return collectionName; + } + return `${dataSourceName}:${collectionName}`; +} diff --git a/packages/core/test/package.json b/packages/core/test/package.json index fc3172483..f6ee33665 100644 --- a/packages/core/test/package.json +++ b/packages/core/test/package.json @@ -56,7 +56,7 @@ "sqlite3": "^5.0.8", "supertest": "^6.1.6", "vite": "^5.0.0", - "vitest": "^1.0.0", + "vitest": "^1.4.0", "vitest-dom": "^0.1.1", "ws": "^8.13.0" }, diff --git a/packages/core/utils/src/json-templates.ts b/packages/core/utils/src/json-templates.ts index 9a38a29f3..baad0ca02 100644 --- a/packages/core/utils/src/json-templates.ts +++ b/packages/core/utils/src/json-templates.ts @@ -101,18 +101,13 @@ const parseString = (() => { value = value(); } - if (typeof value === 'object' && value !== null) { + // Accommodate numbers as values. + if (str.startsWith('{{') && str.endsWith('}}')) { return value; } - // Accommodate numbers as values. - if (matches.length === 1 && str.startsWith('{{') && str.endsWith('}}')) { - return value; - } - - // Accommodate numbers as values. - if (matches.length === 1 && str.startsWith('{{') && str.endsWith('}}')) { - return value; + if (value instanceof Date) { + value = value.toISOString(); } return result.replace(match, value == null ? '' : value); diff --git a/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/client/ActionTrigger.tsx b/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/client/ActionTrigger.tsx index cbf88b9e7..a49e6ada5 100644 --- a/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/client/ActionTrigger.tsx +++ b/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/client/ActionTrigger.tsx @@ -6,7 +6,12 @@ import { useCollectionManager_deprecated, useCompile, } from '@nocobase/client'; -import { Trigger, CollectionBlockInitializer, getCollectionFieldOptions } from '@nocobase/plugin-workflow/client'; +import { + Trigger, + CollectionBlockInitializer, + getCollectionFieldOptions, + useWorkflowAnyExecuted, +} from '@nocobase/plugin-workflow/client'; import { NAMESPACE, useLang } from '../locale'; export default class extends Trigger { @@ -17,10 +22,8 @@ export default class extends Trigger { type: 'string', required: true, 'x-decorator': 'FormItem', - 'x-component': 'CollectionSelect', - 'x-component-props': { - className: 'auto-width', - }, + 'x-component': 'DataSourceCollectionCascader', + 'x-disabled': '{{ useWorkflowAnyExecuted() }}', title: `{{t("Collection", { ns: "${NAMESPACE}" })}}`, description: `{{t("Which collection record belongs to.", { ns: "${NAMESPACE}" })}}`, 'x-reactions': [ @@ -63,6 +66,7 @@ export default class extends Trigger { }; scope = { useCollectionDataSource, + useWorkflowAnyExecuted, }; isActionTriggerable = (config, context) => { return ['create', 'update', 'customize:update', 'customize:triggerWorkflows'].includes(context.action); @@ -126,7 +130,7 @@ export default class extends Trigger { title: `{{t("Trigger data", { ns: "${NAMESPACE}" })}}`, Component: CollectionBlockInitializer, collection: config.collection, - dataSource: '{{$context.data}}', + dataPath: '$context.data', }; } } diff --git a/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/client/__e2e__/configuration.test.ts b/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/client/__e2e__/configuration.test.ts index c3aab3ade..8c2024354 100644 --- a/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/client/__e2e__/configuration.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/client/__e2e__/configuration.test.ts @@ -9,6 +9,7 @@ import { apiUpdateWorkflowTrigger, appendJsonCollectionName, generalWithNoRelationalFields, + apiGetDataSourceCount, } from '@nocobase/plugin-workflow-test/e2e'; import { expect, test } from '@nocobase/test/e2e'; import { dayjs } from '@nocobase/utils'; @@ -51,7 +52,8 @@ test.describe('Configuration page to configure the Trigger node', () => { const formEventTriggerNode = new FormEventTriggerNode(page, workFlowName, triggerNodeCollectionName); await formEventTriggerNode.nodeConfigure.click(); await formEventTriggerNode.collectionDropDown.click(); - await page.getByRole('option', { name: triggerNodeCollectionDisplayName }).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); await formEventTriggerNode.submitButton.click(); //配置录入数据区块 @@ -60,6 +62,10 @@ test.describe('Configuration page to configure the Trigger node', () => { await page.waitForLoadState('networkidle'); await page.getByLabel('schema-initializer-Grid-page:addBlock').hover(); await page.getByRole('menuitem', { name: 'table Table' }).hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: `${triggerNodeCollectionDisplayName}` }).click(); // 移开鼠标,关闭菜单 @@ -143,7 +149,8 @@ test.describe('Configuration page to configure the Trigger node', () => { const formEventTriggerNode = new FormEventTriggerNode(page, workFlowName, triggerNodeCollectionName); await formEventTriggerNode.nodeConfigure.click(); await formEventTriggerNode.collectionDropDown.click(); - await page.getByRole('option', { name: triggerNodeCollectionDisplayName }).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); await formEventTriggerNode.submitButton.click(); //配置录入数据区块 @@ -152,6 +159,10 @@ test.describe('Configuration page to configure the Trigger node', () => { await page.waitForLoadState('networkidle'); await page.getByLabel('schema-initializer-Grid-page:addBlock').hover(); await page.getByRole('menuitem', { name: 'table Table' }).hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: `${triggerNodeCollectionDisplayName}` }).click(); // 移开鼠标,关闭菜单 @@ -649,8 +660,11 @@ test.describe('Configuration page copy to new version', () => { // 3、预期结果:新版本工作流配置内容同旧版本一样 const formEventTriggerNode = new FormEventTriggerNode(page, workFlowName, triggerNodeCollectionName); await formEventTriggerNode.nodeConfigure.click(); - await expect(page.getByRole('button', { name: triggerNodeCollectionDisplayName })).toBeVisible(); - + await expect( + page + .getByLabel('block-item-DataSourceCollectionCascader-workflows-Collection') + .getByText(`Main / ${triggerNodeCollectionDisplayName}`), + ).toBeVisible(); // 4、后置处理:删除工作流 await apiDeleteWorkflow(workflowId); }); diff --git a/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/server/ActionTrigger.ts b/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/server/ActionTrigger.ts index b113924b0..0fc614183 100644 --- a/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/server/ActionTrigger.ts +++ b/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/server/ActionTrigger.ts @@ -1,11 +1,11 @@ import { get } from 'lodash'; import { BelongsTo, HasOne } from 'sequelize'; import { Model, modelAssociationByKey } from '@nocobase/database'; -import { DefaultContext } from '@nocobase/server'; -import { ActionContext } from '@nocobase/resourcer'; -import { Next } from '@nocobase/actions'; +import Application, { DefaultContext } from '@nocobase/server'; +import { Context as ActionContext, Next } from '@nocobase/actions'; import WorkflowPlugin, { Trigger, WorkflowModel, toJSON } from '@nocobase/plugin-workflow'; +import { parseCollectionName } from '@nocobase/data-source-manager'; interface Context extends ActionContext, DefaultContext {} @@ -13,7 +13,7 @@ export default class extends Trigger { constructor(workflow: WorkflowPlugin) { super(workflow); - workflow.app.resourcer.use(this.middleware); + workflow.app.use(this.middleware, { after: 'dataSource' }); } async triggerAction(context: Context, next: Next) { @@ -55,6 +55,7 @@ export default class extends Trigger { private async trigger(context: Context) { const { triggerWorkflows = '', values } = context.action.params; + const dataSourceHeader = context.get('x-data-source') || 'main'; const { currentUser, currentRole } = context.state; const { model: UserModel } = this.workflow.db.getCollection('users'); @@ -79,12 +80,16 @@ export default class extends Trigger { const asyncGroup = []; for (const workflow of workflows) { const { collection, appends = [] } = workflow.config; + const [dataSourceName, collectionName] = parseCollectionName(collection); const trigger = triggers.find((trigger) => trigger[0] == workflow.key); const event = [workflow]; if (context.action.resourceName !== 'workflows') { if (!context.body) { continue; } + if (dataSourceName !== dataSourceHeader) { + continue; + } const { body: data } = context; for (const row of Array.isArray(data) ? data : [data]) { let payload = row; @@ -101,7 +106,7 @@ export default class extends Trigger { } const model = payload.constructor; if (payload instanceof Model) { - if (collection !== model.collection.name) { + if (collectionName !== model.collection.name) { continue; } if (appends.length) { @@ -115,7 +120,9 @@ export default class extends Trigger { event.push({ data: toJSON(payload), ...userInfo }); } } else { - const { model, repository } = context.db.getCollection(collection); + const { model, repository } = (context.app).dataSourceManager.dataSources + .get(dataSourceName) + .collectionManager.getCollection(collectionName); let data = trigger[1] ? get(values, trigger[1]) : values; const pk = get(data, model.primaryKeyAttribute); if (appends.length && pk != null) { diff --git a/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/server/__tests__/trigger.test.ts b/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/server/__tests__/trigger.test.ts index d30cc936a..ae16cfda0 100644 --- a/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/server/__tests__/trigger.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow-action-trigger/src/server/__tests__/trigger.test.ts @@ -12,7 +12,7 @@ describe('workflow > action-trigger', () => { let PostRepo; let CommentRepo; let WorkflowModel; - let UserModel; + let UserRepo; let users; let userAgents; @@ -26,12 +26,14 @@ describe('workflow > action-trigger', () => { WorkflowModel = db.getCollection('workflows').model; PostRepo = db.getCollection('posts').repository; CommentRepo = db.getCollection('comments').repository; - UserModel = db.getCollection('users').model; + UserRepo = db.getCollection('users').repository; - users = await UserModel.bulkCreate([ - { id: 2, nickname: 'a' }, - { id: 3, nickname: 'b' }, - ]); + users = await UserRepo.create({ + values: [ + { id: 2, nickname: 'a', roles: [{ name: 'root' }] }, + { id: 3, nickname: 'b' }, + ], + }); userAgents = users.map((user) => app.agent().login(user)); }); @@ -569,4 +571,49 @@ describe('workflow > action-trigger', () => { expect(e3s[0].status).toBe(EXECUTION_STATUS.RESOLVED); }); }); + + describe('multiple data source', () => { + it('trigger on different data source', async () => { + const workflow = await WorkflowModel.create({ + enabled: true, + type: 'action', + config: { + collection: 'another:posts', + }, + }); + + const res1 = await userAgents[0].resource('posts').create({ + values: { title: 't1' }, + triggerWorkflows: `${workflow.key}`, + }); + expect(res1.status).toBe(200); + + await sleep(500); + + const e1s = await workflow.getExecutions(); + expect(e1s.length).toBe(0); + + // const res2 = await userAgents[0] + // .set('x-data-source', 'another') + // .resource('posts') + // .create({ + // values: { title: 't2' }, + // triggerWorkflows: `${workflow.key}`, + // }); + const res2 = await agent + .login(users[0]) + .set('x-data-source', 'another') + .post('/api/posts:create') + .query({ triggerWorkflows: `${workflow.key}` }) + .send({ title: 't2' }); + + expect(res2.status).toBe(200); + + await sleep(500); + + const e2s = await workflow.getExecutions(); + expect(e2s.length).toBe(1); + expect(e2s[0].status).toBe(EXECUTION_STATUS.RESOLVED); + }); + }); }); diff --git a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/client/AggregateInstruction.tsx b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/client/AggregateInstruction.tsx index 9ee696428..a2dc67c77 100644 --- a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/client/AggregateInstruction.tsx +++ b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/client/AggregateInstruction.tsx @@ -6,6 +6,8 @@ import { SchemaComponentContext, SchemaInitializerItemType, css, + joinCollectionName, + parseCollectionName, useCollectionDataSource, useCollectionFilterOptions, useCollectionManager_deprecated, @@ -116,25 +118,34 @@ function AssociatedConfig({ value, onChange, ...props }): JSX.Element { // need to get: // * source collection (from node.config) // * target collection (from field name) - const { collectionName, target, name } = field; + const { collectionName, target, name, dataSourceKey } = field; - const collection = getCollection(collectionName); + const collection = getCollection(collectionName, dataSourceKey); const primaryKeyField = collection.fields.find((f) => f.primaryKey); - setValuesIn('collection', target); + setValuesIn('collection', `${dataSourceKey}:${target}`); onChange({ name, // primary key data path associatedKey: `{{${path.slice(0, -1).join('.')}.${primaryKeyField.name}}}`, // data associated collection name - associatedCollection: collectionName, + associatedCollection: joinCollectionName(dataSourceKey, collectionName), }); }, [onChange], ); - return ; + return ( + + ); } // based on collection: @@ -217,7 +228,7 @@ export default class extends Instruction { type: 'string', required: true, 'x-decorator': 'FormItem', - 'x-component': 'CollectionSelect', + 'x-component': 'DataSourceCollectionCascader', title: `{{t("Data of collection", { ns: "${NAMESPACE}" })}}`, 'x-reactions': [ { @@ -333,7 +344,8 @@ export default class extends Instruction { 'x-component-props': { useProps() { const { values } = useForm(); - const options = useCollectionFilterOptions(values?.collection); + const [dataSourceName, collectionName] = parseCollectionName(values?.collection); + const options = useCollectionFilterOptions(collectionName, dataSourceName); return { options, className: css` diff --git a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/client/__e2e__/DataOfCollection.test.ts b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/client/__e2e__/DataOfCollection.test.ts index f788fa7c1..aca22522a 100644 --- a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/client/__e2e__/DataOfCollection.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/client/__e2e__/DataOfCollection.test.ts @@ -80,7 +80,8 @@ test.describe('no filter', () => { const aggregateRecordNodeId = await aggregateRecordNode.node.locator('.workflow-node-id').innerText(); await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); await aggregateRecordNode.submitButton.click(); @@ -179,7 +180,8 @@ test.describe('no filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.sumRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); await aggregateRecordNode.submitButton.click(); @@ -281,7 +283,8 @@ test.describe('no filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.avgRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); await aggregateRecordNode.submitButton.click(); @@ -385,7 +388,8 @@ test.describe('no filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.minRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); await aggregateRecordNode.submitButton.click(); @@ -488,7 +492,8 @@ test.describe('no filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.maxRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); await aggregateRecordNode.submitButton.click(); @@ -590,7 +595,8 @@ test.describe('no filter', () => { const aggregateRecordNodeId = await aggregateRecordNode.node.locator('.workflow-node-id').innerText(); await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); await aggregateRecordNode.distinctCheckBox.click(); @@ -690,7 +696,8 @@ test.describe('no filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.sumRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); await aggregateRecordNode.distinctCheckBox.click(); @@ -795,7 +802,8 @@ test.describe('no filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.avgRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); await aggregateRecordNode.distinctCheckBox.click(); @@ -902,7 +910,8 @@ test.describe('no filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.minRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); await aggregateRecordNode.distinctCheckBox.click(); @@ -1006,7 +1015,8 @@ test.describe('no filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.maxRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); await aggregateRecordNode.distinctCheckBox.click(); @@ -1111,7 +1121,8 @@ test.describe('filter', () => { const aggregateRecordNodeId = await aggregateRecordNode.node.locator('.workflow-node-id').innerText(); await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); // 过滤条件 @@ -1217,7 +1228,8 @@ test.describe('filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.sumRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); // 过滤条件 @@ -1327,7 +1339,8 @@ test.describe('filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.avgRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); // 过滤条件 @@ -1442,7 +1455,8 @@ test.describe('filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.minRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); // 过滤条件 @@ -1553,7 +1567,8 @@ test.describe('filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.maxRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); // 过滤条件 @@ -1663,7 +1678,8 @@ test.describe('filter', () => { const aggregateRecordNodeId = await aggregateRecordNode.node.locator('.workflow-node-id').innerText(); await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); await aggregateRecordNode.distinctCheckBox.click(); @@ -1770,7 +1786,8 @@ test.describe('filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.sumRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); await aggregateRecordNode.distinctCheckBox.click(); @@ -1885,7 +1902,8 @@ test.describe('filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.avgRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); await aggregateRecordNode.distinctCheckBox.click(); @@ -2009,7 +2027,8 @@ test.describe('filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.minRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); await aggregateRecordNode.distinctCheckBox.click(); @@ -2121,7 +2140,8 @@ test.describe('filter', () => { await aggregateRecordNode.nodeConfigure.click(); await aggregateRecordNode.maxRadio.click(); await aggregateRecordNode.collectionDropDown.click(); - await page.getByText(aggregateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: aggregateNodeCollectionDisplayName }).click(); await aggregateRecordNode.aggregatedFieldDropDown.click(); await page.getByRole('option', { name: aggregateNodeFieldDisplayName }).click(); await aggregateRecordNode.distinctCheckBox.click(); diff --git a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/AggregateInstruction.ts b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/AggregateInstruction.ts index 50bf8e144..f5d5fc79d 100644 --- a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/AggregateInstruction.ts +++ b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/AggregateInstruction.ts @@ -1,4 +1,5 @@ -import { BelongsToManyRepository, DataTypes, HasManyRepository } from '@nocobase/database'; +import { parseCollectionName } from '@nocobase/data-source-manager'; +import { DataTypes } from '@nocobase/database'; import { Processor, Instruction, JOB_STATUS, FlowNodeModel } from '@nocobase/plugin-workflow'; const aggregators = { @@ -13,13 +14,14 @@ export default class extends Instruction { async run(node: FlowNodeModel, input, processor: Processor) { const { aggregator, associated, collection, association = {}, params = {} } = node.config; const options = processor.getParsedValue(params, node.id); - const { database } = node.constructor; + const [dataSourceName, collectionName] = parseCollectionName(collection); + const { collectionManager } = this.workflow.app.dataSourceManager.dataSources.get(dataSourceName); const repo = associated - ? database.getRepository( + ? collectionManager.getRepository( `${association?.associatedCollection}.${association.name}`, processor.getParsedValue(association?.associatedKey, node.id), ) - : database.getRepository(collection); + : collectionManager.getRepository(collectionName); if (!options.dataType && aggregator === 'avg') { options.dataType = DataTypes.DOUBLE; diff --git a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/__tests__/instruction.test.ts b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/__tests__/instruction.test.ts index 3bb1efe94..712822afa 100644 --- a/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/__tests__/instruction.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow-aggregate/src/server/__tests__/instruction.test.ts @@ -3,6 +3,7 @@ import { Application } from '@nocobase/server'; import { getApp, sleep } from '@nocobase/plugin-workflow-test'; import Plugin from '..'; +import { EXECUTION_STATUS } from '@nocobase/plugin-workflow'; describe('workflow > instructions > aggregate', () => { let app: Application; @@ -295,4 +296,33 @@ describe('workflow > instructions > aggregate', () => { expect(j3.result).toBe(1); }); }); + + describe('multiple data source', () => { + it('query on another data source', async () => { + const AnotherPostRepo = app.dataSourceManager.dataSources.get('another').collectionManager.getRepository('posts'); + const post = await AnotherPostRepo.create({ values: { title: 't1' } }); + const p1s = await AnotherPostRepo.find(); + expect(p1s.length).toBe(1); + + const n1 = await workflow.createNode({ + type: 'aggregate', + config: { + collection: 'another:posts', + aggregator: 'count', + params: { + field: 'id', + }, + }, + }); + + await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const [execution] = await workflow.getExecutions(); + expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED); + const [job] = await execution.getJobs(); + expect(job.result).toBe(1); + }); + }); }); diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/WorkflowTodo.tsx b/packages/plugins/@nocobase/plugin-workflow-manual/src/client/WorkflowTodo.tsx index a429c146b..3ef15cdef 100644 --- a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/WorkflowTodo.tsx +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/client/WorkflowTodo.tsx @@ -24,10 +24,10 @@ import WorkflowPlugin, { linkNodes, useAvailableUpstreams, useFlowContext, + DetailsBlockProvider, } from '@nocobase/plugin-workflow/client'; import { NAMESPACE, useLang } from '../locale'; -import { DetailsBlockProvider } from './instruction/DetailsBlockProvider'; import { FormBlockProvider } from './instruction/FormBlockProvider'; import { ManualFormType, manualFormTypes } from './instruction/SchemaConfig'; diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/__e2e__/createRecordForm.test.ts b/packages/plugins/@nocobase/plugin-workflow-manual/src/client/__e2e__/createRecordForm.test.ts index b1988c123..7b4e2a96f 100644 --- a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/__e2e__/createRecordForm.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/client/__e2e__/createRecordForm.test.ts @@ -10,6 +10,7 @@ import { apiUpdateWorkflowTrigger, appendJsonCollectionName, generalWithNoRelationalFields, + apiGetDataSourceCount, } from '@nocobase/plugin-workflow-test/e2e'; import { expect, test } from '@nocobase/test/e2e'; import { dayjs } from '@nocobase/utils'; @@ -81,6 +82,10 @@ test.describe('field data entry', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -216,6 +221,10 @@ test.describe('field data entry', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -351,6 +360,10 @@ test.describe('field data entry', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -486,6 +499,10 @@ test.describe('field data entry', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -621,6 +638,10 @@ test.describe('field data entry', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -756,6 +777,10 @@ test.describe('field data entry', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -891,6 +916,10 @@ test.describe('field data entry', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -1027,6 +1056,10 @@ test.describe('field data entry', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -1162,6 +1195,10 @@ test.describe('field data entry', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -1297,6 +1334,10 @@ test.describe('field data entry', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -1440,6 +1481,10 @@ test.describe('field data entry', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -1574,6 +1619,10 @@ test.describe('field data entry', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -1715,6 +1764,10 @@ test.describe('field data entry', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -1865,6 +1918,10 @@ test.describe('action button', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -2000,6 +2057,10 @@ test.describe('action button', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -2141,6 +2202,10 @@ test.describe('action button', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/__e2e__/updateRecordForm.test.ts b/packages/plugins/@nocobase/plugin-workflow-manual/src/client/__e2e__/updateRecordForm.test.ts index ddff9eb21..d6c32fb6c 100644 --- a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/__e2e__/updateRecordForm.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/client/__e2e__/updateRecordForm.test.ts @@ -10,6 +10,7 @@ import { apiUpdateWorkflowTrigger, appendJsonCollectionName, generalWithNoRelationalFields, + apiGetDataSourceCount, } from '@nocobase/plugin-workflow-test/e2e'; import { expect, test } from '@nocobase/test/e2e'; import { dayjs } from '@nocobase/utils'; @@ -97,6 +98,10 @@ test.describe('field data update', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.updateRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -249,6 +254,10 @@ test.describe('field data update', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.updateRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -401,6 +410,10 @@ test.describe('field data update', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.updateRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -553,6 +566,10 @@ test.describe('field data update', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.updateRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -705,6 +722,10 @@ test.describe('field data update', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.updateRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -857,6 +878,10 @@ test.describe('field data update', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.updateRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -1025,6 +1050,10 @@ test.describe('field data update', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.updateRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -1193,6 +1222,10 @@ test.describe('field data update', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.updateRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -1361,6 +1394,10 @@ test.describe('field data update', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.updateRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -1530,6 +1567,10 @@ test.describe('field data update', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.updateRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -1707,6 +1748,10 @@ test.describe('field data update', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.updateRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -1875,6 +1920,10 @@ test.describe('field data update', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.updateRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -2049,6 +2098,10 @@ test.describe('field data update', () => { await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.updateRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/__e2e__/workflowTodo.test.ts b/packages/plugins/@nocobase/plugin-workflow-manual/src/client/__e2e__/workflowTodo.test.ts index 672dd6e89..a93881f5c 100644 --- a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/__e2e__/workflowTodo.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/client/__e2e__/workflowTodo.test.ts @@ -8,6 +8,7 @@ import { apiUpdateWorkflowTrigger, appendJsonCollectionName, generalWithNoRelationalFields, + apiGetDataSourceCount, } from '@nocobase/plugin-workflow-test/e2e'; import { expect, test } from '@nocobase/test/e2e'; import { dayjs } from '@nocobase/utils'; @@ -69,6 +70,10 @@ test('filter task node', async ({ page, mockPage, mockCollections, mockRecords } await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page @@ -179,6 +184,10 @@ test('filter workflow name', async ({ page, mockPage, mockCollections, mockRecor await manualNode.configureUserInterfaceButton.click(); await manualNode.addBlockButton.hover(); await manualNode.createRecordFormMenu.hover(); + const dataSourcesCount = await apiGetDataSourceCount(); + if (dataSourcesCount > 1) { + await page.getByRole('menuitem', { name: 'Main right' }).hover(); + } await page.getByRole('menuitem', { name: manualNodeCollectionDisplayName }).click(); await page.mouse.move(300, 0, { steps: 100 }); await page diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/instruction/SchemaConfig.tsx b/packages/plugins/@nocobase/plugin-workflow-manual/src/client/instruction/SchemaConfig.tsx index 271830b44..6af0e1b92 100644 --- a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/instruction/SchemaConfig.tsx +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/client/instruction/SchemaConfig.tsx @@ -35,6 +35,8 @@ import { } from '@nocobase/client'; import WorkflowPlugin, { JOB_STATUS, + DetailsBlockProvider, + SimpleDesigner, useAvailableUpstreams, useFlowContext, useNodeContext, @@ -44,7 +46,6 @@ import WorkflowPlugin, { import { Registry, lodash } from '@nocobase/utils/client'; import { NAMESPACE, useLang } from '../../locale'; -import { DetailsBlockProvider } from './DetailsBlockProvider'; import { FormBlockProvider } from './FormBlockProvider'; import createRecordForm from './forms/create'; import customRecordForm from './forms/custom'; @@ -104,24 +105,6 @@ const blockTypeNames = { record: `{{t("Data record", { ns: "${NAMESPACE}" })}}`, }; -function SimpleDesigner() { - const schema = useFieldSchema(); - const title = blockTypeNames[schema['x-designer-props']?.type] ?? '{{t("Block")}}'; - const compile = useCompile(); - return ( - - - - - - ); -} - /** * @deprecated */ @@ -153,7 +136,7 @@ export const addBlockButton_deprecated = new CompatibleSchemaInitializer({ { name: 'nodes', type: 'subMenu', - title: `{{t("Node result", { ns: "${NAMESPACE}" })}}`, + title: `{{t("Node result", { ns: "workflow" })}}`, children: nodeBlockInitializers, }, ] diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/instruction/forms/custom.tsx b/packages/plugins/@nocobase/plugin-workflow-manual/src/client/instruction/forms/custom.tsx index f81a1057c..13a48ae2c 100644 --- a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/instruction/forms/custom.tsx +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/client/instruction/forms/custom.tsx @@ -3,7 +3,7 @@ import React, { useContext, useMemo, useState } from 'react'; import { ArrayTable } from '@formily/antd-v5'; import { Field, createForm } from '@formily/core'; import { useField, useFieldSchema, useForm } from '@formily/react'; -import lodash from 'lodash'; +import { cloneDeep, pick, set } from 'lodash'; import { ActionContextProvider, @@ -161,7 +161,7 @@ function getOptions(interfaces) { const schema = interfaces[type]; const { group = 'others' } = schema; fields[group] = fields[group] || {}; - lodash.set(fields, [group, type], schema); + set(fields, [group, type], schema); }); return Object.keys(GroupLabels) @@ -208,33 +208,51 @@ const CustomItemsComponent = (props) => { const items = useCommonInterfaceInitializers(); const collection = useCollection_deprecated(); const { setCollectionFields } = useContext(FormBlockContext); + const form = useMemo(() => createForm(), [interfaceOptions]); return ( = pick(item, [ + 'name', + 'group', + 'title', + 'default', + 'validateSchema', + ]); const { - properties: { unique, type, ...properties }, - ...options - } = lodash.cloneDeep(item); - delete properties.name['x-disabled']; - setInterface({ - ...options, - properties, - }); + properties: { unique, type, layout, autoIncrement, ...properties }, + } = item; + fieldInterface.properties = properties; + const result = cloneDeep(fieldInterface); + delete result.properties.name['x-disabled']; + setInterface(result); }, setCallback, }} > - + {interfaceOptions ? ( { 'x-component-props': { type: 'primary', useAction() { - const { values, query } = useForm(); + const { values, query, reset } = useForm(); const messages = [useLang('Field name existed in form')]; return { async run() { @@ -301,6 +319,7 @@ const CustomItemsComponent = (props) => { 'x-toolbar': 'FormItemSchemaToolbar', 'x-settings': 'fieldSettings:FormItem', }); + reset(); setCallback(null); setInterface(null); }, diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/instruction/index.tsx b/packages/plugins/@nocobase/plugin-workflow-manual/src/client/instruction/index.tsx index 886f1d3f4..7167e8e17 100644 --- a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/instruction/index.tsx +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/client/instruction/index.tsx @@ -138,7 +138,7 @@ export default class extends Instruction { title: form.title ?? formKey, Component: CollectionBlockInitializer, collection: form.collection, - dataSource: `{{$jobsMapByNodeKey.${node.key}.${formKey}}}`, + dataPath: `$jobsMapByNodeKey.${node.key}.${formKey}`, } as SchemaInitializerItemType) : null; }) diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/data-source.test.ts b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/data-source.test.ts new file mode 100644 index 000000000..e92dc9cdb --- /dev/null +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/data-source.test.ts @@ -0,0 +1,223 @@ +import Database from '@nocobase/database'; +import { EXECUTION_STATUS, JOB_STATUS } from '@nocobase/plugin-workflow'; +import { getApp, sleep } from '@nocobase/plugin-workflow-test'; +import { MockServer } from '@nocobase/test'; + +// NOTE: skipped because time is not stable on github ci, but should work in local +describe('workflow > instructions > manual', () => { + let app: MockServer; + let agent; + let userAgents; + let db: Database; + let PostRepo; + let AnotherPostRepo; + let WorkflowModel; + let workflow; + let UserModel; + let users; + let UserJobModel; + + beforeEach(async () => { + app = await getApp({ + plugins: ['users', 'auth', 'workflow-manual'], + }); + // await app.getPlugin('auth').install(); + agent = app.agent(); + db = app.db; + WorkflowModel = db.getCollection('workflows').model; + PostRepo = db.getCollection('posts').repository; + AnotherPostRepo = app.dataSourceManager.dataSources.get('another').collectionManager.getRepository('posts'); + UserModel = db.getCollection('users').model; + UserJobModel = db.getModel('users_jobs'); + + users = await UserModel.bulkCreate([ + { id: 2, nickname: 'a' }, + { id: 3, nickname: 'b' }, + ]); + + userAgents = users.map((user) => app.agent().login(user)); + + workflow = await WorkflowModel.create({ + enabled: true, + type: 'collection', + config: { + mode: 1, + collection: 'posts', + }, + }); + }); + + afterEach(() => app.destroy()); + + describe('multiple data source', () => { + describe('create', () => { + it('create as configured', async () => { + const n1 = await workflow.createNode({ + type: 'manual', + config: { + assignees: [users[0].id], + forms: { + f1: { + type: 'create', + actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }], + collection: 'posts', + dataSource: 'another', + }, + }, + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const UserJobModel = db.getModel('users_jobs'); + const pendingJobs = await UserJobModel.findAll({ + order: [['userId', 'ASC']], + }); + expect(pendingJobs.length).toBe(1); + + const res1 = await userAgents[0].resource('users_jobs').submit({ + filterByTk: pendingJobs[0].get('id'), + values: { + result: { f1: { title: 't1' }, _: 'resolve' }, + }, + }); + expect(res1.status).toBe(202); + + await sleep(500); + + const [e1] = await workflow.getExecutions(); + expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED); + const [j1] = await e1.getJobs(); + expect(j1.status).toBe(JOB_STATUS.RESOLVED); + expect(j1.result).toMatchObject({ f1: { title: 't1' } }); + + const posts = await AnotherPostRepo.find(); + expect(posts.length).toBe(1); + expect(posts[0]).toMatchObject({ title: 't1' }); + }); + + it('save first and then commit', async () => { + const n1 = await workflow.createNode({ + type: 'manual', + config: { + assignees: [users[0].id], + forms: { + f1: { + type: 'create', + actions: [ + { status: JOB_STATUS.RESOLVED, key: 'resolve' }, + { status: JOB_STATUS.PENDING, key: 'pending' }, + ], + collection: 'posts', + dataSource: 'another', + }, + }, + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const UserJobModel = db.getModel('users_jobs'); + const pendingJobs = await UserJobModel.findAll({ + order: [['userId', 'ASC']], + }); + expect(pendingJobs.length).toBe(1); + + const res1 = await userAgents[0].resource('users_jobs').submit({ + filterByTk: pendingJobs[0].get('id'), + values: { + result: { f1: { title: 't1' }, _: 'pending' }, + }, + }); + expect(res1.status).toBe(202); + + await sleep(500); + + const [e1] = await workflow.getExecutions(); + expect(e1.status).toBe(EXECUTION_STATUS.STARTED); + const [j1] = await e1.getJobs(); + expect(j1.status).toBe(JOB_STATUS.PENDING); + expect(j1.result).toMatchObject({ f1: { title: 't1' } }); + + const c1 = await AnotherPostRepo.find(); + expect(c1.length).toBe(0); + + const res2 = await userAgents[0].resource('users_jobs').submit({ + filterByTk: pendingJobs[0].get('id'), + values: { + result: { f1: { title: 't2' }, _: 'resolve' }, + }, + }); + + await sleep(500); + + const [e2] = await workflow.getExecutions(); + expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED); + const [j2] = await e2.getJobs(); + expect(j2.status).toBe(JOB_STATUS.RESOLVED); + expect(j2.result).toMatchObject({ f1: { title: 't2' } }); + + const c2 = await AnotherPostRepo.find(); + expect(c2.length).toBe(1); + }); + }); + + describe('update', () => { + it('update as configured', async () => { + const post = await AnotherPostRepo.create({ values: { title: 't1' } }); + + const n1 = await workflow.createNode({ + type: 'manual', + config: { + assignees: [users[0].id], + forms: { + f1: { + type: 'update', + actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }], + collection: 'posts', + dataSource: 'another', + filter: { + id: post.id, + }, + }, + }, + }, + }); + + await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const UserJobModel = db.getModel('users_jobs'); + const pendingJobs = await UserJobModel.findAll({ + order: [['userId', 'ASC']], + }); + expect(pendingJobs.length).toBe(1); + + const res1 = await userAgents[0].resource('users_jobs').submit({ + filterByTk: pendingJobs[0].get('id'), + values: { + result: { f1: { title: 't2' }, _: 'resolve' }, + }, + }); + expect(res1.status).toBe(202); + + await sleep(500); + + const [e2] = await workflow.getExecutions(); + expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED); + const [j1] = await e2.getJobs(); + expect(j1.status).toBe(JOB_STATUS.RESOLVED); + expect(j1.result).toMatchObject({ f1: { title: 't2' } }); + + const postsAfter = await AnotherPostRepo.find(); + expect(postsAfter.length).toBe(1); + expect(postsAfter[0]).toMatchObject({ title: 't2' }); + }); + }); + }); +}); diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/instruction.test.ts b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/form.test.ts similarity index 56% rename from packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/instruction.test.ts rename to packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/form.test.ts index 4f3acca75..1aa2fee67 100644 --- a/packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/instruction.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/form.test.ts @@ -333,516 +333,6 @@ describe('workflow > instructions > manual', () => { }); }); - describe('mode: 0 (single record)', () => { - it('the only user assigned could submit', async () => { - const n1 = await workflow.createNode({ - type: 'manual', - config: { - assignees: [users[0].id], - forms: { - f1: { - actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }], - }, - }, - }, - }); - - const post = await PostRepo.create({ values: { title: 't1' } }); - - await sleep(500); - - const [pending] = await workflow.getExecutions(); - expect(pending.status).toBe(EXECUTION_STATUS.STARTED); - const [j1] = await pending.getJobs(); - expect(j1.status).toBe(JOB_STATUS.PENDING); - - const usersJobs = await UserJobModel.findAll(); - expect(usersJobs.length).toBe(1); - expect(usersJobs[0].status).toBe(JOB_STATUS.PENDING); - expect(usersJobs[0].userId).toBe(users[0].id); - expect(usersJobs[0].jobId).toBe(j1.id); - - const res1 = await agent.resource('users_jobs').submit({ - filterByTk: usersJobs[0].id, - values: { result: { f1: {}, _: 'resolve' } }, - }); - expect(res1.status).toBe(401); - - const res2 = await userAgents[1].resource('users_jobs').submit({ - filterByTk: usersJobs[0].id, - values: { - result: { f1: {}, _: 'resolve' }, - }, - }); - expect(res2.status).toBe(403); - - const res3 = await userAgents[0].resource('users_jobs').submit({ - filterByTk: usersJobs[0].id, - values: { - result: { f1: { a: 1 }, _: 'resolve' }, - }, - }); - expect(res3.status).toBe(202); - - await sleep(1000); - - const [j2] = await pending.getJobs(); - expect(j2.status).toBe(JOB_STATUS.RESOLVED); - expect(j2.result).toEqual({ f1: { a: 1 }, _: 'resolve' }); - - const usersJobsAfter = await UserJobModel.findAll(); - expect(usersJobsAfter.length).toBe(1); - expect(usersJobsAfter[0].status).toBe(JOB_STATUS.RESOLVED); - expect(usersJobsAfter[0].result).toEqual({ f1: { a: 1 }, _: 'resolve' }); - - const res4 = await userAgents[0].resource('users_jobs').submit({ - filterByTk: usersJobs[0].id, - values: { - result: { f1: { a: 2 }, _: 'resolve' }, - }, - }); - expect(res4.status).toBe(400); - }); - - it('any user assigned could submit', async () => { - const n1 = await workflow.createNode({ - type: 'manual', - config: { - assignees: [users[0].id, users[1].id], - forms: { - f1: { - actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }], - }, - }, - }, - }); - - const post = await PostRepo.create({ values: { title: 't1' } }); - - await sleep(500); - - const [pending] = await workflow.getExecutions(); - expect(pending.status).toBe(EXECUTION_STATUS.STARTED); - const [j1] = await pending.getJobs(); - expect(j1.status).toBe(JOB_STATUS.PENDING); - - const usersJobs = await j1.getUsersJobs(); - - const res1 = await userAgents[1].resource('users_jobs').submit({ - filterByTk: usersJobs.find((item) => item.userId === users[1].id).id, - values: { - result: { f1: { a: 1 }, _: 'resolve' }, - }, - }); - expect(res1.status).toBe(202); - - await sleep(1000); - - const [j2] = await pending.getJobs(); - expect(j2.status).toBe(JOB_STATUS.RESOLVED); - expect(j2.result).toEqual({ f1: { a: 1 }, _: 'resolve' }); - - const res2 = await userAgents[0].resource('users_jobs').submit({ - filterByTk: usersJobs.find((item) => item.userId === users[0].id).id, - values: { - result: { f1: { a: 1 }, _: 'resolve' }, - }, - }); - expect(res2.status).toBe(400); - }); - - it('also could submit to users_jobs api', async () => { - const n1 = await workflow.createNode({ - type: 'manual', - config: { - assignees: [users[0].id], - forms: { - f1: { - actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }], - }, - }, - }, - }); - - const post = await PostRepo.create({ values: { title: 't1' } }); - - await sleep(500); - - const UserJobModel = db.getModel('users_jobs'); - const usersJobs = await UserJobModel.findAll(); - expect(usersJobs.length).toBe(1); - expect(usersJobs[0].get('status')).toBe(JOB_STATUS.PENDING); - expect(usersJobs[0].get('userId')).toBe(users[0].id); - - const res = await userAgents[0].resource('users_jobs').submit({ - filterByTk: usersJobs[0].get('id'), - values: { - result: { f1: { a: 1 }, _: 'resolve' }, - }, - }); - expect(res.status).toBe(202); - - await sleep(1000); - - const [execution] = await workflow.getExecutions(); - expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED); - const [job] = await execution.getJobs(); - expect(job.status).toBe(JOB_STATUS.RESOLVED); - expect(job.result).toEqual({ f1: { a: 1 }, _: 'resolve' }); - }); - }); - - describe('mode: 1 (multiple record, all)', () => { - it('all resolved', async () => { - const n1 = await workflow.createNode({ - type: 'manual', - config: { - assignees: [users[0].id, users[1].id], - mode: 1, - forms: { - f1: { - actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }], - }, - }, - }, - }); - - const post = await PostRepo.create({ values: { title: 't1' } }); - - await sleep(500); - - const UserJobModel = db.getModel('users_jobs'); - const pendingJobs = await UserJobModel.findAll({ - order: [['userId', 'ASC']], - }); - expect(pendingJobs.length).toBe(2); - - const res1 = await userAgents[0].resource('users_jobs').submit({ - filterByTk: pendingJobs[0].get('id'), - values: { - result: { f1: { a: 1 }, _: 'resolve' }, - }, - }); - expect(res1.status).toBe(202); - - await sleep(1000); - - const [e1] = await workflow.getExecutions(); - expect(e1.status).toBe(EXECUTION_STATUS.STARTED); - const [j1] = await e1.getJobs(); - expect(j1.status).toBe(JOB_STATUS.PENDING); - expect(j1.result).toBe(0.5); - const usersJobs1 = await UserJobModel.findAll({ - order: [['userId', 'ASC']], - }); - expect(usersJobs1.length).toBe(2); - - const res2 = await userAgents[1].resource('users_jobs').submit({ - filterByTk: pendingJobs[1].get('id'), - values: { - result: { f1: { a: 2 }, _: 'resolve' }, - }, - }); - expect(res2.status).toBe(202); - - await sleep(1000); - - const [e2] = await workflow.getExecutions(); - expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED); - const [j2] = await e2.getJobs(); - expect(j2.status).toBe(JOB_STATUS.RESOLVED); - expect(j2.result).toBe(1); - }); - - it('first rejected', async () => { - const n1 = await workflow.createNode({ - type: 'manual', - config: { - assignees: [users[0].id, users[1].id], - mode: 1, - forms: { - f1: { - actions: [{ status: JOB_STATUS.REJECTED, key: 'reject' }], - }, - }, - }, - }); - - const post = await PostRepo.create({ values: { title: 't1' } }); - - await sleep(500); - - const UserJobModel = db.getModel('users_jobs'); - const pendingJobs = await UserJobModel.findAll({ - order: [['userId', 'ASC']], - }); - expect(pendingJobs.length).toBe(2); - - const res1 = await userAgents[0].resource('users_jobs').submit({ - filterByTk: pendingJobs[0].get('id'), - values: { - result: { f1: { a: 0 }, _: 'reject' }, - }, - }); - expect(res1.status).toBe(202); - - await sleep(1000); - - const [e1] = await workflow.getExecutions(); - expect(e1.status).toBe(EXECUTION_STATUS.REJECTED); - const [j1] = await e1.getJobs(); - expect(j1.status).toBe(JOB_STATUS.REJECTED); - expect(j1.result).toBe(0.5); - const usersJobs1 = await UserJobModel.findAll({ - order: [['userId', 'ASC']], - }); - expect(usersJobs1.length).toBe(2); - - const res2 = await userAgents[1].resource('users_jobs').submit({ - filterByTk: pendingJobs[1].get('id'), - values: { - result: { f1: { a: 0 }, _: 'reject' }, - }, - }); - expect(res2.status).toBe(400); - }); - - it('last rejected', async () => { - const n1 = await workflow.createNode({ - type: 'manual', - config: { - assignees: [users[0].id, users[1].id], - mode: 1, - forms: { - f1: { - actions: [ - { status: JOB_STATUS.RESOLVED, key: 'resolve' }, - { status: JOB_STATUS.REJECTED, key: 'reject' }, - ], - }, - }, - }, - }); - - const post = await PostRepo.create({ values: { title: 't1' } }); - - await sleep(500); - - const UserJobModel = db.getModel('users_jobs'); - const pendingJobs = await UserJobModel.findAll({ - order: [['userId', 'ASC']], - }); - expect(pendingJobs.length).toBe(2); - - const res1 = await userAgents[0].resource('users_jobs').submit({ - filterByTk: pendingJobs[0].get('id'), - values: { - result: { f1: { a: 1 }, _: 'resolve' }, - }, - }); - expect(res1.status).toBe(202); - - await sleep(1000); - - const [e1] = await workflow.getExecutions(); - expect(e1.status).toBe(EXECUTION_STATUS.STARTED); - const [j1] = await e1.getJobs(); - expect(j1.status).toBe(JOB_STATUS.PENDING); - expect(j1.result).toBe(0.5); - const usersJobs1 = await UserJobModel.findAll({ - order: [['userId', 'ASC']], - }); - expect(usersJobs1.length).toBe(2); - - const res2 = await userAgents[1].resource('users_jobs').submit({ - filterByTk: pendingJobs[1].get('id'), - values: { - result: { f1: { a: 0 }, _: 'reject' }, - }, - }); - expect(res2.status).toBe(202); - - await sleep(1000); - - const [e2] = await workflow.getExecutions(); - expect(e2.status).toBe(EXECUTION_STATUS.REJECTED); - const [j2] = await e2.getJobs(); - expect(j2.status).toBe(JOB_STATUS.REJECTED); - expect(j2.result).toBe(1); - }); - }); - - describe('mode: -1 (multiple record, any)', () => { - it('first resolved', async () => { - const n1 = await workflow.createNode({ - type: 'manual', - config: { - assignees: [users[0].id, users[1].id], - mode: -1, - forms: { - f1: { - actions: [ - { status: JOB_STATUS.RESOLVED, key: 'resolve' }, - { status: JOB_STATUS.REJECTED, key: 'reject' }, - ], - }, - }, - }, - }); - - const post = await PostRepo.create({ values: { title: 't1' } }); - - await sleep(500); - - const UserJobModel = db.getModel('users_jobs'); - const pendingJobs = await UserJobModel.findAll({ - order: [['userId', 'ASC']], - }); - expect(pendingJobs.length).toBe(2); - - const res1 = await userAgents[0].resource('users_jobs').submit({ - filterByTk: pendingJobs[0].get('id'), - values: { - result: { f1: { a: 1 }, _: 'resolve' }, - }, - }); - expect(res1.status).toBe(202); - - await sleep(1000); - - const [e1] = await workflow.getExecutions(); - expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED); - const [j1] = await e1.getJobs(); - expect(j1.status).toBe(JOB_STATUS.RESOLVED); - expect(j1.result).toBe(0.5); - - const res2 = await userAgents[1].resource('users_jobs').submit({ - filterByTk: pendingJobs[1].get('id'), - values: { - result: { f1: { a: 0 }, _: 'reject' }, - }, - }); - expect(res2.status).toBe(400); - }); - - it('any resolved', async () => { - const n1 = await workflow.createNode({ - type: 'manual', - config: { - assignees: [users[0].id, users[1].id], - mode: -1, - forms: { - f1: { - actions: [ - { status: JOB_STATUS.RESOLVED, key: 'resolve' }, - { status: JOB_STATUS.REJECTED, key: 'reject' }, - ], - }, - }, - }, - }); - - const post = await PostRepo.create({ values: { title: 't1' } }); - - await sleep(500); - - const UserJobModel = db.getModel('users_jobs'); - const pendingJobs = await UserJobModel.findAll({ - order: [['userId', 'ASC']], - }); - expect(pendingJobs.length).toBe(2); - - const res1 = await userAgents[0].resource('users_jobs').submit({ - filterByTk: pendingJobs[0].get('id'), - values: { - result: { f1: { a: 0 }, _: 'reject' }, - }, - }); - expect(res1.status).toBe(202); - - await sleep(1000); - - const [e1] = await workflow.getExecutions(); - expect(e1.status).toBe(EXECUTION_STATUS.STARTED); - const [j1] = await e1.getJobs(); - expect(j1.status).toBe(JOB_STATUS.PENDING); - expect(j1.result).toBe(0.5); - - const res2 = await userAgents[1].resource('users_jobs').submit({ - filterByTk: pendingJobs[1].get('id'), - values: { - result: { f1: { a: 1 }, _: 'resolve' }, - }, - }); - expect(res2.status).toBe(202); - - await sleep(1000); - - const [e2] = await workflow.getExecutions(); - expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED); - const [j2] = await e2.getJobs(); - expect(j2.status).toBe(JOB_STATUS.RESOLVED); - expect(j2.result).toBe(1); - }); - - it('all rejected', async () => { - const n1 = await workflow.createNode({ - type: 'manual', - config: { - assignees: [users[0].id, users[1].id], - mode: -1, - forms: { - f1: { - actions: [{ status: JOB_STATUS.REJECTED, key: 'reject' }], - }, - }, - }, - }); - - const post = await PostRepo.create({ values: { title: 't1' } }); - - await sleep(500); - - const UserJobModel = db.getModel('users_jobs'); - const pendingJobs = await UserJobModel.findAll({ - order: [['userId', 'ASC']], - }); - expect(pendingJobs.length).toBe(2); - - const res1 = await userAgents[0].resource('users_jobs').submit({ - filterByTk: pendingJobs[0].get('id'), - values: { - result: { f1: { a: 0 }, _: 'reject' }, - }, - }); - expect(res1.status).toBe(202); - - await sleep(1000); - - const [e1] = await workflow.getExecutions(); - expect(e1.status).toBe(EXECUTION_STATUS.STARTED); - const [j1] = await e1.getJobs(); - expect(j1.status).toBe(JOB_STATUS.PENDING); - expect(j1.result).toBe(0.5); - - const res2 = await userAgents[1].resource('users_jobs').submit({ - filterByTk: pendingJobs[1].get('id'), - values: { - result: { f1: { a: 0 }, _: 'reject' }, - }, - }); - expect(res2.status).toBe(202); - - await sleep(1000); - - const [e2] = await workflow.getExecutions(); - expect(e2.status).toBe(EXECUTION_STATUS.REJECTED); - const [j2] = await e2.getJobs(); - expect(j2.status).toBe(JOB_STATUS.REJECTED); - expect(j2.result).toBe(1); - }); - }); - describe('use result of submitted form in manual node', () => { it('result should be available and correct', async () => { const n1 = await workflow.createNode({ diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/mode.test.ts b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/mode.test.ts new file mode 100644 index 000000000..8ea37def4 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/__tests__/mode.test.ts @@ -0,0 +1,561 @@ +import Database from '@nocobase/database'; +import { EXECUTION_STATUS, JOB_STATUS } from '@nocobase/plugin-workflow'; +import { getApp, sleep } from '@nocobase/plugin-workflow-test'; +import { MockServer } from '@nocobase/test'; + +// NOTE: skipped because time is not stable on github ci, but should work in local +describe('workflow > instructions > manual', () => { + let app: MockServer; + let agent; + let userAgents; + let db: Database; + let PostRepo; + let CommentRepo; + let WorkflowModel; + let workflow; + let UserModel; + let users; + let UserJobModel; + + beforeEach(async () => { + app = await getApp({ + plugins: ['users', 'auth', 'workflow-manual'], + }); + // await app.getPlugin('auth').install(); + agent = app.agent(); + db = app.db; + WorkflowModel = db.getCollection('workflows').model; + PostRepo = db.getCollection('posts').repository; + CommentRepo = db.getCollection('comments').repository; + UserModel = db.getCollection('users').model; + UserJobModel = db.getModel('users_jobs'); + + users = await UserModel.bulkCreate([ + { id: 2, nickname: 'a' }, + { id: 3, nickname: 'b' }, + ]); + + userAgents = users.map((user) => app.agent().login(user)); + + workflow = await WorkflowModel.create({ + enabled: true, + type: 'collection', + config: { + mode: 1, + collection: 'posts', + }, + }); + }); + + afterEach(() => app.destroy()); + + describe('mode: 0 (single record)', () => { + it('the only user assigned could submit', async () => { + const n1 = await workflow.createNode({ + type: 'manual', + config: { + assignees: [users[0].id], + forms: { + f1: { + actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }], + }, + }, + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const [pending] = await workflow.getExecutions(); + expect(pending.status).toBe(EXECUTION_STATUS.STARTED); + const [j1] = await pending.getJobs(); + expect(j1.status).toBe(JOB_STATUS.PENDING); + + const usersJobs = await UserJobModel.findAll(); + expect(usersJobs.length).toBe(1); + expect(usersJobs[0].status).toBe(JOB_STATUS.PENDING); + expect(usersJobs[0].userId).toBe(users[0].id); + expect(usersJobs[0].jobId).toBe(j1.id); + + const res1 = await agent.resource('users_jobs').submit({ + filterByTk: usersJobs[0].id, + values: { result: { f1: {}, _: 'resolve' } }, + }); + expect(res1.status).toBe(401); + + const res2 = await userAgents[1].resource('users_jobs').submit({ + filterByTk: usersJobs[0].id, + values: { + result: { f1: {}, _: 'resolve' }, + }, + }); + expect(res2.status).toBe(403); + + const res3 = await userAgents[0].resource('users_jobs').submit({ + filterByTk: usersJobs[0].id, + values: { + result: { f1: { a: 1 }, _: 'resolve' }, + }, + }); + expect(res3.status).toBe(202); + + await sleep(1000); + + const [j2] = await pending.getJobs(); + expect(j2.status).toBe(JOB_STATUS.RESOLVED); + expect(j2.result).toEqual({ f1: { a: 1 }, _: 'resolve' }); + + const usersJobsAfter = await UserJobModel.findAll(); + expect(usersJobsAfter.length).toBe(1); + expect(usersJobsAfter[0].status).toBe(JOB_STATUS.RESOLVED); + expect(usersJobsAfter[0].result).toEqual({ f1: { a: 1 }, _: 'resolve' }); + + const res4 = await userAgents[0].resource('users_jobs').submit({ + filterByTk: usersJobs[0].id, + values: { + result: { f1: { a: 2 }, _: 'resolve' }, + }, + }); + expect(res4.status).toBe(400); + }); + + it('any user assigned could submit', async () => { + const n1 = await workflow.createNode({ + type: 'manual', + config: { + assignees: [users[0].id, users[1].id], + forms: { + f1: { + actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }], + }, + }, + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const [pending] = await workflow.getExecutions(); + expect(pending.status).toBe(EXECUTION_STATUS.STARTED); + const [j1] = await pending.getJobs(); + expect(j1.status).toBe(JOB_STATUS.PENDING); + + const usersJobs = await j1.getUsersJobs(); + + const res1 = await userAgents[1].resource('users_jobs').submit({ + filterByTk: usersJobs.find((item) => item.userId === users[1].id).id, + values: { + result: { f1: { a: 1 }, _: 'resolve' }, + }, + }); + expect(res1.status).toBe(202); + + await sleep(1000); + + const [j2] = await pending.getJobs(); + expect(j2.status).toBe(JOB_STATUS.RESOLVED); + expect(j2.result).toEqual({ f1: { a: 1 }, _: 'resolve' }); + + const res2 = await userAgents[0].resource('users_jobs').submit({ + filterByTk: usersJobs.find((item) => item.userId === users[0].id).id, + values: { + result: { f1: { a: 1 }, _: 'resolve' }, + }, + }); + expect(res2.status).toBe(400); + }); + + it('also could submit to users_jobs api', async () => { + const n1 = await workflow.createNode({ + type: 'manual', + config: { + assignees: [users[0].id], + forms: { + f1: { + actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }], + }, + }, + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const UserJobModel = db.getModel('users_jobs'); + const usersJobs = await UserJobModel.findAll(); + expect(usersJobs.length).toBe(1); + expect(usersJobs[0].get('status')).toBe(JOB_STATUS.PENDING); + expect(usersJobs[0].get('userId')).toBe(users[0].id); + + const res = await userAgents[0].resource('users_jobs').submit({ + filterByTk: usersJobs[0].get('id'), + values: { + result: { f1: { a: 1 }, _: 'resolve' }, + }, + }); + expect(res.status).toBe(202); + + await sleep(1000); + + const [execution] = await workflow.getExecutions(); + expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED); + const [job] = await execution.getJobs(); + expect(job.status).toBe(JOB_STATUS.RESOLVED); + expect(job.result).toEqual({ f1: { a: 1 }, _: 'resolve' }); + }); + }); + + describe('mode: 1 (multiple record, all)', () => { + it('all resolved', async () => { + const n1 = await workflow.createNode({ + type: 'manual', + config: { + assignees: [users[0].id, users[1].id], + mode: 1, + forms: { + f1: { + actions: [{ status: JOB_STATUS.RESOLVED, key: 'resolve' }], + }, + }, + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const UserJobModel = db.getModel('users_jobs'); + const pendingJobs = await UserJobModel.findAll({ + order: [['userId', 'ASC']], + }); + expect(pendingJobs.length).toBe(2); + + const res1 = await userAgents[0].resource('users_jobs').submit({ + filterByTk: pendingJobs[0].get('id'), + values: { + result: { f1: { a: 1 }, _: 'resolve' }, + }, + }); + expect(res1.status).toBe(202); + + await sleep(1000); + + const [e1] = await workflow.getExecutions(); + expect(e1.status).toBe(EXECUTION_STATUS.STARTED); + const [j1] = await e1.getJobs(); + expect(j1.status).toBe(JOB_STATUS.PENDING); + expect(j1.result).toBe(0.5); + const usersJobs1 = await UserJobModel.findAll({ + order: [['userId', 'ASC']], + }); + expect(usersJobs1.length).toBe(2); + + const res2 = await userAgents[1].resource('users_jobs').submit({ + filterByTk: pendingJobs[1].get('id'), + values: { + result: { f1: { a: 2 }, _: 'resolve' }, + }, + }); + expect(res2.status).toBe(202); + + await sleep(1000); + + const [e2] = await workflow.getExecutions(); + expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED); + const [j2] = await e2.getJobs(); + expect(j2.status).toBe(JOB_STATUS.RESOLVED); + expect(j2.result).toBe(1); + }); + + it('first rejected', async () => { + const n1 = await workflow.createNode({ + type: 'manual', + config: { + assignees: [users[0].id, users[1].id], + mode: 1, + forms: { + f1: { + actions: [{ status: JOB_STATUS.REJECTED, key: 'reject' }], + }, + }, + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const UserJobModel = db.getModel('users_jobs'); + const pendingJobs = await UserJobModel.findAll({ + order: [['userId', 'ASC']], + }); + expect(pendingJobs.length).toBe(2); + + const res1 = await userAgents[0].resource('users_jobs').submit({ + filterByTk: pendingJobs[0].get('id'), + values: { + result: { f1: { a: 0 }, _: 'reject' }, + }, + }); + expect(res1.status).toBe(202); + + await sleep(1000); + + const [e1] = await workflow.getExecutions(); + expect(e1.status).toBe(EXECUTION_STATUS.REJECTED); + const [j1] = await e1.getJobs(); + expect(j1.status).toBe(JOB_STATUS.REJECTED); + expect(j1.result).toBe(0.5); + const usersJobs1 = await UserJobModel.findAll({ + order: [['userId', 'ASC']], + }); + expect(usersJobs1.length).toBe(2); + + const res2 = await userAgents[1].resource('users_jobs').submit({ + filterByTk: pendingJobs[1].get('id'), + values: { + result: { f1: { a: 0 }, _: 'reject' }, + }, + }); + expect(res2.status).toBe(400); + }); + + it('last rejected', async () => { + const n1 = await workflow.createNode({ + type: 'manual', + config: { + assignees: [users[0].id, users[1].id], + mode: 1, + forms: { + f1: { + actions: [ + { status: JOB_STATUS.RESOLVED, key: 'resolve' }, + { status: JOB_STATUS.REJECTED, key: 'reject' }, + ], + }, + }, + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const UserJobModel = db.getModel('users_jobs'); + const pendingJobs = await UserJobModel.findAll({ + order: [['userId', 'ASC']], + }); + expect(pendingJobs.length).toBe(2); + + const res1 = await userAgents[0].resource('users_jobs').submit({ + filterByTk: pendingJobs[0].get('id'), + values: { + result: { f1: { a: 1 }, _: 'resolve' }, + }, + }); + expect(res1.status).toBe(202); + + await sleep(1000); + + const [e1] = await workflow.getExecutions(); + expect(e1.status).toBe(EXECUTION_STATUS.STARTED); + const [j1] = await e1.getJobs(); + expect(j1.status).toBe(JOB_STATUS.PENDING); + expect(j1.result).toBe(0.5); + const usersJobs1 = await UserJobModel.findAll({ + order: [['userId', 'ASC']], + }); + expect(usersJobs1.length).toBe(2); + + const res2 = await userAgents[1].resource('users_jobs').submit({ + filterByTk: pendingJobs[1].get('id'), + values: { + result: { f1: { a: 0 }, _: 'reject' }, + }, + }); + expect(res2.status).toBe(202); + + await sleep(1000); + + const [e2] = await workflow.getExecutions(); + expect(e2.status).toBe(EXECUTION_STATUS.REJECTED); + const [j2] = await e2.getJobs(); + expect(j2.status).toBe(JOB_STATUS.REJECTED); + expect(j2.result).toBe(1); + }); + }); + + describe('mode: -1 (multiple record, any)', () => { + it('first resolved', async () => { + const n1 = await workflow.createNode({ + type: 'manual', + config: { + assignees: [users[0].id, users[1].id], + mode: -1, + forms: { + f1: { + actions: [ + { status: JOB_STATUS.RESOLVED, key: 'resolve' }, + { status: JOB_STATUS.REJECTED, key: 'reject' }, + ], + }, + }, + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const UserJobModel = db.getModel('users_jobs'); + const pendingJobs = await UserJobModel.findAll({ + order: [['userId', 'ASC']], + }); + expect(pendingJobs.length).toBe(2); + + const res1 = await userAgents[0].resource('users_jobs').submit({ + filterByTk: pendingJobs[0].get('id'), + values: { + result: { f1: { a: 1 }, _: 'resolve' }, + }, + }); + expect(res1.status).toBe(202); + + await sleep(1000); + + const [e1] = await workflow.getExecutions(); + expect(e1.status).toBe(EXECUTION_STATUS.RESOLVED); + const [j1] = await e1.getJobs(); + expect(j1.status).toBe(JOB_STATUS.RESOLVED); + expect(j1.result).toBe(0.5); + + const res2 = await userAgents[1].resource('users_jobs').submit({ + filterByTk: pendingJobs[1].get('id'), + values: { + result: { f1: { a: 0 }, _: 'reject' }, + }, + }); + expect(res2.status).toBe(400); + }); + + it('any resolved', async () => { + const n1 = await workflow.createNode({ + type: 'manual', + config: { + assignees: [users[0].id, users[1].id], + mode: -1, + forms: { + f1: { + actions: [ + { status: JOB_STATUS.RESOLVED, key: 'resolve' }, + { status: JOB_STATUS.REJECTED, key: 'reject' }, + ], + }, + }, + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const UserJobModel = db.getModel('users_jobs'); + const pendingJobs = await UserJobModel.findAll({ + order: [['userId', 'ASC']], + }); + expect(pendingJobs.length).toBe(2); + + const res1 = await userAgents[0].resource('users_jobs').submit({ + filterByTk: pendingJobs[0].get('id'), + values: { + result: { f1: { a: 0 }, _: 'reject' }, + }, + }); + expect(res1.status).toBe(202); + + await sleep(1000); + + const [e1] = await workflow.getExecutions(); + expect(e1.status).toBe(EXECUTION_STATUS.STARTED); + const [j1] = await e1.getJobs(); + expect(j1.status).toBe(JOB_STATUS.PENDING); + expect(j1.result).toBe(0.5); + + const res2 = await userAgents[1].resource('users_jobs').submit({ + filterByTk: pendingJobs[1].get('id'), + values: { + result: { f1: { a: 1 }, _: 'resolve' }, + }, + }); + expect(res2.status).toBe(202); + + await sleep(1000); + + const [e2] = await workflow.getExecutions(); + expect(e2.status).toBe(EXECUTION_STATUS.RESOLVED); + const [j2] = await e2.getJobs(); + expect(j2.status).toBe(JOB_STATUS.RESOLVED); + expect(j2.result).toBe(1); + }); + + it('all rejected', async () => { + const n1 = await workflow.createNode({ + type: 'manual', + config: { + assignees: [users[0].id, users[1].id], + mode: -1, + forms: { + f1: { + actions: [{ status: JOB_STATUS.REJECTED, key: 'reject' }], + }, + }, + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const UserJobModel = db.getModel('users_jobs'); + const pendingJobs = await UserJobModel.findAll({ + order: [['userId', 'ASC']], + }); + expect(pendingJobs.length).toBe(2); + + const res1 = await userAgents[0].resource('users_jobs').submit({ + filterByTk: pendingJobs[0].get('id'), + values: { + result: { f1: { a: 0 }, _: 'reject' }, + }, + }); + expect(res1.status).toBe(202); + + await sleep(1000); + + const [e1] = await workflow.getExecutions(); + expect(e1.status).toBe(EXECUTION_STATUS.STARTED); + const [j1] = await e1.getJobs(); + expect(j1.status).toBe(JOB_STATUS.PENDING); + expect(j1.result).toBe(0.5); + + const res2 = await userAgents[1].resource('users_jobs').submit({ + filterByTk: pendingJobs[1].get('id'), + values: { + result: { f1: { a: 0 }, _: 'reject' }, + }, + }); + expect(res2.status).toBe(202); + + await sleep(1000); + + const [e2] = await workflow.getExecutions(); + expect(e2.status).toBe(EXECUTION_STATUS.REJECTED); + const [j2] = await e2.getJobs(); + expect(j2.status).toBe(JOB_STATUS.REJECTED); + expect(j2.result).toBe(1); + }); + }); +}); diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/server/forms/create.ts b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/forms/create.ts index a8df62961..9afc9b760 100644 --- a/packages/plugins/@nocobase/plugin-workflow-manual/src/server/forms/create.ts +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/forms/create.ts @@ -1,8 +1,15 @@ import { Processor } from '@nocobase/plugin-workflow'; import ManualInstruction from '../ManualInstruction'; -export default async function (this: ManualInstruction, instance, { collection }, processor: Processor) { - const repo = this.workflow.db.getRepository(collection); +export default async function ( + this: ManualInstruction, + instance, + { dataSource = 'main', collection }, + processor: Processor, +) { + const repo = this.workflow.app.dataSourceManager.dataSources + .get(dataSource) + .collectionManager.getRepository(collection); if (!repo) { throw new Error(`collection ${collection} for create data on manual node not found`); } @@ -18,6 +25,6 @@ export default async function (this: ManualInstruction, instance, { collection } context: { executionId: processor.execution.id, }, - // transaction: processor.transaction, + transaction: processor.transaction, }); } diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/server/forms/update.ts b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/forms/update.ts index 62702b83a..83adfe3b8 100644 --- a/packages/plugins/@nocobase/plugin-workflow-manual/src/server/forms/update.ts +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/forms/update.ts @@ -1,8 +1,15 @@ import { Processor } from '@nocobase/plugin-workflow'; import ManualInstruction from '../ManualInstruction'; -export default async function (this: ManualInstruction, instance, { collection, filter = {} }, processor: Processor) { - const repo = this.workflow.db.getRepository(collection); +export default async function ( + this: ManualInstruction, + instance, + { dataSource = 'main', collection, filter = {} }, + processor: Processor, +) { + const repo = this.workflow.app.dataSourceManager.dataSources + .get(dataSource) + .collectionManager.getRepository(collection); if (!repo) { throw new Error(`collection ${collection} for update data on manual node not found`); } @@ -18,6 +25,6 @@ export default async function (this: ManualInstruction, instance, { collection, context: { executionId: processor.execution.id, }, - // transaction: processor.transaction, + transaction: processor.transaction, }); } diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/server/migrations/20240314163421-change-schema-data-source.ts b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/migrations/20240314163421-change-schema-data-source.ts new file mode 100644 index 000000000..4d452c2e5 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-workflow-manual/src/server/migrations/20240314163421-change-schema-data-source.ts @@ -0,0 +1,76 @@ +import { Migration } from '@nocobase/server'; + +function findSchema(root, filter, onlyLeaf = false) { + const result = []; + + if (!root) { + return result; + } + + if (filter(root) && (!onlyLeaf || !root.properties)) { + result.push(root); + return result; + } + + if (root.properties) { + Object.keys(root.properties).forEach((key) => { + result.push(...findSchema(root.properties[key], filter)); + }); + } + return result; +} + +function migrateSchema(schema) { + const root = { properties: schema }; + + const [node] = findSchema(root, (item) => { + return ( + item['x-decorator'] === 'DetailsBlockProvider' && + item['x-component'] === 'CardItem' && + item['x-designer'] === 'SimpleDesigner' + ); + }); + + if (node && node['x-decorator-props']?.dataSource) { + node['x-decorator-props'].dataPath = node['x-decorator-props'].dataSource.replace(/^{{|}}$/g, ''); + delete node['x-decorator-props'].dataSource; + } + + return schema; +} + +export default class extends Migration { + async up() { + const { db } = this.context; + const NodeRepo = db.getRepository('flow_nodes'); + await db.sequelize.transaction(async (transaction) => { + const nodes = await NodeRepo.find({ + filter: { + type: 'manual', + }, + transaction, + }); + console.log('%d nodes need to be migrated.', nodes.length); + + await nodes.reduce( + (promise, node) => + promise.then(() => { + const { schema, ...config } = node.config; + return node.update( + { + config: { + ...config, + ...migrateSchema(schema), + }, + }, + { + silent: true, + transaction, + }, + ); + }), + Promise.resolve(), + ); + }); + } +} diff --git a/packages/plugins/@nocobase/plugin-workflow-sql/src/client/SQLInstruction.tsx b/packages/plugins/@nocobase/plugin-workflow-sql/src/client/SQLInstruction.tsx index a640ec4d6..bdb41f516 100644 --- a/packages/plugins/@nocobase/plugin-workflow-sql/src/client/SQLInstruction.tsx +++ b/packages/plugins/@nocobase/plugin-workflow-sql/src/client/SQLInstruction.tsx @@ -12,6 +12,21 @@ export default class extends Instruction { group = 'collection'; description = `{{t("Execute a SQL statement in database.", { ns: "${NAMESPACE}" })}}`; fieldset = { + dataSource: { + type: 'string', + required: true, + title: `{{t("Data source")}}`, + description: `{{t("Select a data source to execute SQL.", { ns: "${NAMESPACE}" })}}`, + 'x-decorator': 'FormItem', + 'x-component': 'DataSourceSelect', + 'x-component-props': { + className: 'auto-width', + filter(item) { + return item.options.isDBInstance; + }, + }, + default: 'main', + }, sql: { type: 'string', required: true, diff --git a/packages/plugins/@nocobase/plugin-workflow-sql/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-workflow-sql/src/locale/zh-CN.json index 4d841b8e4..bb9549300 100644 --- a/packages/plugins/@nocobase/plugin-workflow-sql/src/locale/zh-CN.json +++ b/packages/plugins/@nocobase/plugin-workflow-sql/src/locale/zh-CN.json @@ -1,5 +1,6 @@ { "SQL action": "SQL 操作", "Execute a SQL statement in database.": "在数据库中执行一个 SQL 语句", + "Select a data source to execute SQL.": "选择一个数据源来执行 SQL", "SQL query result could be used through <1>JSON query node (Commercial plugin).": "SQL 执行的结果可在 <1>JSON 解析节点 中使用(商业插件)。" } diff --git a/packages/plugins/@nocobase/plugin-workflow-sql/src/server/SQLInstruction.ts b/packages/plugins/@nocobase/plugin-workflow-sql/src/server/SQLInstruction.ts index f75a1beaa..d47a0bfbb 100644 --- a/packages/plugins/@nocobase/plugin-workflow-sql/src/server/SQLInstruction.ts +++ b/packages/plugins/@nocobase/plugin-workflow-sql/src/server/SQLInstruction.ts @@ -2,15 +2,22 @@ import { Processor, Instruction, JOB_STATUS, FlowNodeModel } from '@nocobase/plu export default class extends Instruction { async run(node: FlowNodeModel, input, processor: Processor) { - const { sequelize } = (node.constructor).database; - const sql = processor.getParsedValue(node.config.sql ?? '', node.id).trim(); + // @ts-ignore + const { db } = this.workflow.app.dataSourceManager.dataSources.get( + node.config.dataSource || 'main', + ).collectionManager; + if (!db) { + throw new Error(`type of data source "${node.config.dataSource}" is not database`); + } + const sql = processor.getParsedValue(node.config.sql || '', node.id).trim(); if (!sql) { return { status: JOB_STATUS.RESOLVED, }; } - const result = await sequelize.query(sql, { + // @ts-ignore + const result = await db.sequelize.query(sql, { transaction: processor.transaction, // plain: true, // model: db.getCollection(node.config.collection).model diff --git a/packages/plugins/@nocobase/plugin-workflow-sql/src/server/__tests__/instruction.test.ts b/packages/plugins/@nocobase/plugin-workflow-sql/src/server/__tests__/instruction.test.ts index 4c626a66a..39bb2acb4 100644 --- a/packages/plugins/@nocobase/plugin-workflow-sql/src/server/__tests__/instruction.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow-sql/src/server/__tests__/instruction.test.ts @@ -92,6 +92,26 @@ describe('workflow > instructions > sql', () => { }); describe('sql with variables', () => { + it('$system.now', async () => { + const queryInterface = db.sequelize.getQueryInterface(); + const n1 = await workflow.createNode({ + type: 'sql', + config: { + sql: `select '{{$system.now}}' as a`, + }, + }); + + const post = await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const [execution] = await workflow.getExecutions(); + const [sqlJob] = await execution.getJobs({ order: [['id', 'ASC']] }); + expect(sqlJob.status).toBe(JOB_STATUS.RESOLVED); + // expect(queryJob.status).toBe(JOB_STATUS.RESOLVED); + // expect(queryJob.result.read).toBe(post.id); + }); + it('update', async () => { const queryInterface = db.sequelize.getQueryInterface(); const n1 = await workflow.createNode({ @@ -195,4 +215,35 @@ describe('workflow > instructions > sql', () => { expect(job.status).toBe(JOB_STATUS.RESOLVED); }); }); + + describe('multiple data source', () => { + it('query on another data source', async () => { + const anotherSource = app.dataSourceManager.dataSources.get('another'); + const PostCollection = anotherSource.collectionManager.getCollection('posts'); + const { repository: AnotherPostRepo } = PostCollection; + const post = await AnotherPostRepo.create({ values: { title: 't1' } }); + const p1s = await AnotherPostRepo.find(); + expect(p1s.length).toBe(1); + + const n1 = await workflow.createNode({ + type: 'sql', + config: { + dataSource: 'another', + sql: `select * from ${PostCollection.quotedTableName()}`, + }, + }); + + await PostRepo.create({ values: { title: 't1' } }); + + await sleep(500); + + const [execution] = await workflow.getExecutions(); + expect(execution.status).toBe(EXECUTION_STATUS.RESOLVED); + const [job] = await execution.getJobs(); + expect(job.result.length).toBe(2); + expect(job.result[0].length).toBe(1); + // @ts-ignore + expect(job.result[0][0].id).toBe(post.id); + }); + }); }); diff --git a/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2ePageObjectModel.ts b/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2ePageObjectModel.ts index a5bed4fc2..8f744651a 100644 --- a/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2ePageObjectModel.ts +++ b/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2ePageObjectModel.ts @@ -79,62 +79,102 @@ export class ApprovalTriggerNode { nodeTitle: Locator; nodeConfigure: Locator; collectionDropDown: Locator; - checkWthdrawable: Locator; - configureUserInterfaceButton: Locator; + dataBlocksInitiationRadio: Locator; + dataBlocksAndGlobalApprovalBlocksInitiationRadio: Locator; + allowedToBeWithdrawnCheckbox: Locator; + goToconfigureButton: Locator; addBlockButton: Locator; addApplyFormMenu: Locator; configureFieldsButton: Locator; configureActionsButton: Locator; saveDraftSwitch: Locator; + submitButton: Locator; + cancelButton: Locator; addNodeButton: Locator; constructor(page: Page, triggerName: string, collectionName: string) { this.page = page; this.node = page.getByText('TriggeraConfigure'); this.nodeTitle = page.locator('textarea').filter({ hasText: triggerName }); this.nodeConfigure = page.getByRole('button', { name: 'Configure' }); - this.collectionDropDown = page.getByRole('button', { name: 'Select collection' }); - this.checkWthdrawable = page.getByLabel('Withdrawable'); - this.configureUserInterfaceButton = page.getByRole('button', { name: 'Configure user interface' }); - this.addBlockButton = page.getByRole('button', { name: 'Add block' }); + this.collectionDropDown = page + .getByLabel('block-item-DataSourceCollectionCascader-workflows-Collection') + .locator('.ant-select-selection-search-input'); + this.dataBlocksInitiationRadio = page.getByLabel('Initiate and approve in data blocks only'); + this.dataBlocksAndGlobalApprovalBlocksInitiationRadio = page.getByLabel( + 'Initiate and approve in both data blocks and global approval blocks', + ); + this.allowedToBeWithdrawnCheckbox = page.getByLabel('Allowed to be withdrawn'); + this.goToconfigureButton = page.getByRole('button', { name: 'Go to configure' }); + this.addBlockButton = page.getByLabel(`schema-initializer-Grid-ApprovalApplyAddBlockButton-${collectionName}`); this.addApplyFormMenu = page.getByRole('menuitem', { name: 'Apply form' }); - this.configureFieldsButton = page.getByTestId('configure-fields-button-of-form-item-' + collectionName); - this.configureActionsButton = page.getByTestId( - 'approval-trigger-configure-form-actions-add-action-button-' + collectionName, + this.configureFieldsButton = page.getByLabel(`schema-initializer-Grid-form:configureFields-${collectionName}`); + this.configureActionsButton = page.getByLabel( + `schema-initializer-ActionBar-ApprovalApplyAddActionButton-${collectionName}`, ); this.saveDraftSwitch = page.getByRole('menuitem', { name: 'Save draft' }).getByRole('switch'); - this.addNodeButton = page.getByLabel('add-button', { exact: true }); + this.submitButton = page.getByLabel('action-Action-Submit-workflows'); + this.cancelButton = page.getByLabel('action-Action-Cancel-workflows'); + this.addNodeButton = this.addNodeButton = page.getByLabel('add-button', { exact: true }); } } -export class ApprovalNode { +export class ApprovalPassthroughModeNode { readonly page: Page; node: Locator; nodeTitle: Locator; nodeConfigure: Locator; + addAssigneesButton: Locator; assigneesDropDown: Locator; - checkReturnable: Locator; - configureUserInterfaceButton: Locator; + OrRadio: Locator; + AndRadio: Locator; + votingRadio: Locator; + parallellyRadio: Locator; + sequentiallyRadio: Locator; + goToconfigureButton: Locator; addBlockButton: Locator; - addApplyFormMenu: Locator; - configureFieldsButton: Locator; - configureActionsButton: Locator; - saveDraftSwitch: Locator; + addDetailsMenu: Locator; + detailsConfigureFieldsButton: Locator; + addActionsMenu: Locator; + actionsConfigureFieldsButton: Locator; + actionsConfigureActionsButton: Locator; + addApproveButton: Locator; + addRejectButton: Locator; + addReturnButton: Locator; + addNodeResult: Locator; + submitButton: Locator; + cancelButton: Locator; addNodeButton: Locator; constructor(page: Page, nodeName: string, collectionName: string) { this.page = page; - this.node = page.getByText('TriggeraConfigure'); - this.nodeTitle = page.locator('textarea').filter({ hasText: nodeName }); - this.nodeConfigure = page.getByRole('button', { name: 'Configure' }); - this.assigneesDropDown = page.getByLabel('Search'); - this.checkReturnable = page.getByLabel('Returnable'); - this.configureUserInterfaceButton = page.getByRole('button', { name: 'Configure user interface' }); - this.addBlockButton = page.getByTestId('add-block-button-in-workflow-workflows'); - this.addApplyFormMenu = page.getByRole('menuitem', { name: 'Apply form' }); - this.configureFieldsButton = page.getByTestId('configure-fields-button-of-form-item-' + collectionName); - this.configureActionsButton = page.getByTestId( - 'approval-trigger-configure-form-actions-add-action-button-' + collectionName, + this.node = page.getByLabel(`Approval-${nodeName}`, { exact: true }); + this.nodeTitle = page.getByLabel(`Approval-${nodeName}`, { exact: true }).getByRole('textbox'); + this.nodeConfigure = page + .getByLabel(`Approval-${nodeName}`, { exact: true }) + .getByRole('button', { name: 'Configure' }); + this.addAssigneesButton = page.getByRole('button', { name: 'plus Add assignee' }); + this.assigneesDropDown = page.getByTestId('select-single'); + this.OrRadio = page.getByLabel('Or', { exact: true }); + this.AndRadio = page.getByLabel('And', { exact: true }); + this.votingRadio = page.getByLabel('Voting', { exact: true }); + this.parallellyRadio = page.getByLabel('Parallelly', { exact: true }); + this.sequentiallyRadio = page.getByLabel('Sequentially', { exact: true }); + this.goToconfigureButton = page.getByRole('button', { name: 'Go to configure' }); + this.addBlockButton = page.getByLabel('schema-initializer-Grid-ApprovalProcessAddBlockButton-workflows'); + this.addDetailsMenu = page.getByRole('menuitem', { name: 'Details' }); + this.detailsConfigureFieldsButton = page.getByLabel( + `schema-initializer-Grid-ReadPrettyFormItemInitializers-${collectionName}`, ); - this.saveDraftSwitch = page.getByRole('menuitem', { name: 'Save draft' }).getByRole('switch'); + this.addActionsMenu = page.getByRole('menuitem', { name: 'Actions' }).getByRole('switch'); + this.actionsConfigureFieldsButton = page.getByLabel('schema-initializer-Grid-FormItemInitializers-approvalRecords'); + this.actionsConfigureActionsButton = page.getByLabel( + 'schema-initializer-ActionBar-ApprovalProcessAddActionButton-approvalRecords', + ); + this.addApproveButton = page.getByRole('menuitem', { name: 'Approve' }).getByRole('switch'); + this.addRejectButton = page.getByRole('menuitem', { name: 'Reject' }).getByRole('switch'); + this.addReturnButton = page.getByRole('menuitem', { name: 'Return' }).getByRole('switch'); + this.addNodeResult = page.getByRole('menuitem', { name: 'Node result right' }); + this.submitButton = page.getByLabel('action-Action-Submit-workflows'); + this.cancelButton = page.getByLabel('action-Action-Cancel-workflows'); this.addNodeButton = page.getByLabel(`add-button-calculation-${nodeName}`, { exact: true }); } } @@ -164,7 +204,9 @@ export class ScheduleTriggerNode { this.RrpeatModeDropdown = page.getByLabel('block-item-RepeatField-workflows-Repeat mode'); this.dataTableTimeFieldOptions = page.getByLabel('Based on date field of collection'); - this.collectionDropDown = page.getByRole('button', { name: 'Select collection' }); + this.collectionDropDown = page + .getByLabel('block-item-DataSourceCollectionCascader-workflows-Collection') + .locator('.ant-select-selection-search-input'); this.startTimeDropdown = page.getByLabel('block-item-OnField-workflows-Starts on'); this.submitButton = page.getByLabel('action-Action-Submit-workflows'); this.cancelButton = page.getByLabel('action-Action-Cancel-workflows'); @@ -187,7 +229,10 @@ export class CollectionTriggerNode { this.node = page.getByLabel(`Trigger-${triggerName}`); this.nodeTitle = page.getByLabel(`Trigger-${triggerName}`).getByRole('textbox'); this.nodeConfigure = page.getByLabel(`Trigger-${triggerName}`).getByRole('button', { name: 'Configure' }); - this.collectionDropDown = page.getByRole('button', { name: 'Select collection' }); + // this.collectionDropDown = page.getByRole('button', { name: 'Select collection' }); + this.collectionDropDown = page + .getByLabel('block-item-DataSourceCollectionCascader-workflows-Collection') + .locator('.ant-select-selection-search-input'); this.triggerOnDropdown = page .getByLabel('block-item-Select-workflows-Trigger on') .getByRole('button', { name: 'Trigger on' }); @@ -212,7 +257,9 @@ export class FormEventTriggerNode { this.node = page.getByLabel(`Trigger-${triggerName}`); this.nodeTitle = page.getByLabel(`Trigger-${triggerName}`).getByRole('textbox'); this.nodeConfigure = page.getByLabel(`Trigger-${triggerName}`).getByRole('button', { name: 'Configure' }); - this.collectionDropDown = page.getByRole('button', { name: 'Select collection' }); + this.collectionDropDown = page + .getByLabel('block-item-DataSourceCollectionCascader-workflows-Collection') + .locator('.ant-select-selection-search-input'); this.relationalDataDropdown = page.getByTestId('select-field-Preload associations'); this.submitButton = page.getByLabel('action-Action-Submit-workflows'); this.cancelButton = page.getByLabel('action-Action-Cancel-workflows'); @@ -269,7 +316,9 @@ export class QueryRecordNode { this.nodeConfigure = page .getByLabel(`Query record-${nodeName}`, { exact: true }) .getByRole('button', { name: 'Configure' }); - this.collectionDropDown = page.getByRole('button', { name: 'Select collection' }); + this.collectionDropDown = page + .getByLabel('block-item-DataSourceCollectionCascader-workflows-Collection') + .locator('.ant-select-selection-search-input'); this.allowMultipleDataBoxesForResults = page.getByLabel('Allow multiple records as'); this.addSortFieldsButton = page.getByRole('button', { name: 'plus Add sort field' }); this.pageNumberEditBox = page.getByLabel('variable-constant'); @@ -299,7 +348,9 @@ export class CreateRecordNode { this.nodeConfigure = page .getByLabel(`Create record-${nodeName}`, { exact: true }) .getByRole('button', { name: 'Configure' }); - this.collectionDropDown = page.getByRole('button', { name: 'Select collection' }); + this.collectionDropDown = page + .getByLabel('block-item-DataSourceCollectionCascader-workflows-Collection') + .locator('.ant-select-selection-search-input'); this.addFieldsButton = page.getByRole('button', { name: 'plus Add field' }); this.submitButton = page.getByLabel('action-Action-Submit-workflows'); this.cancelButton = page.getByLabel('action-Action-Cancel-workflows'); @@ -326,7 +377,9 @@ export class UpdateRecordNode { this.nodeConfigure = page .getByLabel(`Update record-${nodeName}`, { exact: true }) .getByRole('button', { name: 'Configure' }); - this.collectionDropDown = page.getByRole('button', { name: 'Select collection' }); + this.collectionDropDown = page + .getByLabel('block-item-DataSourceCollectionCascader-workflows-Collection') + .locator('.ant-select-selection-search-input'); this.batchUpdateModeRadio = page .getByLabel('block-item-IndividualHooksRadioWithTooltip-workflows-Update mode') .getByLabel('Update in a batch'); @@ -356,7 +409,9 @@ export class DeleteRecordNode { this.nodeConfigure = page .getByLabel(`Delete record-${nodeName}`, { exact: true }) .getByRole('button', { name: 'Configure' }); - this.collectionDropDown = page.getByRole('button', { name: 'Select collection' }); + this.collectionDropDown = page + .getByLabel('block-item-DataSourceCollectionCascader-workflows-Collection') + .locator('.ant-select-selection-search-input'); this.submitButton = page.getByLabel('action-Action-Submit-workflows'); this.cancelButton = page.getByLabel('action-Action-Cancel-workflows'); this.addNodeButton = page.getByLabel(`add-button-delete-${nodeName}`, { exact: true }); @@ -395,11 +450,12 @@ export class AggregateNode { this.minRadio = page.getByLabel('MIN', { exact: true }); this.dataTableDataRadio = page.getByLabel('Data of collection'); this.linkedDataTableDataRadio = page.getByLabel('Data of associated collection'); - this.collectionDropDown = page.getByRole('button', { name: 'Select collection' }); - // this.aggregatedFieldDropDown = page.getByLabel('block-item-FieldsSelect-workflows-Field to aggregate').getByRole('textbox').getByRole('combobox'); - this.aggregatedFieldDropDown = page.locator( - 'input.ant-select-selection-search-input[role="combobox"][aria-haspopup="listbox"]', - ); + this.collectionDropDown = page + .getByLabel('block-item-DataSourceCollectionCascader-workflows-Data of collection') + .locator('.ant-select-selection-search-input'); + this.aggregatedFieldDropDown = page + .getByLabel('block-item-FieldsSelect-workflows-Field to aggregate') + .locator('.ant-select-selection-search-input'); this.distinctCheckBox = page .getByLabel('block-item-Checkbox-workflows-Distinct') .locator('input.ant-checkbox-input[type="checkbox"]'); @@ -572,7 +628,7 @@ export default module.exports = { WorkflowManagement, WorkflowListRecords, ApprovalTriggerNode, - ApprovalNode, + ApprovalPassthroughModeNode, ScheduleTriggerNode, CollectionTriggerNode, FormEventTriggerNode, diff --git a/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2eUtils.ts b/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2eUtils.ts index debedba5e..4d39877eb 100644 --- a/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2eUtils.ts +++ b/packages/plugins/@nocobase/plugin-workflow-test/src/e2e/e2eUtils.ts @@ -1,4 +1,4 @@ -import { request } from '@nocobase/test/e2e'; +import { request, Page } from '@nocobase/test/e2e'; const PORT = process.env.APP_PORT || 20000; const APP_BASE_URL = process.env.APP_BASE_URL || `http://localhost:${PORT}`; @@ -722,6 +722,156 @@ export const apiSubmitRecordTriggerFormEvent = async (triggerWorkflows: string, return await result.json(); }; +// 获取数据源个数 +export const apiGetDataSourceCount = async () => { + const api = await request.newContext({ + storageState: process.env.PLAYWRIGHT_AUTH_FILE, + }); + const state = await api.storageState(); + const headers = getHeaders(state); + const result = await api.get(`/api/dataSources:list?pageSize=50`, { + headers, + }); + + if (!result.ok()) { + throw new Error(await result.text()); + } + /* + { + "data": 1 + } + */ + return (await result.json()).meta.count; +}; + +// 添加业务表单条数据触发工作流表单事件,triggerWorkflows=key1!field,key2,key3!field.subfield +export const apiCreateRecordTriggerActionEvent = async ( + collectionName: string, + triggerWorkflows: string, + data: any, +) => { + const api = await request.newContext({ + storageState: process.env.PLAYWRIGHT_AUTH_FILE, + }); + const state = await api.storageState(); + const headers = getHeaders(state); + /* + { + "title": "a11", + "enabled": true, + "description": null + } + */ + const result = await api.post(`/api/${collectionName}:create?triggerWorkflows=${triggerWorkflows}`, { + headers, + data, + }); + + if (!result.ok()) { + throw new Error(await result.text()); + } + /* + { + "data": { + "id": 1, + "createdAt": "2023-12-12T02:43:53.793Z", + "updatedAt": "2023-12-12T05:41:33.300Z", + "key": "fzk3j2oj4el", + "title": "a11", + "enabled": true, + "description": null + }, + "meta": { + "allowedActions": { + "view": [ + 1 + ], + "update": [ + 1 + ], + "destroy": [ + 1 + ] + } + } + } + */ + return (await result.json()).data; +}; + +// 审批中心发起审批 +export const apiApplyApprovalEvent = async (data: any) => { + const api = await request.newContext({ + storageState: process.env.PLAYWRIGHT_AUTH_FILE, + }); + const state = await api.storageState(); + const headers = getHeaders(state); + /* + { + "title": "a11", + "enabled": true, + "description": null + } + */ + const result = await api.post('/api/approvals:create', { + headers, + data, + }); + + if (!result.ok()) { + throw new Error(await result.text()); + } + /* + { + "data": { + "id": 35, + "collectionName": "tt_amt_orgREmwr", + "data": { + "id": 6, + "url": null, + "sort": 3, + "email": null, + "phone": null, + "address": null, + "orgcode": "区域编码000000006", + "orgname": "阿三大苏打实打实的", + "isenable": null, + "staffnum": null, + "createdAt": "2024-03-09T11:37:47.620Z", + "sharesnum": null, + "updatedAt": "2024-03-09T11:37:47.620Z", + "insurednum": null, + "range_json": null, + "regcapital": null, + "testdataid": null, + "createdById": 1, + "paidcapital": null, + "range_check": [], + "updatedById": 1, + "status_radio": null, + "establishdate": null, + "insuranceratio": null, + "range_markdown": null, + "range_richtext": null, + "status_singleselect": null, + "range_multipleselect": [], + "insuranceratio_formula": null + }, + "status": 2, + "workflowId": 39, + "dataKey": "6", + "updatedAt": "2024-03-09T11:37:47.640Z", + "createdAt": "2024-03-09T11:37:47.640Z", + "createdById": 1, + "updatedById": 1, + "workflowKey": null, + "latestExecutionId": null + } + } + */ + return (await result.json()).data; +}; + const getStorageItem = (key: string, storageState: any) => { return storageState.origins .find((item) => item.origin === APP_BASE_URL) @@ -767,6 +917,16 @@ function getHeaders(storageState: any) { return headers; } +// 用户登录新会话 +export const userLogin = async (page: Page, approvalUserEmail: string, approvalUser: string) => { + await page.goto(`${process.env.APP_BASE_URL}/signin`); + await page.getByPlaceholder('Email').fill(approvalUserEmail); + await page.getByPlaceholder('Password').fill(approvalUser); + await page.getByRole('button', { name: 'Sign in' }).click(); + await page.waitForLoadState('networkidle'); + return page; +}; + export default module.exports = { apiCreateWorkflow, apiUpdateWorkflow, @@ -783,4 +943,8 @@ export default module.exports = { apiCreateRecordTriggerFormEvent, apiSubmitRecordTriggerFormEvent, apiFilterList, + apiGetDataSourceCount, + apiCreateRecordTriggerActionEvent, + apiApplyApprovalEvent, + userLogin, }; diff --git a/packages/plugins/@nocobase/plugin-workflow-test/src/server/index.ts b/packages/plugins/@nocobase/plugin-workflow-test/src/server/index.ts index 1f384d5bc..85116ef5b 100644 --- a/packages/plugins/@nocobase/plugin-workflow-test/src/server/index.ts +++ b/packages/plugins/@nocobase/plugin-workflow-test/src/server/index.ts @@ -1,11 +1,14 @@ import path from 'path'; import { ApplicationOptions, Plugin } from '@nocobase/server'; -import { MockServer, createMockServer } from '@nocobase/test'; +import { MockServer, createMockServer, mockDatabase } from '@nocobase/test'; import functions from './functions'; import triggers from './triggers'; import instructions from './instructions'; +import { Resourcer } from '@nocobase/resourcer'; +import { SequelizeDataSource } from '@nocobase/data-source-manager'; +import { uid } from '@nocobase/utils'; export interface MockServerOptions extends ApplicationOptions { collectionsPath?: string; @@ -33,7 +36,7 @@ export async function getApp(options: MockServerOptions = {}): Promise Object.assign(components, workflowPlugin.instructions.get(type).components), + {}, + ); + + return ( + + + {children} + + + ); +} diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/ExecutionLink.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/ExecutionLink.tsx index 5220a0f72..922d804a5 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/ExecutionLink.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/ExecutionLink.tsx @@ -3,7 +3,8 @@ import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; import { useActionContext, useRecord } from '@nocobase/client'; -import { getWorkflowExecutionsPath } from './constant'; + +import { getWorkflowExecutionsPath } from './utils'; export const ExecutionLink = () => { const { t } = useTranslation(); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/FlowContext.ts b/packages/plugins/@nocobase/plugin-workflow/src/client/FlowContext.tsx similarity index 100% rename from packages/plugins/@nocobase/plugin-workflow/src/client/FlowContext.ts rename to packages/plugins/@nocobase/plugin-workflow/src/client/FlowContext.tsx diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/WorkflowCanvas.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/WorkflowCanvas.tsx index 6b654fc6b..2e826a5f3 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/WorkflowCanvas.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/WorkflowCanvas.tsx @@ -21,8 +21,7 @@ import { FlowContext, useFlowContext } from './FlowContext'; import { lang } from './locale'; import { executionSchema } from './schemas/executions'; import useStyles from './style'; -import { linkNodes } from './utils'; -import { getWorkflowDetailPath } from './constant'; +import { linkNodes, getWorkflowDetailPath } from './utils'; import { ExecutionStatusColumn } from './components/ExecutionStatus'; function ExecutionResourceProvider({ request, filter = {}, ...others }) { diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/WorkflowLink.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/WorkflowLink.tsx index 27829791c..16cb9e888 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/WorkflowLink.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/WorkflowLink.tsx @@ -2,9 +2,10 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; -import { getWorkflowDetailPath } from './constant'; import { useActionContext, useGetAriaLabelOfAction, useRecord } from '@nocobase/client'; +import { getWorkflowDetailPath } from './utils'; + export const WorkflowLink = () => { const { t } = useTranslation(); const { id } = useRecord(); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/collectionEventTrigger/configuration.test.ts b/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/collectionEventTrigger/configuration.test.ts index e544d178f..c74c1310b 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/collectionEventTrigger/configuration.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/collectionEventTrigger/configuration.test.ts @@ -47,7 +47,9 @@ test.describe('Configuration page to configure the Trigger node', () => { const collectionTriggerNode = new CollectionTriggerNode(page, workFlowName, triggerNodeCollectionName); await collectionTriggerNode.nodeConfigure.click(); await collectionTriggerNode.collectionDropDown.click(); - await page.getByRole('option', { name: triggerNodeCollectionDisplayName }).click(); + // await page.getByRole('option', { name: triggerNodeCollectionDisplayName }).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); await collectionTriggerNode.triggerOnDropdown.click(); await page.getByText('After record added', { exact: true }).click(); await collectionTriggerNode.submitButton.click(); @@ -101,7 +103,8 @@ test.describe('Configuration page to configure the Trigger node', () => { const collectionTriggerNode = new CollectionTriggerNode(page, workFlowName, triggerNodeCollectionName); await collectionTriggerNode.nodeConfigure.click(); await collectionTriggerNode.collectionDropDown.click(); - await page.getByRole('option', { name: triggerNodeCollectionDisplayName }).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); await collectionTriggerNode.triggerOnDropdown.click(); await page.getByText('After record updated', { exact: true }).click(); await collectionTriggerNode.submitButton.click(); @@ -157,7 +160,8 @@ test.describe('Configuration page to configure the Trigger node', () => { const collectionTriggerNode = new CollectionTriggerNode(page, workFlowName, triggerNodeCollectionName); await collectionTriggerNode.nodeConfigure.click(); await collectionTriggerNode.collectionDropDown.click(); - await page.getByRole('option', { name: triggerNodeCollectionDisplayName }).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); await collectionTriggerNode.triggerOnDropdown.click(); await page.getByText('After record added or updated', { exact: true }).click(); await collectionTriggerNode.submitButton.click(); @@ -218,7 +222,8 @@ test.describe('Configuration page to configure the Trigger node', () => { const collectionTriggerNode = new CollectionTriggerNode(page, workFlowName, triggerNodeCollectionName); await collectionTriggerNode.nodeConfigure.click(); await collectionTriggerNode.collectionDropDown.click(); - await page.getByRole('option', { name: triggerNodeCollectionDisplayName }).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); await collectionTriggerNode.triggerOnDropdown.click(); await page.getByText('After record added or updated', { exact: true }).click(); // 设置触发器过滤条件 @@ -285,7 +290,8 @@ test.describe('Configuration page to configure the Trigger node', () => { const collectionTriggerNode = new CollectionTriggerNode(page, workFlowName, triggerNodeCollectionName); await collectionTriggerNode.nodeConfigure.click(); await collectionTriggerNode.collectionDropDown.click(); - await page.getByRole('option', { name: triggerNodeCollectionDisplayName }).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); await collectionTriggerNode.triggerOnDropdown.click(); await page.getByText('After record updated', { exact: true }).click(); // 设置触发器过滤条件 @@ -723,7 +729,12 @@ test.describe('Configuration page copy to new version', () => { // 3、预期结果:新版本工作流配置内容同旧版本一样 const collectionTriggerNode = new CollectionTriggerNode(page, workFlowName, triggerNodeCollectionName); await collectionTriggerNode.nodeConfigure.click(); - await expect(page.getByRole('button', { name: triggerNodeCollectionDisplayName })).toBeVisible(); + // await expect(page.getByRole('button', { name: `Main / ${triggerNodeCollectionDisplayName}` })).toBeVisible(); + await expect( + page + .getByLabel('block-item-DataSourceCollectionCascader-workflows-Collection') + .getByText(`Main / ${triggerNodeCollectionDisplayName}`), + ).toBeVisible(); // 4、后置处理:删除工作流 await apiDeleteWorkflow(workflowId); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/createRecordNode/CreateRecord.test.ts b/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/createRecordNode/CreateRecord.test.ts index 6c85b964e..21cdb3de6 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/createRecordNode/CreateRecord.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/createRecordNode/CreateRecord.test.ts @@ -73,7 +73,8 @@ test('Collection event add data trigger, single row text fields for common table const createRecordNodeId = await createRecordNode.node.locator('.workflow-node-id').innerText(); await createRecordNode.nodeConfigure.click(); await createRecordNode.collectionDropDown.click(); - await page.getByText(createNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: createNodeCollectionDisplayName }).click(); // 设置字段 await createRecordNode.addFieldsButton.click(); await page.getByRole('menuitem', { name: createNodeFieldDisplayName }).click(); @@ -172,7 +173,8 @@ test('Collection event add data trigger, normal table single line text field, se const createRecordNodeId = await createRecordNode.node.locator('.workflow-node-id').innerText(); await createRecordNode.nodeConfigure.click(); await createRecordNode.collectionDropDown.click(); - await page.getByText(createNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: createNodeCollectionDisplayName }).click(); // 设置字段 await createRecordNode.addFieldsButton.click(); await page.getByRole('menuitem', { name: createNodeFieldDisplayName }).click(); @@ -273,7 +275,8 @@ test('Collection event add data trigger, normal table integer field, set constan const createRecordNodeId = await createRecordNode.node.locator('.workflow-node-id').innerText(); await createRecordNode.nodeConfigure.click(); await createRecordNode.collectionDropDown.click(); - await page.getByText(createNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: createNodeCollectionDisplayName }).click(); // 设置字段 await createRecordNode.addFieldsButton.click(); await page.getByRole('menuitem', { name: createNodeFieldDisplayName }).click(); @@ -372,7 +375,8 @@ test('Collection event add data trigger, normal table integer field, set trigger const createRecordNodeId = await createRecordNode.node.locator('.workflow-node-id').innerText(); await createRecordNode.nodeConfigure.click(); await createRecordNode.collectionDropDown.click(); - await page.getByText(createNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: createNodeCollectionDisplayName }).click(); // 设置字段 await createRecordNode.addFieldsButton.click(); await page.getByRole('menuitem', { name: createNodeFieldDisplayName }).click(); @@ -473,7 +477,8 @@ test('Collection event add data trigger, normal table numeric field, set constan const createRecordNodeId = await createRecordNode.node.locator('.workflow-node-id').innerText(); await createRecordNode.nodeConfigure.click(); await createRecordNode.collectionDropDown.click(); - await page.getByText(createNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: createNodeCollectionDisplayName }).click(); // 设置字段 await createRecordNode.addFieldsButton.click(); await page.getByRole('menuitem', { name: createNodeFieldDisplayName }).click(); @@ -572,7 +577,8 @@ test('Collection event add data trigger, normal table numeric field, set trigger const createRecordNodeId = await createRecordNode.node.locator('.workflow-node-id').innerText(); await createRecordNode.nodeConfigure.click(); await createRecordNode.collectionDropDown.click(); - await page.getByText(createNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: createNodeCollectionDisplayName }).click(); // 设置字段 await createRecordNode.addFieldsButton.click(); await page.getByRole('menuitem', { name: createNodeFieldDisplayName }).click(); @@ -673,7 +679,8 @@ test('Collection event add data trigger, normal table dropdown radio field, set const createRecordNodeId = await createRecordNode.node.locator('.workflow-node-id').innerText(); await createRecordNode.nodeConfigure.click(); await createRecordNode.collectionDropDown.click(); - await page.getByText(createNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: createNodeCollectionDisplayName }).click(); // 设置字段 await createRecordNode.addFieldsButton.click(); await page.getByRole('menuitem', { name: createNodeFieldDisplayName }).click(); @@ -769,7 +776,8 @@ test('Collection event add data trigger, normal table dropdown radio field, set const createRecordNodeId = await createRecordNode.node.locator('.workflow-node-id').innerText(); await createRecordNode.nodeConfigure.click(); await createRecordNode.collectionDropDown.click(); - await page.getByText(createNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: createNodeCollectionDisplayName }).click(); // 设置字段 await createRecordNode.addFieldsButton.click(); await page.getByRole('menuitem', { name: createNodeFieldDisplayName }).click(); @@ -870,7 +878,8 @@ test('Collection event add data trigger, normal table dropdown radio fields, set const createRecordNodeId = await createRecordNode.node.locator('.workflow-node-id').innerText(); await createRecordNode.nodeConfigure.click(); await createRecordNode.collectionDropDown.click(); - await page.getByText(createNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: createNodeCollectionDisplayName }).click(); // 设置字段 await createRecordNode.addFieldsButton.click(); await page.getByRole('menuitem', { name: createNodeFieldDisplayName }).click(); @@ -967,7 +976,8 @@ test('Collection event add data trigger, normal table dropdown radio fields, set const createRecordNodeId = await createRecordNode.node.locator('.workflow-node-id').innerText(); await createRecordNode.nodeConfigure.click(); await createRecordNode.collectionDropDown.click(); - await page.getByText(createNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: createNodeCollectionDisplayName }).click(); // 设置字段 await createRecordNode.addFieldsButton.click(); await page.getByRole('menuitem', { name: createNodeFieldDisplayName }).click(); @@ -1068,7 +1078,8 @@ test('Collection event add data trigger, normal table date field, set constant d const createRecordNodeId = await createRecordNode.node.locator('.workflow-node-id').innerText(); await createRecordNode.nodeConfigure.click(); await createRecordNode.collectionDropDown.click(); - await page.getByText(createNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: createNodeCollectionDisplayName }).click(); // 设置字段 await createRecordNode.addFieldsButton.click(); await page.getByRole('menuitem', { name: createNodeFieldDisplayName }).click(); @@ -1169,7 +1180,8 @@ test('Collection event add data trigger, normal table date field, set trigger no const createRecordNodeId = await createRecordNode.node.locator('.workflow-node-id').innerText(); await createRecordNode.nodeConfigure.click(); await createRecordNode.collectionDropDown.click(); - await page.getByText(createNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: createNodeCollectionDisplayName }).click(); // 设置字段 await createRecordNode.addFieldsButton.click(); await page.getByRole('menuitem', { name: createNodeFieldDisplayName }).click(); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/deleteRecordNode/DeleteRecord.test.ts b/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/deleteRecordNode/DeleteRecord.test.ts index e40cb2755..c43bb7018 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/deleteRecordNode/DeleteRecord.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/deleteRecordNode/DeleteRecord.test.ts @@ -78,7 +78,8 @@ test('Collection event add data trigger, filter single line text field not null, const deleteRecordNode = new DeleteRecordNode(page, deleteRecordNodeName); await deleteRecordNode.nodeConfigure.click(); await deleteRecordNode.collectionDropDown.click(); - await page.getByText(deleteNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: deleteNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page.getByLabel('block-item-Filter-workflows-Filter').getByRole('button', { name: 'Select field' }).click(); @@ -172,7 +173,8 @@ test('Collection event add data trigger, filter single line text field is trigge const deleteRecordNode = new DeleteRecordNode(page, deleteRecordNodeName); await deleteRecordNode.nodeConfigure.click(); await deleteRecordNode.collectionDropDown.click(); - await page.getByText(deleteNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: deleteNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page.getByLabel('block-item-Filter-workflows-Filter').getByRole('button', { name: 'Select field' }).click(); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/queryRecordNode/QueryRecord.test.ts b/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/queryRecordNode/QueryRecord.test.ts index f7c2aa531..2ef724a9a 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/queryRecordNode/QueryRecord.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/queryRecordNode/QueryRecord.test.ts @@ -61,7 +61,8 @@ test('Collection event add data trigger, no filter no sort query common table 1 const queryRecordNodeId = await queryRecordNode.node.locator('.workflow-node-id').innerText(); await queryRecordNode.nodeConfigure.click(); await queryRecordNode.collectionDropDown.click(); - await page.getByRole('option', { name: triggerNodeCollectionDisplayName }).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); await queryRecordNode.submitButton.click(); // 2、测试步骤:添加数据触发工作流 @@ -138,7 +139,8 @@ test('Collection event add data trigger, no filtering and no sorting, query comm const queryRecordNodeId = await queryRecordNode.node.locator('.workflow-node-id').innerText(); await queryRecordNode.nodeConfigure.click(); await queryRecordNode.collectionDropDown.click(); - await page.getByRole('option', { name: triggerNodeCollectionDisplayName }).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); await queryRecordNode.allowMultipleDataBoxesForResults.check(); await expect(queryRecordNode.pageNumberEditBox).toHaveValue('1'); await expect(queryRecordNode.pageSizeEditBox).toHaveValue('20'); @@ -216,7 +218,8 @@ test('Collection event add data trigger, no filter ID ascending, query common ta const queryRecordNodeId = await queryRecordNode.node.locator('.workflow-node-id').innerText(); await queryRecordNode.nodeConfigure.click(); await queryRecordNode.collectionDropDown.click(); - await page.getByRole('option', { name: triggerNodeCollectionDisplayName }).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); await queryRecordNode.allowMultipleDataBoxesForResults.check(); // 设置排序条件 await queryRecordNode.addSortFieldsButton.click(); @@ -324,7 +327,8 @@ test('Collection event add data trigger, no filter ID descending, query common t const queryRecordNodeId = await queryRecordNode.node.locator('.workflow-node-id').innerText(); await queryRecordNode.nodeConfigure.click(); await queryRecordNode.collectionDropDown.click(); - await page.getByRole('option', { name: triggerNodeCollectionDisplayName }).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); await queryRecordNode.allowMultipleDataBoxesForResults.check(); // 设置排序条件 await queryRecordNode.addSortFieldsButton.click(); @@ -433,7 +437,8 @@ test('Collection event add data trigger, no filtering and no sorting, query mult const queryRecordNodeId = await queryRecordNode.node.locator('.workflow-node-id').innerText(); await queryRecordNode.nodeConfigure.click(); await queryRecordNode.collectionDropDown.click(); - await page.getByRole('option', { name: triggerNodeCollectionDisplayName }).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); await queryRecordNode.allowMultipleDataBoxesForResults.check(); await expect(queryRecordNode.pageNumberEditBox).toHaveValue('1'); await expect(queryRecordNode.pageSizeEditBox).toHaveValue('20'); @@ -536,7 +541,8 @@ test('Collection event add data trigger, no filtering and no sorting, query the const queryRecordNodeId = await queryRecordNode.node.locator('.workflow-node-id').innerText(); await queryRecordNode.nodeConfigure.click(); await queryRecordNode.collectionDropDown.click(); - await page.getByRole('option', { name: triggerNodeCollectionDisplayName }).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); // await queryRecordNode.allowMultipleDataBoxesForResults.check(); await expect(queryRecordNode.pageNumberEditBox).toHaveValue('1'); await expect(queryRecordNode.pageSizeEditBox).toHaveValue('20'); @@ -641,7 +647,8 @@ test('Collection event add data trigger, no filtering and no sorting, query the const queryRecordNodeId = await queryRecordNode.node.locator('.workflow-node-id').innerText(); await queryRecordNode.nodeConfigure.click(); await queryRecordNode.collectionDropDown.click(); - await page.getByRole('option', { name: triggerNodeCollectionDisplayName }).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: triggerNodeCollectionDisplayName }).click(); await queryRecordNode.allowMultipleDataBoxesForResults.check(); await expect(queryRecordNode.pageNumberEditBox).toHaveValue('1'); await expect(queryRecordNode.pageSizeEditBox).toHaveValue('20'); @@ -769,7 +776,8 @@ test('Collection event add data trigger, filter to meet all conditions (status_s const queryRecordNodeId = await queryRecordNode.node.locator('.workflow-node-id').innerText(); await queryRecordNode.nodeConfigure.click(); await queryRecordNode.collectionDropDown.click(); - await page.getByText(queryNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: queryNodeCollectionDisplayName }).click(); // await queryRecordNode.allowMultipleDataBoxesForResults.check(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); @@ -884,7 +892,8 @@ test('Collection event add data trigger, filter to satisfy any condition (status const queryRecordNodeId = await queryRecordNode.node.locator('.workflow-node-id').innerText(); await queryRecordNode.nodeConfigure.click(); await queryRecordNode.collectionDropDown.click(); - await page.getByText(queryNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: queryNodeCollectionDisplayName }).click(); await queryRecordNode.allowMultipleDataBoxesForResults.check(); // 设置过滤条件 await page.getByTestId('filter-select-all-or-any').click(); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/updateRecordNode/updateInABatch.test.ts b/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/updateRecordNode/updateInABatch.test.ts index e683c4366..ebbcd1c2e 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/updateRecordNode/updateInABatch.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/updateRecordNode/updateInABatch.test.ts @@ -86,7 +86,8 @@ test('Collection event add data trigger, filter single line text field not empty const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page @@ -240,7 +241,8 @@ test('Collection event add data trigger, filter single line text field not empty const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page @@ -401,7 +403,8 @@ test('Collection event add data trigger, filter multi-line text field not empty, const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page @@ -555,7 +558,8 @@ test('Collection event add data trigger, filter multiline text field not empty, const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page @@ -710,7 +714,8 @@ test('Collection event add data trigger, filter integer field not null, common t const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page @@ -860,7 +865,8 @@ test('Collection event add data trigger, filter integer field not empty, common const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page @@ -1014,7 +1020,8 @@ test('Collection event add data trigger, filter numeric field not null, common t const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page @@ -1163,7 +1170,8 @@ test('Collection event add data trigger, filter numeric field not empty, common const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page @@ -1315,7 +1323,8 @@ test('Collection event add data trigger, filter dropdown radio field not null, c const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page @@ -1464,7 +1473,8 @@ test('Collection event add data trigger, filter dropdown radio field not empty, const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page @@ -1619,7 +1629,8 @@ test('Collection event add data trigger, filter dropdown radio fields not null, const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page @@ -1772,7 +1783,8 @@ test('Collection event add data trigger, filter dropdown radio fields not empty, const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page @@ -1931,7 +1943,8 @@ test('Collection event add data trigger, filter date field not null, common tabl const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page @@ -2086,7 +2099,8 @@ test('Collection event add data trigger, filter date field not empty, common tab const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); await page diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/updateRecordNode/updateOneByOne.test.ts b/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/updateRecordNode/updateOneByOne.test.ts index d0a6a3ef9..9fb74ace6 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/updateRecordNode/updateOneByOne.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/__e2e__/updateRecordNode/updateOneByOne.test.ts @@ -86,7 +86,8 @@ test('Collection event add data trigger, filter single line text field not empty const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); await updateRecordNode.articleByArticleUpdateModeRadio.click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); @@ -241,7 +242,8 @@ test('Collection event add data trigger, filter single line text field not empty const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); await updateRecordNode.articleByArticleUpdateModeRadio.click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); @@ -403,7 +405,8 @@ test('Collection event add data trigger, filter multi-line text field not empty, const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); await updateRecordNode.articleByArticleUpdateModeRadio.click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); @@ -558,7 +561,8 @@ test('Collection event add data trigger, filter multiline text field not empty, const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); await updateRecordNode.articleByArticleUpdateModeRadio.click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); @@ -714,7 +718,8 @@ test('Collection event add data trigger, filter integer field not null, common t const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); await updateRecordNode.articleByArticleUpdateModeRadio.click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); @@ -865,7 +870,8 @@ test('Collection event add data trigger, filter integer field not empty, common const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); await updateRecordNode.articleByArticleUpdateModeRadio.click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); @@ -1020,7 +1026,8 @@ test('Collection event add data trigger, filter numeric field not null, common t const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); await updateRecordNode.articleByArticleUpdateModeRadio.click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); @@ -1170,7 +1177,8 @@ test('Collection event add data trigger, filter numeric field not empty, common const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); await updateRecordNode.articleByArticleUpdateModeRadio.click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); @@ -1323,7 +1331,8 @@ test('Collection event add data trigger, filter dropdown radio field not null, c const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); await updateRecordNode.articleByArticleUpdateModeRadio.click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); @@ -1473,7 +1482,8 @@ test('Collection event add data trigger, filter dropdown radio field not empty, const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); await updateRecordNode.articleByArticleUpdateModeRadio.click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); @@ -1629,7 +1639,8 @@ test('Collection event add data trigger, filter dropdown radio fields not null, const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); await updateRecordNode.articleByArticleUpdateModeRadio.click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); @@ -1783,7 +1794,8 @@ test('Collection event add data trigger, filter dropdown radio fields not empty, const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); await updateRecordNode.articleByArticleUpdateModeRadio.click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); @@ -1943,7 +1955,8 @@ test('Collection event add data trigger, filter date field not null, common tabl const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); await updateRecordNode.articleByArticleUpdateModeRadio.click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); @@ -2099,7 +2112,8 @@ test('Collection event add data trigger, filter date field not empty, common tab const updateRecordNodeId = await updateRecordNode.node.locator('.workflow-node-id').innerText(); await updateRecordNode.nodeConfigure.click(); await updateRecordNode.collectionDropDown.click(); - await page.getByText(updateNodeCollectionDisplayName).click(); + await page.getByRole('menuitemcheckbox', { name: 'Main right' }).click(); + await page.getByRole('menuitemcheckbox', { name: updateNodeCollectionDisplayName }).click(); await updateRecordNode.articleByArticleUpdateModeRadio.click(); // 设置过滤条件 await page.getByText('Add condition', { exact: true }).click(); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/components/CollectionBlockInitializer.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/components/CollectionBlockInitializer.tsx index 72bc3b079..3359b905d 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/components/CollectionBlockInitializer.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/components/CollectionBlockInitializer.tsx @@ -1,9 +1,11 @@ import React from 'react'; +import { uid } from '@formily/shared'; import { CollectionProvider_deprecated, SchemaInitializerItem, SchemaInitializerItemType, + parseCollectionName, useCollectionManager_deprecated, useRecordCollectionDataSourceItems, useSchemaInitializer, @@ -13,23 +15,29 @@ import { import { traverseSchema } from '../utils'; -function InnerCollectionBlockInitializer({ collection, dataSource, ...props }) { +function InnerCollectionBlockInitializer({ collection, dataPath, ...props }) { const { insert } = useSchemaInitializer(); const { getTemplateSchemaByMode } = useSchemaTemplateManager(); const { getCollection } = useCollectionManager_deprecated(); const items = useRecordCollectionDataSourceItems('FormItem') as SchemaInitializerItemType[]; - const resolvedCollection = getCollection(collection); + let resolvedCollection; + if (typeof collection === 'string') { + const [dataSourceName, collectionName] = parseCollectionName(collection); + resolvedCollection = getCollection(collectionName, dataSourceName); + } else { + resolvedCollection = collection; + } async function onConfirm({ item }) { const template = item.template ? await getTemplateSchemaByMode(item) : null; const result = { type: 'void', - name: resolvedCollection.name, + name: uid(), title: resolvedCollection.title, 'x-decorator': 'DetailsBlockProvider', 'x-decorator-props': { collection, - dataSource, + dataPath, }, 'x-component': 'CardItem', 'x-component-props': { @@ -66,11 +74,20 @@ function InnerCollectionBlockInitializer({ collection, dataSource, ...props }) { return ; } -export function CollectionBlockInitializer() { +export function CollectionBlockInitializer(props) { const itemConfig = useSchemaInitializerItem(); + const sourceCollection = props?.collection ?? itemConfig.collection; + let dataSource, collection; + if (typeof sourceCollection === 'string') { + const parsed = parseCollectionName(sourceCollection); + dataSource = parsed[0]; + collection = parsed[1]; + } else { + collection = sourceCollection; + } return ( - - + + ); } diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/components/CollectionFieldset.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/components/CollectionFieldset.tsx index e98aaf13c..3c9d7bd3a 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/components/CollectionFieldset.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/components/CollectionFieldset.tsx @@ -6,6 +6,7 @@ import { SchemaComponent, Variable, css, + parseCollectionName, useCollectionManager_deprecated, useCompile, useToken, @@ -21,7 +22,8 @@ function AssociationInput(props) { const { path } = useField(); const fieldName = path.segments[path.segments.length - 1] as string; const { values: config } = useForm(); - const fields = getCollectionFields(config?.collection); + const [dataSourceName, collectionName] = parseCollectionName(config?.collection); + const fields = getCollectionFields(collectionName, dataSourceName); const { type } = fields.find((item) => item.name === fieldName); const value = Array.isArray(props.value) ? props.value.join(',') : props.value; @@ -39,11 +41,11 @@ const CollectionFieldSet = observer( const { t } = useTranslation(); const compile = useCompile(); const form = useForm(); - const { getCollection, getCollectionFields } = useCollectionManager_deprecated(); + const { getCollectionFields } = useCollectionManager_deprecated(); const scope = useWorkflowVariableOptions(); const { values: config } = form; - const collectionName = config?.collection; - const collectionFields = getCollectionFields(collectionName).filter((field) => field.uiSchema); + const [dataSourceName, collectionName] = parseCollectionName(config?.collection); + const collectionFields = getCollectionFields(collectionName, dataSourceName).filter((field) => field.uiSchema); const fields = filter ? collectionFields.filter(filter.bind(config)) : collectionFields; const unassignedFields = useMemo(() => fields.filter((field) => !value || !(field.name in value)), [fields, value]); @@ -79,7 +81,7 @@ const CollectionFieldSet = observer( `} > {fields.length ? ( - + {fields .filter((field) => value && field.name in value) .map((field) => { diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/instruction/DetailsBlockProvider.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/components/DetailsBlockProvider.tsx similarity index 71% rename from packages/plugins/@nocobase/plugin-workflow-manual/src/client/instruction/DetailsBlockProvider.tsx rename to packages/plugins/@nocobase/plugin-workflow/src/client/components/DetailsBlockProvider.tsx index 434c2fb89..b378de71c 100644 --- a/packages/plugins/@nocobase/plugin-workflow-manual/src/client/instruction/DetailsBlockProvider.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/components/DetailsBlockProvider.tsx @@ -1,19 +1,21 @@ +import React, { useMemo, useRef } from 'react'; import { createForm } from '@formily/core'; import { useField } from '@formily/react'; +import { get } from 'lodash'; import { BlockRequestContext_deprecated, CollectionProvider_deprecated, FormBlockContext, RecordProvider, + parseCollectionName, useAPIClient, useAssociationNames, useBlockRequestContext, } from '@nocobase/client'; -import { useFlowContext } from '@nocobase/plugin-workflow/client'; -import { parse } from '@nocobase/utils/client'; -import React, { useMemo, useRef } from 'react'; -function useFlowContextData(dataSource) { +import { useFlowContext } from '../FlowContext'; + +function useFlowContextData(dataPath) { const { execution, nodes } = useFlowContext(); const nodesKeyMap = useMemo(() => { @@ -31,16 +33,24 @@ function useFlowContextData(dataSource) { [execution?.context, execution?.jobs, nodesKeyMap], ); - const result = useMemo(() => parse(dataSource)(data), [data, dataSource]); + const result = useMemo(() => get(data, dataPath), [data, dataPath]); return result; } -export function DetailsBlockProvider(props) { +export function DetailsBlockProvider({ collection, dataPath, children }) { const field = useField(); const formBlockRef = useRef(null); const { getAssociationAppends } = useAssociationNames(); const { appends, updateAssociationValues } = getAssociationAppends(); - const values = useFlowContextData(props.dataSource); + const values = useFlowContextData(dataPath); + let dataSourceName, resolvedCollection; + if (typeof collection === 'string') { + const parsed = parseCollectionName(collection); + dataSourceName = parsed[0]; + resolvedCollection = parsed[1]; + } else { + resolvedCollection = collection; + } const form = useMemo( () => @@ -61,11 +71,11 @@ export function DetailsBlockProvider(props) { }, }; const api = useAPIClient(); - const resource = api.resource(props.collection); + const resource = api.resource(resolvedCollection); const __parent = useBlockRequestContext(); return ( - + - {props.children} + {children} diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/components/FieldsSelect.tsx b/packages/plugins/@nocobase/plugin-workflow/src/client/components/FieldsSelect.tsx index 358766af8..c8b684e96 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/components/FieldsSelect.tsx +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/components/FieldsSelect.tsx @@ -2,7 +2,7 @@ import { observer, useForm } from '@formily/react'; import { Select } from 'antd'; import React from 'react'; -import { useCollectionManager_deprecated, useCompile } from '@nocobase/client'; +import { parseCollectionName, useCollectionManager_deprecated, useCompile } from '@nocobase/client'; function defaultFilter() { return true; @@ -14,7 +14,8 @@ export const FieldsSelect = observer( const compile = useCompile(); const { getCollectionFields } = useCollectionManager_deprecated(); const { values } = useForm(); - const fields = getCollectionFields(values?.collection); + const [dataSourceName, collectionName] = parseCollectionName(values?.collection); + const fields = getCollectionFields(collectionName, dataSourceName); return ( { + form.setValuesIn('unitConversionType', value); + }} + > + {t('Multiply by')} + {t('Divide by')} + + ); +}; + +export const SchemaSettingsNumberFormat = function NumberFormatConfig(props: { fieldSchema: Schema }) { + const { fieldSchema } = props; + const field = useField(); + const { dn } = useDesignable(); + const { t } = useTranslation(); + const { getCollectionJoinField } = useCollectionManager_deprecated(); + const collectionField = getCollectionJoinField(fieldSchema?.['x-collection-field']) || {}; + const { formatStyle, unitConversion, unitConversionType, separator, step, addonBefore, addonAfter } = + fieldSchema['x-component-props'] || {}; + const { step: prescition } = collectionField?.uiSchema['x-component-props'] || {}; + + return ( + , + }, + }, + separator: { + type: 'string', + default: separator || '0,0.00', + enum: [ + { + value: '0,0.00', + label: t('100,000.00'), + }, + { + value: '0.0,00', + label: t('100.000,00'), + }, + { + value: '0 0,00', + label: t('100 000.00'), + }, + { + value: '0.00', + label: t('100000.00'), + }, + ], + 'x-decorator': 'FormItem', + 'x-component': 'Select', + title: "{{t('Separator')}}", + }, + step: { + type: 'string', + title: '{{t("Precision")}}', + 'x-component': 'Select', + 'x-decorator': 'FormItem', + default: step || prescition || '1', + enum: [ + { value: '1', label: '1' }, + { value: '0.1', label: '1.0' }, + { value: '0.01', label: '1.00' }, + { value: '0.001', label: '1.000' }, + { value: '0.0001', label: '1.0000' }, + { value: '0.00001', label: '1.00000' }, + ], + }, + addonBefore: { + type: 'string', + title: '{{t("Prefix")}}', + 'x-component': 'Input', + 'x-decorator': 'FormItem', + default: addonBefore, + }, + addonAfter: { + type: 'string', + title: '{{t("Suffix")}}', + 'x-component': 'Input', + 'x-decorator': 'FormItem', + default: addonAfter, + }, + }, + } as ISchema + } + onSubmit={(data) => { + const schema = { + ['x-uid']: fieldSchema['x-uid'], + }; + schema['x-component-props'] = fieldSchema['x-component-props'] || {}; + fieldSchema['x-component-props'] = { + ...(fieldSchema['x-component-props'] || {}), + ...data, + }; + schema['x-component-props'] = fieldSchema['x-component-props']; + field.componentProps = fieldSchema['x-component-props']; + //子表格/表格区块 + const parts = (field.path.entire as string).split('.'); + parts.pop(); + const modifiedString = parts.join('.'); + field.query(`${modifiedString}.*[0:].${fieldSchema.name}`).forEach((f) => { + if (f.props.name === fieldSchema.name) { + f.setComponentProps({ ...data }); + } + }); + dn.emit('patch', { + schema, + }); + dn.refresh(); + }} + /> + ); +}; diff --git a/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts b/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts index 7272480d3..e9c608b05 100644 --- a/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts +++ b/packages/core/client/src/schema-settings/SchemaSettingsPlugin.ts @@ -44,6 +44,7 @@ import { selectComponentFieldSettings } from '../modules/fields/component/Select import { subTablePopoverComponentFieldSettings } from '../modules/fields/component/SubTable/subTablePopoverComponentFieldSettings'; import { tagComponentFieldSettings } from '../modules/fields/component/Tag/tagComponentFieldSettings'; import { unixTimestampComponentFieldSettings } from '../modules/fields/component/UnixTimestamp/unixTimestampComponentFieldSettings'; +import { inputNumberComponentFieldSettings } from '../modules/fields/component/InputNumber/inputNumberComponentFieldSettings'; export class SchemaSettingsPlugin extends Plugin { async load() { @@ -94,6 +95,7 @@ export class SchemaSettingsPlugin extends Plugin { this.schemaSettingsManager.add(subTablePopoverComponentFieldSettings); this.schemaSettingsManager.add(datePickerComponentFieldSettings); this.schemaSettingsManager.add(unixTimestampComponentFieldSettings); + this.schemaSettingsManager.add(inputNumberComponentFieldSettings); this.schemaSettingsManager.add(fileManagerComponentFieldSettings); this.schemaSettingsManager.add(tagComponentFieldSettings); From 339822241f2f641656f64107318b793d63d0b2c9 Mon Sep 17 00:00:00 2001 From: chenos Date: Tue, 26 Mar 2024 17:25:06 +0800 Subject: [PATCH 26/36] =?UTF-8?q?chore(versions):=20=F0=9F=98=8A=20publish?= =?UTF-8?q?=20v0.20.0-alpha.17?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lerna.json | 2 +- packages/core/acl/package.json | 6 +- packages/core/actions/package.json | 8 +- packages/core/app/package.json | 10 +- packages/core/auth/package.json | 12 +- packages/core/build/package.json | 2 +- packages/core/cache/package.json | 2 +- packages/core/cli/package.json | 6 +- packages/core/client/package.json | 8 +- .../core/create-nocobase-app/package.json | 2 +- .../core/data-source-manager/package.json | 12 +- packages/core/database/package.json | 6 +- packages/core/devtools/package.json | 8 +- packages/core/evaluators/package.json | 4 +- packages/core/logger/package.json | 2 +- packages/core/resourcer/package.json | 4 +- packages/core/sdk/package.json | 2 +- packages/core/server/package.json | 26 ++-- packages/core/telemetry/package.json | 4 +- packages/core/test/package.json | 4 +- packages/core/utils/package.json | 2 +- .../plugins/@nocobase/plugin-acl/package.json | 2 +- .../plugin-action-bulk-edit/package.json | 2 +- .../plugin-action-bulk-update/package.json | 2 +- .../plugin-action-duplicate/package.json | 2 +- .../plugin-action-print/package.json | 2 +- .../@nocobase/plugin-api-doc/package.json | 2 +- .../@nocobase/plugin-api-keys/package.json | 2 +- .../@nocobase/plugin-audit-logs/package.json | 2 +- .../@nocobase/plugin-auth/package.json | 2 +- .../plugin-backup-restore/package.json | 2 +- .../@nocobase/plugin-calendar/package.json | 2 +- .../plugins/@nocobase/plugin-cas/package.json | 2 +- .../@nocobase/plugin-charts/package.json | 2 +- .../plugin-china-region/package.json | 2 +- .../@nocobase/plugin-client/package.json | 2 +- .../plugin-collection-manager/package.json | 2 +- .../plugin-custom-request/package.json | 2 +- .../plugin-data-source-manager/package.json | 2 +- .../plugin-data-visualization/package.json | 2 +- .../plugin-disable-pm-add/package.json | 2 +- .../plugin-error-handler/package.json | 2 +- .../plugin-excel-formula-field/package.json | 2 +- .../@nocobase/plugin-export/package.json | 2 +- .../plugin-file-manager/package.json | 2 +- .../plugin-formula-field/package.json | 2 +- .../@nocobase/plugin-gantt/package.json | 2 +- .../package.json | 2 +- .../plugin-iframe-block/package.json | 2 +- .../@nocobase/plugin-import/package.json | 2 +- .../@nocobase/plugin-kanban/package.json | 2 +- .../package.json | 2 +- .../@nocobase/plugin-logger/package.json | 2 +- .../plugins/@nocobase/plugin-map/package.json | 2 +- .../plugin-math-formula-field/package.json | 2 +- .../plugin-mobile-client/package.json | 2 +- .../plugin-mock-collections/package.json | 2 +- .../plugin-multi-app-manager/package.json | 2 +- .../package.json | 2 +- .../plugin-notifications/package.json | 2 +- .../@nocobase/plugin-oidc/package.json | 2 +- .../@nocobase/plugin-saml/package.json | 2 +- .../plugin-sample-hello/package.json | 2 +- .../plugin-sequence-field/package.json | 2 +- .../@nocobase/plugin-sms-auth/package.json | 2 +- .../plugin-snapshot-field/package.json | 2 +- .../plugin-system-settings/package.json | 2 +- .../plugin-theme-editor/package.json | 2 +- .../plugin-ui-routes-storage/package.json | 2 +- .../plugin-ui-schema-storage/package.json | 2 +- .../@nocobase/plugin-users/package.json | 2 +- .../plugin-verification/package.json | 2 +- .../package.json | 2 +- .../plugin-workflow-aggregate/package.json | 2 +- .../plugin-workflow-delay/package.json | 2 +- .../package.json | 2 +- .../plugin-workflow-loop/package.json | 2 +- .../plugin-workflow-manual/package.json | 2 +- .../plugin-workflow-parallel/package.json | 2 +- .../plugin-workflow-request/package.json | 2 +- .../plugin-workflow-sql/package.json | 2 +- .../plugin-workflow-test/package.json | 2 +- .../@nocobase/plugin-workflow/package.json | 4 +- packages/presets/nocobase/package.json | 114 +++++++++--------- 84 files changed, 186 insertions(+), 186 deletions(-) diff --git a/lerna.json b/lerna.json index ee82671c1..57a40e21d 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "npmClient": "yarn", "useWorkspaces": true, "npmClientArgs": ["--ignore-engines"], diff --git a/packages/core/acl/package.json b/packages/core/acl/package.json index 7bf58c829..ea10f05c7 100644 --- a/packages/core/acl/package.json +++ b/packages/core/acl/package.json @@ -1,13 +1,13 @@ { "name": "@nocobase/acl", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "", "license": "Apache-2.0", "main": "./lib/index.js", "types": "./lib/index.d.ts", "dependencies": { - "@nocobase/resourcer": "0.20.0-alpha.16", - "@nocobase/utils": "0.20.0-alpha.16", + "@nocobase/resourcer": "0.20.0-alpha.17", + "@nocobase/utils": "0.20.0-alpha.17", "minimatch": "^5.1.1" }, "repository": { diff --git a/packages/core/actions/package.json b/packages/core/actions/package.json index 0acaa14c2..6db4dba4d 100644 --- a/packages/core/actions/package.json +++ b/packages/core/actions/package.json @@ -1,14 +1,14 @@ { "name": "@nocobase/actions", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "", "license": "Apache-2.0", "main": "./lib/index.js", "types": "./lib/index.d.ts", "dependencies": { - "@nocobase/cache": "0.20.0-alpha.16", - "@nocobase/database": "0.20.0-alpha.16", - "@nocobase/resourcer": "0.20.0-alpha.16" + "@nocobase/cache": "0.20.0-alpha.17", + "@nocobase/database": "0.20.0-alpha.17", + "@nocobase/resourcer": "0.20.0-alpha.17" }, "repository": { "type": "git", diff --git a/packages/core/app/package.json b/packages/core/app/package.json index 8f26b0ce8..8b2572dfd 100644 --- a/packages/core/app/package.json +++ b/packages/core/app/package.json @@ -1,17 +1,17 @@ { "name": "@nocobase/app", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "", "license": "AGPL-3.0", "main": "./lib/index.js", "types": "./lib/index.d.ts", "dependencies": { - "@nocobase/database": "0.20.0-alpha.16", - "@nocobase/preset-nocobase": "0.20.0-alpha.16", - "@nocobase/server": "0.20.0-alpha.16" + "@nocobase/database": "0.20.0-alpha.17", + "@nocobase/preset-nocobase": "0.20.0-alpha.17", + "@nocobase/server": "0.20.0-alpha.17" }, "devDependencies": { - "@nocobase/client": "0.20.0-alpha.16" + "@nocobase/client": "0.20.0-alpha.17" }, "repository": { "type": "git", diff --git a/packages/core/auth/package.json b/packages/core/auth/package.json index 923c88627..9e2da52a3 100644 --- a/packages/core/auth/package.json +++ b/packages/core/auth/package.json @@ -1,16 +1,16 @@ { "name": "@nocobase/auth", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "", "license": "Apache-2.0", "main": "./lib/index.js", "types": "./lib/index.d.ts", "dependencies": { - "@nocobase/actions": "0.20.0-alpha.16", - "@nocobase/cache": "0.20.0-alpha.16", - "@nocobase/database": "0.20.0-alpha.16", - "@nocobase/resourcer": "0.20.0-alpha.16", - "@nocobase/utils": "0.20.0-alpha.16", + "@nocobase/actions": "0.20.0-alpha.17", + "@nocobase/cache": "0.20.0-alpha.17", + "@nocobase/database": "0.20.0-alpha.17", + "@nocobase/resourcer": "0.20.0-alpha.17", + "@nocobase/utils": "0.20.0-alpha.17", "@types/jsonwebtoken": "^8.5.8", "jsonwebtoken": "^8.5.1" }, diff --git a/packages/core/build/package.json b/packages/core/build/package.json index 93bf79c7c..8bee12eb9 100755 --- a/packages/core/build/package.json +++ b/packages/core/build/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/build", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "Library build tool based on rollup.", "main": "lib/index.js", "types": "./lib/index.d.ts", diff --git a/packages/core/cache/package.json b/packages/core/cache/package.json index c78c8c30a..7c911ae76 100644 --- a/packages/core/cache/package.json +++ b/packages/core/cache/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/cache", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "", "license": "Apache-2.0", "main": "./lib/index.js", diff --git a/packages/core/cli/package.json b/packages/core/cli/package.json index 0436cd394..b3af7a018 100644 --- a/packages/core/cli/package.json +++ b/packages/core/cli/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/cli", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "", "license": "Apache-2.0", "main": "./src/index.js", @@ -8,7 +8,7 @@ "nocobase": "./bin/index.js" }, "dependencies": { - "@nocobase/app": "0.20.0-alpha.16", + "@nocobase/app": "0.20.0-alpha.17", "@types/fs-extra": "^11.0.1", "@umijs/utils": "3.5.20", "chalk": "^4.1.1", @@ -25,7 +25,7 @@ "tsx": "^4.6.2" }, "devDependencies": { - "@nocobase/devtools": "0.20.0-alpha.16" + "@nocobase/devtools": "0.20.0-alpha.17" }, "repository": { "type": "git", diff --git a/packages/core/client/package.json b/packages/core/client/package.json index 381e4413b..db5cb1e96 100644 --- a/packages/core/client/package.json +++ b/packages/core/client/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/client", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "Apache-2.0", "main": "lib/index.js", "module": "es/index.mjs", @@ -26,9 +26,9 @@ "@formily/reactive-react": "^2.2.27", "@formily/shared": "^2.2.27", "@formily/validator": "^2.2.27", - "@nocobase/evaluators": "0.20.0-alpha.16", - "@nocobase/sdk": "0.20.0-alpha.16", - "@nocobase/utils": "0.20.0-alpha.16", + "@nocobase/evaluators": "0.20.0-alpha.17", + "@nocobase/sdk": "0.20.0-alpha.17", + "@nocobase/utils": "0.20.0-alpha.17", "ahooks": "^3.7.2", "antd": "^5.12.8", "antd-style": "3.4.5", diff --git a/packages/core/create-nocobase-app/package.json b/packages/core/create-nocobase-app/package.json index 9f4880024..6ca5b30c6 100755 --- a/packages/core/create-nocobase-app/package.json +++ b/packages/core/create-nocobase-app/package.json @@ -1,6 +1,6 @@ { "name": "create-nocobase-app", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "src/index.js", "license": "Apache-2.0", "dependencies": { diff --git a/packages/core/data-source-manager/package.json b/packages/core/data-source-manager/package.json index 2468156ea..b8a0ec0ba 100644 --- a/packages/core/data-source-manager/package.json +++ b/packages/core/data-source-manager/package.json @@ -1,16 +1,16 @@ { "name": "@nocobase/data-source-manager", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "", "license": "Apache-2.0", "main": "./lib/index.js", "types": "./lib/index.d.ts", "dependencies": { - "@nocobase/actions": "0.20.0-alpha.16", - "@nocobase/cache": "0.20.0-alpha.16", - "@nocobase/database": "0.20.0-alpha.16", - "@nocobase/resourcer": "0.20.0-alpha.16", - "@nocobase/utils": "0.20.0-alpha.16", + "@nocobase/actions": "0.20.0-alpha.17", + "@nocobase/cache": "0.20.0-alpha.17", + "@nocobase/database": "0.20.0-alpha.17", + "@nocobase/resourcer": "0.20.0-alpha.17", + "@nocobase/utils": "0.20.0-alpha.17", "@types/jsonwebtoken": "^8.5.8", "jsonwebtoken": "^8.5.1" }, diff --git a/packages/core/database/package.json b/packages/core/database/package.json index b2a7bffc8..1fb7420f8 100644 --- a/packages/core/database/package.json +++ b/packages/core/database/package.json @@ -1,13 +1,13 @@ { "name": "@nocobase/database", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "", "main": "./lib/index.js", "types": "./lib/index.d.ts", "license": "Apache-2.0", "dependencies": { - "@nocobase/logger": "0.20.0-alpha.16", - "@nocobase/utils": "0.20.0-alpha.16", + "@nocobase/logger": "0.20.0-alpha.17", + "@nocobase/utils": "0.20.0-alpha.17", "async-mutex": "^0.3.2", "chalk": "^4.1.1", "cron-parser": "4.4.0", diff --git a/packages/core/devtools/package.json b/packages/core/devtools/package.json index 9ffc8dbf7..4f3b70f96 100644 --- a/packages/core/devtools/package.json +++ b/packages/core/devtools/package.json @@ -1,13 +1,13 @@ { "name": "@nocobase/devtools", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "", "license": "Apache-2.0", "main": "./src/index.js", "dependencies": { - "@nocobase/build": "0.20.0-alpha.16", - "@nocobase/client": "0.20.0-alpha.16", - "@nocobase/test": "0.20.0-alpha.16", + "@nocobase/build": "0.20.0-alpha.17", + "@nocobase/client": "0.20.0-alpha.17", + "@nocobase/test": "0.20.0-alpha.17", "@types/koa": "^2.13.4", "@types/koa-bodyparser": "^4.3.4", "@types/lodash": "^4.14.177", diff --git a/packages/core/evaluators/package.json b/packages/core/evaluators/package.json index 17a742c85..3e31615fc 100644 --- a/packages/core/evaluators/package.json +++ b/packages/core/evaluators/package.json @@ -1,13 +1,13 @@ { "name": "@nocobase/evaluators", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "", "main": "./lib/index.js", "types": "./lib/index.d.ts", "license": "Apache-2.0", "dependencies": { "@formulajs/formulajs": "4.2.0", - "@nocobase/utils": "0.20.0-alpha.16", + "@nocobase/utils": "0.20.0-alpha.17", "mathjs": "^10.6.0" }, "repository": { diff --git a/packages/core/logger/package.json b/packages/core/logger/package.json index 2b7569ae8..041756f45 100644 --- a/packages/core/logger/package.json +++ b/packages/core/logger/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/logger", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "nocobase logging library", "license": "Apache-2.0", "main": "./lib/index.js", diff --git a/packages/core/resourcer/package.json b/packages/core/resourcer/package.json index 073199e0a..54abdc1b4 100644 --- a/packages/core/resourcer/package.json +++ b/packages/core/resourcer/package.json @@ -1,12 +1,12 @@ { "name": "@nocobase/resourcer", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "", "main": "./lib/index.js", "types": "./lib/index.d.ts", "license": "Apache-2.0", "dependencies": { - "@nocobase/utils": "0.20.0-alpha.16", + "@nocobase/utils": "0.20.0-alpha.17", "deepmerge": "^4.2.2", "koa-compose": "^4.1.0", "lodash": "^4.17.21", diff --git a/packages/core/sdk/package.json b/packages/core/sdk/package.json index c970e9150..db0d75dde 100644 --- a/packages/core/sdk/package.json +++ b/packages/core/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/sdk", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "Apache-2.0", "main": "lib/index.js", "types": "lib/index.d.ts", diff --git a/packages/core/server/package.json b/packages/core/server/package.json index e1e1d47ee..469dc19b5 100644 --- a/packages/core/server/package.json +++ b/packages/core/server/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/server", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "lib/index.js", "types": "./lib/index.d.ts", "license": "Apache-2.0", @@ -10,18 +10,18 @@ "@koa/cors": "^3.1.0", "@koa/multer": "^3.0.2", "@koa/router": "^9.4.0", - "@nocobase/acl": "0.20.0-alpha.16", - "@nocobase/actions": "0.20.0-alpha.16", - "@nocobase/auth": "0.20.0-alpha.16", - "@nocobase/cache": "0.20.0-alpha.16", - "@nocobase/data-source-manager": "0.20.0-alpha.16", - "@nocobase/database": "0.20.0-alpha.16", - "@nocobase/evaluators": "0.20.0-alpha.16", - "@nocobase/logger": "0.20.0-alpha.16", - "@nocobase/resourcer": "0.20.0-alpha.16", - "@nocobase/sdk": "0.20.0-alpha.16", - "@nocobase/telemetry": "0.20.0-alpha.16", - "@nocobase/utils": "0.20.0-alpha.16", + "@nocobase/acl": "0.20.0-alpha.17", + "@nocobase/actions": "0.20.0-alpha.17", + "@nocobase/auth": "0.20.0-alpha.17", + "@nocobase/cache": "0.20.0-alpha.17", + "@nocobase/data-source-manager": "0.20.0-alpha.17", + "@nocobase/database": "0.20.0-alpha.17", + "@nocobase/evaluators": "0.20.0-alpha.17", + "@nocobase/logger": "0.20.0-alpha.17", + "@nocobase/resourcer": "0.20.0-alpha.17", + "@nocobase/sdk": "0.20.0-alpha.17", + "@nocobase/telemetry": "0.20.0-alpha.17", + "@nocobase/utils": "0.20.0-alpha.17", "@types/decompress": "4.2.4", "@types/ini": "^1.3.31", "@types/koa-send": "^4.1.3", diff --git a/packages/core/telemetry/package.json b/packages/core/telemetry/package.json index 54adaf084..c205f1b0e 100644 --- a/packages/core/telemetry/package.json +++ b/packages/core/telemetry/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/telemetry", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "nocobase telemetry library", "license": "Apache-2.0", "main": "./lib/index.js", @@ -11,7 +11,7 @@ "directory": "packages/telemetry" }, "dependencies": { - "@nocobase/utils": "0.20.0-alpha.16", + "@nocobase/utils": "0.20.0-alpha.17", "@opentelemetry/api": "^1.7.0", "@opentelemetry/instrumentation": "^0.46.0", "@opentelemetry/resources": "^1.19.0", diff --git a/packages/core/test/package.json b/packages/core/test/package.json index 9f6698f72..7871a6401 100644 --- a/packages/core/test/package.json +++ b/packages/core/test/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/test", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "lib/index.js", "module": "./src/index.ts", "types": "./lib/index.d.ts", @@ -40,7 +40,7 @@ }, "dependencies": { "@faker-js/faker": "8.1.0", - "@nocobase/server": "0.20.0-alpha.16", + "@nocobase/server": "0.20.0-alpha.17", "@playwright/test": "^1.42.1", "@testing-library/react": "^14.0.0", "@testing-library/react-hooks": "^8.0.1", diff --git a/packages/core/utils/package.json b/packages/core/utils/package.json index 7ae13329c..50c910c32 100644 --- a/packages/core/utils/package.json +++ b/packages/core/utils/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/utils", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "lib/index.js", "types": "./lib/index.d.ts", "license": "Apache-2.0", diff --git a/packages/plugins/@nocobase/plugin-acl/package.json b/packages/plugins/@nocobase/plugin-acl/package.json index 43810e23a..6ac8bc994 100644 --- a/packages/plugins/@nocobase/plugin-acl/package.json +++ b/packages/plugins/@nocobase/plugin-acl/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "权限控制", "description": "Based on roles, resources, and actions, access control can precisely manage interface configuration permissions, data operation permissions, menu access permissions, and plugin permissions.", "description.zh-CN": "基于角色、资源和操作的权限控制,可以精确控制界面配置权限、数据操作权限、菜单访问权限、插件权限。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/acl", diff --git a/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json b/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json index 4f3367102..539686547 100644 --- a/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json +++ b/packages/plugins/@nocobase/plugin-action-bulk-edit/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-action-bulk-edit", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/action-bulk-edit", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-edit", diff --git a/packages/plugins/@nocobase/plugin-action-bulk-update/package.json b/packages/plugins/@nocobase/plugin-action-bulk-update/package.json index bc04a194e..631d952ac 100644 --- a/packages/plugins/@nocobase/plugin-action-bulk-update/package.json +++ b/packages/plugins/@nocobase/plugin-action-bulk-update/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-action-bulk-update", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/action-bulk-update", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-update", diff --git a/packages/plugins/@nocobase/plugin-action-duplicate/package.json b/packages/plugins/@nocobase/plugin-action-duplicate/package.json index 53711f695..791087636 100644 --- a/packages/plugins/@nocobase/plugin-action-duplicate/package.json +++ b/packages/plugins/@nocobase/plugin-action-duplicate/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-action-duplicate", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/action-duplicate", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-duplicate", diff --git a/packages/plugins/@nocobase/plugin-action-print/package.json b/packages/plugins/@nocobase/plugin-action-print/package.json index 60502edea..3fe51127a 100644 --- a/packages/plugins/@nocobase/plugin-action-print/package.json +++ b/packages/plugins/@nocobase/plugin-action-print/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-action-print", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/action-print", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-print", diff --git a/packages/plugins/@nocobase/plugin-api-doc/package.json b/packages/plugins/@nocobase/plugin-api-doc/package.json index cfe9bf961..fb771dec3 100644 --- a/packages/plugins/@nocobase/plugin-api-doc/package.json +++ b/packages/plugins/@nocobase/plugin-api-doc/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-api-doc", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "displayName": "API documentation", "displayName.zh-CN": "API 文档", "description": "An OpenAPI documentation generator for NocoBase HTTP API.", diff --git a/packages/plugins/@nocobase/plugin-api-keys/package.json b/packages/plugins/@nocobase/plugin-api-keys/package.json index 135276f4b..182ad552b 100644 --- a/packages/plugins/@nocobase/plugin-api-keys/package.json +++ b/packages/plugins/@nocobase/plugin-api-keys/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "认证:API 密钥", "description": "Allows users to use API key to access application's HTTP API", "description.zh-CN": "允许用户使用 API 密钥访问应用的 HTTP API", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/api-keys", diff --git a/packages/plugins/@nocobase/plugin-audit-logs/package.json b/packages/plugins/@nocobase/plugin-audit-logs/package.json index 0cb2d0f17..ae8af0f71 100644 --- a/packages/plugins/@nocobase/plugin-audit-logs/package.json +++ b/packages/plugins/@nocobase/plugin-audit-logs/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-audit-logs", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "displayName": "Audit logs (deprecated)", "displayName.zh-CN": "审计日志(废弃)", "description": "This plugin is deprecated. There will be a new audit log plugin in the future.", diff --git a/packages/plugins/@nocobase/plugin-auth/package.json b/packages/plugins/@nocobase/plugin-auth/package.json index d5a768b8a..c52486684 100644 --- a/packages/plugins/@nocobase/plugin-auth/package.json +++ b/packages/plugins/@nocobase/plugin-auth/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-auth", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/auth", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth", diff --git a/packages/plugins/@nocobase/plugin-backup-restore/package.json b/packages/plugins/@nocobase/plugin-backup-restore/package.json index a34a10876..dc3e49088 100644 --- a/packages/plugins/@nocobase/plugin-backup-restore/package.json +++ b/packages/plugins/@nocobase/plugin-backup-restore/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "应用的备份与还原", "description": "Backup and restore applications for scenarios such as application replication, migration, and upgrades.", "description.zh-CN": "备份和还原应用,可用于应用的复制、迁移、升级等场景。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/backup-restore", diff --git a/packages/plugins/@nocobase/plugin-calendar/package.json b/packages/plugins/@nocobase/plugin-calendar/package.json index f659ca4ef..30c9721e7 100644 --- a/packages/plugins/@nocobase/plugin-calendar/package.json +++ b/packages/plugins/@nocobase/plugin-calendar/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-calendar", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "displayName": "Calendar", "displayName.zh-CN": "日历", "description": "Provides callendar collection template and block for managing date data, typically for date/time related information such as events, appointments, tasks, and so on.", diff --git a/packages/plugins/@nocobase/plugin-cas/package.json b/packages/plugins/@nocobase/plugin-cas/package.json index a70837777..98071299d 100644 --- a/packages/plugins/@nocobase/plugin-cas/package.json +++ b/packages/plugins/@nocobase/plugin-cas/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "认证:CAS", "description": "CAS authentication.", "description.zh-CN": "通过 CAS 协议认证身份。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/auth-cas", diff --git a/packages/plugins/@nocobase/plugin-charts/package.json b/packages/plugins/@nocobase/plugin-charts/package.json index 841f844f9..c03c11de7 100644 --- a/packages/plugins/@nocobase/plugin-charts/package.json +++ b/packages/plugins/@nocobase/plugin-charts/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "图表(废弃)", "description": "The plugin has been deprecated, please use the data visualization plugin instead.", "description.zh-CN": "已废弃插件,请使用数据可视化插件代替。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "./dist/server/index.js", "license": "AGPL-3.0", "devDependencies": { diff --git a/packages/plugins/@nocobase/plugin-china-region/package.json b/packages/plugins/@nocobase/plugin-china-region/package.json index cbc042c7f..9708a553f 100644 --- a/packages/plugins/@nocobase/plugin-china-region/package.json +++ b/packages/plugins/@nocobase/plugin-china-region/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-china-region", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "displayName": "Administrative divisions of China", "displayName.zh-CN": "中国行政区划", "description": "Provides data and field type for administrative divisions of China.", diff --git a/packages/plugins/@nocobase/plugin-client/package.json b/packages/plugins/@nocobase/plugin-client/package.json index eb0dcb795..fe35b9fd9 100644 --- a/packages/plugins/@nocobase/plugin-client/package.json +++ b/packages/plugins/@nocobase/plugin-client/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "WEB 客户端", "description": "Provides a client interface for the NocoBase server", "description.zh-CN": "为 NocoBase 服务端提供客户端界面", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "./dist/server/index.js", "license": "AGPL-3.0", "devDependencies": { diff --git a/packages/plugins/@nocobase/plugin-collection-manager/package.json b/packages/plugins/@nocobase/plugin-collection-manager/package.json index ac89b9d98..9164115ce 100644 --- a/packages/plugins/@nocobase/plugin-collection-manager/package.json +++ b/packages/plugins/@nocobase/plugin-collection-manager/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "数据源:主数据库", "description": "NocoBase main database, supports relational databases such as MySQL, PostgreSQL, SQLite and so on.", "description.zh-CN": "NocoBase 主数据库,支持 MySQL、PostgreSQL、SQLite 等关系型数据库。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/data-source-main", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/data-source-main", diff --git a/packages/plugins/@nocobase/plugin-custom-request/package.json b/packages/plugins/@nocobase/plugin-custom-request/package.json index 619039b34..bdc32ba68 100644 --- a/packages/plugins/@nocobase/plugin-custom-request/package.json +++ b/packages/plugins/@nocobase/plugin-custom-request/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-custom-request", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/action-custom-request", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-custom-request", diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/package.json b/packages/plugins/@nocobase/plugin-data-source-manager/package.json index 686b5a986..0ab3e4806 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/package.json +++ b/packages/plugins/@nocobase/plugin-data-source-manager/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-data-source-manager", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "dist/server/index.js", "displayName": "Data source manager", "displayName.zh-CN": "数据源管理", diff --git a/packages/plugins/@nocobase/plugin-data-visualization/package.json b/packages/plugins/@nocobase/plugin-data-visualization/package.json index b8c6d9447..8838b7f05 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/package.json +++ b/packages/plugins/@nocobase/plugin-data-visualization/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-data-visualization", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "displayName": "Data visualization", "displayName.zh-CN": "数据可视化", "description": "Provides data visualization feature, including chart block and chart filter block, support line charts, area charts, bar charts and more than a dozen kinds of charts, you can also extend more chart types.", diff --git a/packages/plugins/@nocobase/plugin-disable-pm-add/package.json b/packages/plugins/@nocobase/plugin-disable-pm-add/package.json index de1c68f05..aaf90c631 100644 --- a/packages/plugins/@nocobase/plugin-disable-pm-add/package.json +++ b/packages/plugins/@nocobase/plugin-disable-pm-add/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-disable-pm-add", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "./dist/server/index.js", "peerDependencies": { "@nocobase/client": "0.x", diff --git a/packages/plugins/@nocobase/plugin-error-handler/package.json b/packages/plugins/@nocobase/plugin-error-handler/package.json index 6d51634b6..0609166cd 100644 --- a/packages/plugins/@nocobase/plugin-error-handler/package.json +++ b/packages/plugins/@nocobase/plugin-error-handler/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "错误处理器", "description": "Handling application errors and exceptions.", "description.zh-CN": "处理应用程序中的错误和异常。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "devDependencies": { diff --git a/packages/plugins/@nocobase/plugin-excel-formula-field/package.json b/packages/plugins/@nocobase/plugin-excel-formula-field/package.json index 9e7147ccf..03ddd392d 100644 --- a/packages/plugins/@nocobase/plugin-excel-formula-field/package.json +++ b/packages/plugins/@nocobase/plugin-excel-formula-field/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-excel-formula-field", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "", "license": "AGPL-3.0", "main": "./dist/server/index.js", diff --git a/packages/plugins/@nocobase/plugin-export/package.json b/packages/plugins/@nocobase/plugin-export/package.json index 1dd653535..6a647a256 100644 --- a/packages/plugins/@nocobase/plugin-export/package.json +++ b/packages/plugins/@nocobase/plugin-export/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "操作:导出记录", "description": "Export filtered records to excel, you can configure which fields to export.", "description.zh-CN": "导出筛选后的记录到 Excel 中,可以配置导出哪些字段。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/action-export", diff --git a/packages/plugins/@nocobase/plugin-file-manager/package.json b/packages/plugins/@nocobase/plugin-file-manager/package.json index a3b04163d..20e117c3d 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/package.json +++ b/packages/plugins/@nocobase/plugin-file-manager/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-file-manager", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "displayName": "File manager", "displayName.zh-CN": "文件管理器", "description": "Provides files storage services with files collection template and attachment field.", diff --git a/packages/plugins/@nocobase/plugin-formula-field/package.json b/packages/plugins/@nocobase/plugin-formula-field/package.json index c6402cd4e..898ec5df8 100644 --- a/packages/plugins/@nocobase/plugin-formula-field/package.json +++ b/packages/plugins/@nocobase/plugin-formula-field/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "数据表字段:公式", "description": "Configure and store the results of calculations between multiple field values in the same record, supporting both Math.js and Excel formula functions.", "description.zh-CN": "可以配置并存储同一条记录的多字段值之间的计算结果,支持 Math.js 和 Excel formula functions 两种引擎", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/field-formula", diff --git a/packages/plugins/@nocobase/plugin-gantt/package.json b/packages/plugins/@nocobase/plugin-gantt/package.json index c7075c4ce..26d104095 100644 --- a/packages/plugins/@nocobase/plugin-gantt/package.json +++ b/packages/plugins/@nocobase/plugin-gantt/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-gantt", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "displayName": "Block: Gantt", "displayName.zh-CN": "区块:甘特图", "description": "Provides Gantt block.", diff --git a/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json b/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json index e388677a4..666137456 100644 --- a/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json +++ b/packages/plugins/@nocobase/plugin-graph-collection-manager/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "可视化数据表管理", "description": "An ER diagram-like tool. Currently only the Master database is supported.", "description.zh-CN": "类似 ER 图的工具,目前只支持主数据库。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/graph-collection-manager", diff --git a/packages/plugins/@nocobase/plugin-iframe-block/package.json b/packages/plugins/@nocobase/plugin-iframe-block/package.json index 077e5d06c..51a0f115c 100644 --- a/packages/plugins/@nocobase/plugin-iframe-block/package.json +++ b/packages/plugins/@nocobase/plugin-iframe-block/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "区块:iframe", "description": "Create an iframe block on the page to embed and display external web pages or content.", "description.zh-CN": "在页面上创建和管理iframe,用于嵌入和展示外部网页或内容。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/block-iframe", diff --git a/packages/plugins/@nocobase/plugin-import/package.json b/packages/plugins/@nocobase/plugin-import/package.json index 38f7c5299..05dcda968 100644 --- a/packages/plugins/@nocobase/plugin-import/package.json +++ b/packages/plugins/@nocobase/plugin-import/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "操作:导入记录", "description": "Import records using excel templates. You can configure which fields to import and templates will be generated automatically.", "description.zh-CN": "使用 Excel 模板导入数据,可以配置导入哪些字段,自动生成模板。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/action-import", diff --git a/packages/plugins/@nocobase/plugin-kanban/package.json b/packages/plugins/@nocobase/plugin-kanban/package.json index d6cf4b3d2..8231d6b51 100644 --- a/packages/plugins/@nocobase/plugin-kanban/package.json +++ b/packages/plugins/@nocobase/plugin-kanban/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-kanban", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/block-kanban", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/block-kanban", diff --git a/packages/plugins/@nocobase/plugin-localization-management/package.json b/packages/plugins/@nocobase/plugin-localization-management/package.json index 5168fd3ee..0b97125dc 100644 --- a/packages/plugins/@nocobase/plugin-localization-management/package.json +++ b/packages/plugins/@nocobase/plugin-localization-management/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-localization-management", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/localization-management", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/localization-management", diff --git a/packages/plugins/@nocobase/plugin-logger/package.json b/packages/plugins/@nocobase/plugin-logger/package.json index 14374f437..e2c1eff6f 100644 --- a/packages/plugins/@nocobase/plugin-logger/package.json +++ b/packages/plugins/@nocobase/plugin-logger/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "日志", "description": "Server-side logs, mainly including API request logs and system runtime logs, and allows to package and download log files.", "description.zh-CN": "服务端日志,主要包括接口请求日志和系统运行日志,并支持打包和下载日志文件。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/logger", diff --git a/packages/plugins/@nocobase/plugin-map/package.json b/packages/plugins/@nocobase/plugin-map/package.json index 184e86704..6da8d793a 100644 --- a/packages/plugins/@nocobase/plugin-map/package.json +++ b/packages/plugins/@nocobase/plugin-map/package.json @@ -2,7 +2,7 @@ "name": "@nocobase/plugin-map", "displayName": "Block: Map", "displayName.zh-CN": "区块:地图", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "Map block, support Gaode map and Google map, you can also extend more map types.", "description.zh-CN": "地图区块,支持高德地图和 Google 地图,你也可以扩展更多地图类型。", "license": "AGPL-3.0", diff --git a/packages/plugins/@nocobase/plugin-math-formula-field/package.json b/packages/plugins/@nocobase/plugin-math-formula-field/package.json index 32f3ac9d7..25a9494b6 100644 --- a/packages/plugins/@nocobase/plugin-math-formula-field/package.json +++ b/packages/plugins/@nocobase/plugin-math-formula-field/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "数学公式字段", "description": "A powerful mathematical formula field plugin designed to provide flexible mathematical and formula calculation capabilities for databases or data management systems. It seamlessly integrates with various database systems such as MySQL, PostgreSQL, and offers an intuitive user interface for defining and executing mathematical formula fields.", "description.zh-CN": "一个功能强大的数学公式字段插件,旨在为数据库或数据管理系统提供灵活的数学计算和公式计算功能。它可以与各种数据库系统(如MySQL、PostgreSQL)无缝集成,并提供直观的用户界面来定义和执行数学公式字段。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "devDependencies": { diff --git a/packages/plugins/@nocobase/plugin-mobile-client/package.json b/packages/plugins/@nocobase/plugin-mobile-client/package.json index c9ce7e7ee..bf0cdebd5 100644 --- a/packages/plugins/@nocobase/plugin-mobile-client/package.json +++ b/packages/plugins/@nocobase/plugin-mobile-client/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-mobile-client", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/mobile-client", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/mobile-client", diff --git a/packages/plugins/@nocobase/plugin-mock-collections/package.json b/packages/plugins/@nocobase/plugin-mock-collections/package.json index 5c02f8866..de73377cf 100644 --- a/packages/plugins/@nocobase/plugin-mock-collections/package.json +++ b/packages/plugins/@nocobase/plugin-mock-collections/package.json @@ -2,7 +2,7 @@ "name": "@nocobase/plugin-mock-collections", "displayName": "mock-collections", "description": "mock-collections", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "./dist/server/index.js", "license": "AGPL-3.0", "peerDependencies": { diff --git a/packages/plugins/@nocobase/plugin-multi-app-manager/package.json b/packages/plugins/@nocobase/plugin-multi-app-manager/package.json index ddb8b705e..d9a8563ef 100644 --- a/packages/plugins/@nocobase/plugin-multi-app-manager/package.json +++ b/packages/plugins/@nocobase/plugin-multi-app-manager/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "多应用管理器", "description": "Dynamically create multiple apps without separate deployments.", "description.zh-CN": "无需单独部署即可动态创建多个应用。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/multi-app-manager", diff --git a/packages/plugins/@nocobase/plugin-multi-app-share-collection/package.json b/packages/plugins/@nocobase/plugin-multi-app-share-collection/package.json index 89910b4fd..45fc0f2b0 100644 --- a/packages/plugins/@nocobase/plugin-multi-app-share-collection/package.json +++ b/packages/plugins/@nocobase/plugin-multi-app-share-collection/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "多应用数据表共享", "description": "", "description.zh-CN": "", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "./dist/server/index.js", "devDependencies": { "@formily/react": "2.x", diff --git a/packages/plugins/@nocobase/plugin-notifications/package.json b/packages/plugins/@nocobase/plugin-notifications/package.json index b0ec6b87d..1641330de 100644 --- a/packages/plugins/@nocobase/plugin-notifications/package.json +++ b/packages/plugins/@nocobase/plugin-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-notifications", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "description": "", "license": "AGPL-3.0", "main": "./dist/server/index.js", diff --git a/packages/plugins/@nocobase/plugin-oidc/package.json b/packages/plugins/@nocobase/plugin-oidc/package.json index 895744767..4cdcbbced 100644 --- a/packages/plugins/@nocobase/plugin-oidc/package.json +++ b/packages/plugins/@nocobase/plugin-oidc/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "认证:OIDC", "description": "OIDC (OpenID Connect) authentication.", "description.zh-CN": "通过 OIDC (OpenID Connect) 协议认证身份。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/auth-oidc", diff --git a/packages/plugins/@nocobase/plugin-saml/package.json b/packages/plugins/@nocobase/plugin-saml/package.json index 7fef44631..a98b5ce7e 100644 --- a/packages/plugins/@nocobase/plugin-saml/package.json +++ b/packages/plugins/@nocobase/plugin-saml/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "认证:SAML 2.0", "description": "SAML 2.0 authentication.", "description.zh-CN": "通过 SAML 2.0 协议认证身份。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/auth-saml", diff --git a/packages/plugins/@nocobase/plugin-sample-hello/package.json b/packages/plugins/@nocobase/plugin-sample-hello/package.json index 29a0d39c5..d970cdcfb 100644 --- a/packages/plugins/@nocobase/plugin-sample-hello/package.json +++ b/packages/plugins/@nocobase/plugin-sample-hello/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-sample-hello", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "./dist/server/index.js", "displayName": "Hello", "displayName.zh-CN": "Hello", diff --git a/packages/plugins/@nocobase/plugin-sequence-field/package.json b/packages/plugins/@nocobase/plugin-sequence-field/package.json index 0abb9649a..8c07760e7 100644 --- a/packages/plugins/@nocobase/plugin-sequence-field/package.json +++ b/packages/plugins/@nocobase/plugin-sequence-field/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "数据表字段:自动编码", "description": "Automatically generate codes based on configured rules, supporting combinations of dates, numbers, and text.", "description.zh-CN": "根据配置的规则自动生成编码,支持日期、数字、文本的组合。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/field-sequence", diff --git a/packages/plugins/@nocobase/plugin-sms-auth/package.json b/packages/plugins/@nocobase/plugin-sms-auth/package.json index bff8e0362..082c68cf2 100644 --- a/packages/plugins/@nocobase/plugin-sms-auth/package.json +++ b/packages/plugins/@nocobase/plugin-sms-auth/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "认证:短信", "description": "SMS authentication.", "description.zh-CN": "通过短信验证码认证身份。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/auth-sms", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/auth-sms", diff --git a/packages/plugins/@nocobase/plugin-snapshot-field/package.json b/packages/plugins/@nocobase/plugin-snapshot-field/package.json index 728468055..d684b5585 100644 --- a/packages/plugins/@nocobase/plugin-snapshot-field/package.json +++ b/packages/plugins/@nocobase/plugin-snapshot-field/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "数据表字段:关系快照", "description": "When adding a new record, create a snapshot for its relational record and save in the new record. The snapshot will not be updated when the relational record is updated.", "description.zh-CN": "在添加数据时,为它的关系数据创建快照,并保存在当前的数据中。关系数据更新时,快照不会更新。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/field-snapshot", diff --git a/packages/plugins/@nocobase/plugin-system-settings/package.json b/packages/plugins/@nocobase/plugin-system-settings/package.json index d4a3fc430..c475876ea 100644 --- a/packages/plugins/@nocobase/plugin-system-settings/package.json +++ b/packages/plugins/@nocobase/plugin-system-settings/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "系统设置", "description": "Used to adjust the system title, logo, language, etc.", "description.zh-CN": "用于调整系统的标题、LOGO、语言等。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/system-settings", diff --git a/packages/plugins/@nocobase/plugin-theme-editor/package.json b/packages/plugins/@nocobase/plugin-theme-editor/package.json index 0986b1b95..55277ce30 100644 --- a/packages/plugins/@nocobase/plugin-theme-editor/package.json +++ b/packages/plugins/@nocobase/plugin-theme-editor/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/plugin-theme-editor", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "main": "dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/theme-editor", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/theme-editor", diff --git a/packages/plugins/@nocobase/plugin-ui-routes-storage/package.json b/packages/plugins/@nocobase/plugin-ui-routes-storage/package.json index 3a8284d3a..e11e757ee 100644 --- a/packages/plugins/@nocobase/plugin-ui-routes-storage/package.json +++ b/packages/plugins/@nocobase/plugin-ui-routes-storage/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "路由管理", "description": "manage the routes of nocobase", "description.zh-CN": "管理页面路由。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "devDependencies": { diff --git a/packages/plugins/@nocobase/plugin-ui-schema-storage/package.json b/packages/plugins/@nocobase/plugin-ui-schema-storage/package.json index ad3c18295..be6191d2f 100644 --- a/packages/plugins/@nocobase/plugin-ui-schema-storage/package.json +++ b/packages/plugins/@nocobase/plugin-ui-schema-storage/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "UI schema 存储服务", "description": "Provides centralized UI schema storage service.", "description.zh-CN": "提供中心化的 UI schema 存储服务。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/ui-schema-storage", diff --git a/packages/plugins/@nocobase/plugin-users/package.json b/packages/plugins/@nocobase/plugin-users/package.json index cc346b6d2..562952a8e 100644 --- a/packages/plugins/@nocobase/plugin-users/package.json +++ b/packages/plugins/@nocobase/plugin-users/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "用户", "description": "Provides basic user model, as well as created by and updated by fields.", "description.zh-CN": "提供了基础的用户模型,以及创建人和最后更新人字段。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/users", diff --git a/packages/plugins/@nocobase/plugin-verification/package.json b/packages/plugins/@nocobase/plugin-verification/package.json index 137c7b157..5c4d83060 100644 --- a/packages/plugins/@nocobase/plugin-verification/package.json +++ b/packages/plugins/@nocobase/plugin-verification/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "验证码", "description": "verification setting.", "description.zh-CN": "验证码配置。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/verification", diff --git a/packages/plugins/@nocobase/plugin-workflow-action-trigger/package.json b/packages/plugins/@nocobase/plugin-workflow-action-trigger/package.json index ffda302d3..a521c898c 100644 --- a/packages/plugins/@nocobase/plugin-workflow-action-trigger/package.json +++ b/packages/plugins/@nocobase/plugin-workflow-action-trigger/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "工作流:数据操作触发器", "description": "Bind action buttons to trigger workflow events when clicked.", "description.zh-CN": "可对数据操作按钮绑定,在点击后触发对应的工作流事件。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/plugins/workflow-action-trigger", diff --git a/packages/plugins/@nocobase/plugin-workflow-aggregate/package.json b/packages/plugins/@nocobase/plugin-workflow-aggregate/package.json index 0b3939c89..b2caaae55 100644 --- a/packages/plugins/@nocobase/plugin-workflow-aggregate/package.json +++ b/packages/plugins/@nocobase/plugin-workflow-aggregate/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "工作流:聚合查询节点", "description": "Used to aggregate data against the database in workflow, such as: statistics, sum, average, etc.", "description.zh-CN": "可用于在工作流中对数据库进行聚合查询,如:统计数量、求和、平均值等。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/workflow-aggregate", diff --git a/packages/plugins/@nocobase/plugin-workflow-delay/package.json b/packages/plugins/@nocobase/plugin-workflow-delay/package.json index dfc14fa8f..b3bb47a55 100644 --- a/packages/plugins/@nocobase/plugin-workflow-delay/package.json +++ b/packages/plugins/@nocobase/plugin-workflow-delay/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "工作流:延时节点", "description": "Could be used in workflow parallel branch for waiting other branches.", "description.zh-CN": "可用于工作流并行分支中等待其他分支执行完成。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/workflow-delay", diff --git a/packages/plugins/@nocobase/plugin-workflow-dynamic-calculation/package.json b/packages/plugins/@nocobase/plugin-workflow-dynamic-calculation/package.json index eb98d15cb..4eae623d5 100644 --- a/packages/plugins/@nocobase/plugin-workflow-dynamic-calculation/package.json +++ b/packages/plugins/@nocobase/plugin-workflow-dynamic-calculation/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "工作流:动态表达式计算节点", "description": "Useful plugin for doing dynamic calculation based on expression collection records in workflow.", "description.zh-CN": "用于在工作流中进行基于数据行的动态表达式计算。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/workflow-dynamic-calculation", diff --git a/packages/plugins/@nocobase/plugin-workflow-loop/package.json b/packages/plugins/@nocobase/plugin-workflow-loop/package.json index 86df41246..49610212d 100644 --- a/packages/plugins/@nocobase/plugin-workflow-loop/package.json +++ b/packages/plugins/@nocobase/plugin-workflow-loop/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "工作流:循环节点", "description": "Used to repeat the sub-process processing of each value in an array, and can also be used for fixed times of sub-process processing.", "description.zh-CN": "用于对一个数组中的每个值进行重复的子流程处理,也可用于固定次数的重复子流程处理。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/workflow-loop", diff --git a/packages/plugins/@nocobase/plugin-workflow-manual/package.json b/packages/plugins/@nocobase/plugin-workflow-manual/package.json index b2031c767..833458260 100644 --- a/packages/plugins/@nocobase/plugin-workflow-manual/package.json +++ b/packages/plugins/@nocobase/plugin-workflow-manual/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "工作流:人工处理节点", "description": "Could be used for workflows which some of decisions are made by users.", "description.zh-CN": "用于人工控制部分决策的流程。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/workflow-manual", diff --git a/packages/plugins/@nocobase/plugin-workflow-parallel/package.json b/packages/plugins/@nocobase/plugin-workflow-parallel/package.json index 08ca2f185..6d62c58ec 100644 --- a/packages/plugins/@nocobase/plugin-workflow-parallel/package.json +++ b/packages/plugins/@nocobase/plugin-workflow-parallel/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "工作流:并行分支节点", "description": "Could be used for parallel execution of branch processes in the workflow.", "description.zh-CN": "用于在工作流中需要并行执行的分支流程。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/workflow-parallel", diff --git a/packages/plugins/@nocobase/plugin-workflow-request/package.json b/packages/plugins/@nocobase/plugin-workflow-request/package.json index ab656b8b9..15a5b639c 100644 --- a/packages/plugins/@nocobase/plugin-workflow-request/package.json +++ b/packages/plugins/@nocobase/plugin-workflow-request/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "工作流:HTTP 请求节点", "description": "Send HTTP requests to any HTTP service for data interaction in workflow.", "description.zh-CN": "可用于在工作流中向任意 HTTP 服务发送请求,进行数据交互。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/workflow-request", diff --git a/packages/plugins/@nocobase/plugin-workflow-sql/package.json b/packages/plugins/@nocobase/plugin-workflow-sql/package.json index 869471451..2ddd45046 100644 --- a/packages/plugins/@nocobase/plugin-workflow-sql/package.json +++ b/packages/plugins/@nocobase/plugin-workflow-sql/package.json @@ -4,7 +4,7 @@ "displayName.zh-CN": "工作流:SQL 节点", "description": "Execute SQL statements in workflow.", "description.zh-CN": "可用于在工作流中对数据库执行任意 SQL 语句。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/workflow-sql", diff --git a/packages/plugins/@nocobase/plugin-workflow-test/package.json b/packages/plugins/@nocobase/plugin-workflow-test/package.json index 986fcb12c..e9df67eee 100644 --- a/packages/plugins/@nocobase/plugin-workflow-test/package.json +++ b/packages/plugins/@nocobase/plugin-workflow-test/package.json @@ -2,7 +2,7 @@ "name": "@nocobase/plugin-workflow-test", "displayName": "Workflow: test kit", "displayName.zh-CN": "工作流:测试工具包", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "dist/server/index.js", "types": "./dist/server/index.d.ts", diff --git a/packages/plugins/@nocobase/plugin-workflow/package.json b/packages/plugins/@nocobase/plugin-workflow/package.json index 2f30c1be1..ddc08fcc0 100644 --- a/packages/plugins/@nocobase/plugin-workflow/package.json +++ b/packages/plugins/@nocobase/plugin-workflow/package.json @@ -4,13 +4,13 @@ "displayName.zh-CN": "工作流", "description": "A powerful BPM tool that provides foundational support for business automation, with the capability to extend unlimited triggers and nodes.", "description.zh-CN": "一个强大的 BPM 工具,为业务自动化提供基础支持,并且可任意扩展更多的触发器和节点。", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./dist/server/index.js", "homepage": "https://docs.nocobase.com/handbook/workflow", "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/workflow", "dependencies": { - "@nocobase/plugin-workflow-test": "0.20.0-alpha.16" + "@nocobase/plugin-workflow-test": "0.20.0-alpha.17" }, "devDependencies": { "@ant-design/icons": "5.x", diff --git a/packages/presets/nocobase/package.json b/packages/presets/nocobase/package.json index 856bc4b35..ffda2f8a9 100644 --- a/packages/presets/nocobase/package.json +++ b/packages/presets/nocobase/package.json @@ -1,66 +1,66 @@ { "name": "@nocobase/preset-nocobase", - "version": "0.20.0-alpha.16", + "version": "0.20.0-alpha.17", "license": "AGPL-3.0", "main": "./lib/server/index.js", "dependencies": { "@formily/json-schema": "2.x", - "@nocobase/plugin-acl": "0.20.0-alpha.16", - "@nocobase/plugin-action-bulk-edit": "0.20.0-alpha.16", - "@nocobase/plugin-action-bulk-update": "0.20.0-alpha.16", - "@nocobase/plugin-action-duplicate": "0.20.0-alpha.16", - "@nocobase/plugin-action-print": "0.20.0-alpha.16", - "@nocobase/plugin-api-doc": "0.20.0-alpha.16", - "@nocobase/plugin-api-keys": "0.20.0-alpha.16", - "@nocobase/plugin-audit-logs": "0.20.0-alpha.16", - "@nocobase/plugin-auth": "0.20.0-alpha.16", - "@nocobase/plugin-backup-restore": "0.20.0-alpha.16", - "@nocobase/plugin-calendar": "0.20.0-alpha.16", - "@nocobase/plugin-cas": "0.20.0-alpha.16", - "@nocobase/plugin-charts": "0.20.0-alpha.16", - "@nocobase/plugin-china-region": "0.20.0-alpha.16", - "@nocobase/plugin-client": "0.20.0-alpha.16", - "@nocobase/plugin-collection-manager": "0.20.0-alpha.16", - "@nocobase/plugin-custom-request": "0.20.0-alpha.16", - "@nocobase/plugin-data-source-manager": "0.20.0-alpha.16", - "@nocobase/plugin-data-visualization": "0.20.0-alpha.16", - "@nocobase/plugin-error-handler": "0.20.0-alpha.16", - "@nocobase/plugin-export": "0.20.0-alpha.16", - "@nocobase/plugin-file-manager": "0.20.0-alpha.16", - "@nocobase/plugin-formula-field": "0.20.0-alpha.16", - "@nocobase/plugin-gantt": "0.20.0-alpha.16", - "@nocobase/plugin-graph-collection-manager": "0.20.0-alpha.16", - "@nocobase/plugin-iframe-block": "0.20.0-alpha.16", - "@nocobase/plugin-import": "0.20.0-alpha.16", - "@nocobase/plugin-kanban": "0.20.0-alpha.16", - "@nocobase/plugin-localization-management": "0.20.0-alpha.16", - "@nocobase/plugin-logger": "0.20.0-alpha.16", - "@nocobase/plugin-map": "0.20.0-alpha.16", - "@nocobase/plugin-mobile-client": "0.20.0-alpha.16", - "@nocobase/plugin-mock-collections": "0.20.0-alpha.16", - "@nocobase/plugin-multi-app-manager": "0.20.0-alpha.16", - "@nocobase/plugin-multi-app-share-collection": "0.20.0-alpha.16", - "@nocobase/plugin-oidc": "0.20.0-alpha.16", - "@nocobase/plugin-saml": "0.20.0-alpha.16", - "@nocobase/plugin-sequence-field": "0.20.0-alpha.16", - "@nocobase/plugin-sms-auth": "0.20.0-alpha.16", - "@nocobase/plugin-snapshot-field": "0.20.0-alpha.16", - "@nocobase/plugin-system-settings": "0.20.0-alpha.16", - "@nocobase/plugin-theme-editor": "0.20.0-alpha.16", - "@nocobase/plugin-ui-schema-storage": "0.20.0-alpha.16", - "@nocobase/plugin-users": "0.20.0-alpha.16", - "@nocobase/plugin-verification": "0.20.0-alpha.16", - "@nocobase/plugin-workflow": "0.20.0-alpha.16", - "@nocobase/plugin-workflow-action-trigger": "0.20.0-alpha.16", - "@nocobase/plugin-workflow-aggregate": "0.20.0-alpha.16", - "@nocobase/plugin-workflow-delay": "0.20.0-alpha.16", - "@nocobase/plugin-workflow-dynamic-calculation": "0.20.0-alpha.16", - "@nocobase/plugin-workflow-loop": "0.20.0-alpha.16", - "@nocobase/plugin-workflow-manual": "0.20.0-alpha.16", - "@nocobase/plugin-workflow-parallel": "0.20.0-alpha.16", - "@nocobase/plugin-workflow-request": "0.20.0-alpha.16", - "@nocobase/plugin-workflow-sql": "0.20.0-alpha.16", - "@nocobase/server": "0.20.0-alpha.16", + "@nocobase/plugin-acl": "0.20.0-alpha.17", + "@nocobase/plugin-action-bulk-edit": "0.20.0-alpha.17", + "@nocobase/plugin-action-bulk-update": "0.20.0-alpha.17", + "@nocobase/plugin-action-duplicate": "0.20.0-alpha.17", + "@nocobase/plugin-action-print": "0.20.0-alpha.17", + "@nocobase/plugin-api-doc": "0.20.0-alpha.17", + "@nocobase/plugin-api-keys": "0.20.0-alpha.17", + "@nocobase/plugin-audit-logs": "0.20.0-alpha.17", + "@nocobase/plugin-auth": "0.20.0-alpha.17", + "@nocobase/plugin-backup-restore": "0.20.0-alpha.17", + "@nocobase/plugin-calendar": "0.20.0-alpha.17", + "@nocobase/plugin-cas": "0.20.0-alpha.17", + "@nocobase/plugin-charts": "0.20.0-alpha.17", + "@nocobase/plugin-china-region": "0.20.0-alpha.17", + "@nocobase/plugin-client": "0.20.0-alpha.17", + "@nocobase/plugin-collection-manager": "0.20.0-alpha.17", + "@nocobase/plugin-custom-request": "0.20.0-alpha.17", + "@nocobase/plugin-data-source-manager": "0.20.0-alpha.17", + "@nocobase/plugin-data-visualization": "0.20.0-alpha.17", + "@nocobase/plugin-error-handler": "0.20.0-alpha.17", + "@nocobase/plugin-export": "0.20.0-alpha.17", + "@nocobase/plugin-file-manager": "0.20.0-alpha.17", + "@nocobase/plugin-formula-field": "0.20.0-alpha.17", + "@nocobase/plugin-gantt": "0.20.0-alpha.17", + "@nocobase/plugin-graph-collection-manager": "0.20.0-alpha.17", + "@nocobase/plugin-iframe-block": "0.20.0-alpha.17", + "@nocobase/plugin-import": "0.20.0-alpha.17", + "@nocobase/plugin-kanban": "0.20.0-alpha.17", + "@nocobase/plugin-localization-management": "0.20.0-alpha.17", + "@nocobase/plugin-logger": "0.20.0-alpha.17", + "@nocobase/plugin-map": "0.20.0-alpha.17", + "@nocobase/plugin-mobile-client": "0.20.0-alpha.17", + "@nocobase/plugin-mock-collections": "0.20.0-alpha.17", + "@nocobase/plugin-multi-app-manager": "0.20.0-alpha.17", + "@nocobase/plugin-multi-app-share-collection": "0.20.0-alpha.17", + "@nocobase/plugin-oidc": "0.20.0-alpha.17", + "@nocobase/plugin-saml": "0.20.0-alpha.17", + "@nocobase/plugin-sequence-field": "0.20.0-alpha.17", + "@nocobase/plugin-sms-auth": "0.20.0-alpha.17", + "@nocobase/plugin-snapshot-field": "0.20.0-alpha.17", + "@nocobase/plugin-system-settings": "0.20.0-alpha.17", + "@nocobase/plugin-theme-editor": "0.20.0-alpha.17", + "@nocobase/plugin-ui-schema-storage": "0.20.0-alpha.17", + "@nocobase/plugin-users": "0.20.0-alpha.17", + "@nocobase/plugin-verification": "0.20.0-alpha.17", + "@nocobase/plugin-workflow": "0.20.0-alpha.17", + "@nocobase/plugin-workflow-action-trigger": "0.20.0-alpha.17", + "@nocobase/plugin-workflow-aggregate": "0.20.0-alpha.17", + "@nocobase/plugin-workflow-delay": "0.20.0-alpha.17", + "@nocobase/plugin-workflow-dynamic-calculation": "0.20.0-alpha.17", + "@nocobase/plugin-workflow-loop": "0.20.0-alpha.17", + "@nocobase/plugin-workflow-manual": "0.20.0-alpha.17", + "@nocobase/plugin-workflow-parallel": "0.20.0-alpha.17", + "@nocobase/plugin-workflow-request": "0.20.0-alpha.17", + "@nocobase/plugin-workflow-sql": "0.20.0-alpha.17", + "@nocobase/server": "0.20.0-alpha.17", "cronstrue": "^2.11.0" }, "repository": { From dd0538ae048353266f99f52fbd9f6a9de420c572 Mon Sep 17 00:00:00 2001 From: chenos Date: Tue, 26 Mar 2024 17:25:09 +0800 Subject: [PATCH 27/36] chore: update changelog --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a72a034c8..779e8ea09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +## [v0.20.0-alpha.17](https://github.com/nocobase/nocobase/compare/v0.20.0-alpha.16...v0.20.0-alpha.17) - 2024-03-26 + +### Merged + +- feat: read pretty input number field support display format config [`#3815`](https://github.com/nocobase/nocobase/pull/3815) +- fix(Table): fix invalid pagination [`#3821`](https://github.com/nocobase/nocobase/pull/3821) +- chore: add tsdoc [`#3788`](https://github.com/nocobase/nocobase/pull/3788) +- chore(test): fix agent type [`#3819`](https://github.com/nocobase/nocobase/pull/3819) +- fix: embed plugin need hooks and e2e change [`#3727`](https://github.com/nocobase/nocobase/pull/3727) +- fix(associationBlock): fix association blocks for parent collection f… [`#3813`](https://github.com/nocobase/nocobase/pull/3813) +- fix(plugin-workflow-manual): fix schema migration [`#3814`](https://github.com/nocobase/nocobase/pull/3814) +- refactor(DataBlock): table block [`#3748`](https://github.com/nocobase/nocobase/pull/3748) +- fix(Details): block template [`#3807`](https://github.com/nocobase/nocobase/pull/3807) +- chore: cascade can replace set null action [`#3812`](https://github.com/nocobase/nocobase/pull/3812) +- feat(data-vi): support multiple data sources [`#3743`](https://github.com/nocobase/nocobase/pull/3743) +- feat(plugin-workflow): support multiple data source in workflow [`#3739`](https://github.com/nocobase/nocobase/pull/3739) +- chore: add options for matching and ignoring test files in e2e and p-test commands [`#3811`](https://github.com/nocobase/nocobase/pull/3811) +- chore: file collection template preset fields should be disabled [`#3810`](https://github.com/nocobase/nocobase/pull/3810) +- fix(plugin-workflow): remove string template in condition calculation [`#3688`](https://github.com/nocobase/nocobase/pull/3688) +- fix: refresh collection name when update [`#3797`](https://github.com/nocobase/nocobase/pull/3797) +- fix: reload when data source click refresh [`#3804`](https://github.com/nocobase/nocobase/pull/3804) +- fix: plugin manager keywords [`#3809`](https://github.com/nocobase/nocobase/pull/3809) +- fix: expand action and add new action should support drag & sort [`#3808`](https://github.com/nocobase/nocobase/pull/3808) +- fix: create attachments middleware [`#3794`](https://github.com/nocobase/nocobase/pull/3794) +- fix: useExpressionScope [`#3805`](https://github.com/nocobase/nocobase/pull/3805) +- chore: set default association reference on delete action to no action [`#3722`](https://github.com/nocobase/nocobase/pull/3722) +- fix: field permission all fields should be displayed [`#3799`](https://github.com/nocobase/nocobase/pull/3799) + +### Commits + +- chore(versions): 😊 publish v0.20.0-alpha.17 [`3398222`](https://github.com/nocobase/nocobase/commit/339822241f2f641656f64107318b793d63d0b2c9) +- fix: description [`0dc0d32`](https://github.com/nocobase/nocobase/commit/0dc0d329f80c268672bd80fc6cb0190c3cef964d) +- chore: update changelog [`35a6514`](https://github.com/nocobase/nocobase/commit/35a6514993bede12b952ce13641f7258fe6c76d2) + ## [v0.20.0-alpha.16](https://github.com/nocobase/nocobase/compare/v0.20.0-alpha.15...v0.20.0-alpha.16) - 2024-03-23 ### Merged From b1be8381b5d112099f3caf2d6bbf46e9c93f0041 Mon Sep 17 00:00:00 2001 From: chenos Date: Tue, 26 Mar 2024 19:51:14 +0800 Subject: [PATCH 28/36] fix(filter-form): htmlType=submit --- .../schema-initializer/items/CreateFilterActionInitializer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/client/src/schema-initializer/items/CreateFilterActionInitializer.tsx b/packages/core/client/src/schema-initializer/items/CreateFilterActionInitializer.tsx index 5e622745f..dbea92159 100644 --- a/packages/core/client/src/schema-initializer/items/CreateFilterActionInitializer.tsx +++ b/packages/core/client/src/schema-initializer/items/CreateFilterActionInitializer.tsx @@ -10,6 +10,7 @@ export const CreateFilterActionInitializer = (props) => { 'x-designer': 'Action.Designer', 'x-component-props': { type: 'primary', + htmlType: 'submit', useProps: '{{ useFilterBlockActionProps }}', }, }; From 85b4afbe7c471b46a74f692cb59e68699caafe8b Mon Sep 17 00:00:00 2001 From: jack zhang <1098626505@qq.com> Date: Wed, 27 Mar 2024 07:44:06 +0800 Subject: [PATCH 29/36] optimize ci (#3825) * feat: add client skip * fix: add table console (skip frontend ci) * feat: add skip ci --- .github/workflows/nocobase-test-backend.yml | 4 ++++ .github/workflows/nocobase-test-e2e.yml | 1 + .github/workflows/nocobase-test-frontend.yml | 1 + 3 files changed, 6 insertions(+) diff --git a/.github/workflows/nocobase-test-backend.yml b/.github/workflows/nocobase-test-backend.yml index 5be681806..4a0a38009 100644 --- a/.github/workflows/nocobase-test-backend.yml +++ b/.github/workflows/nocobase-test-backend.yml @@ -30,6 +30,7 @@ on: jobs: sqlite-test: + if: "!contains(github.event.head_commit.message, 'skip server ci'))" strategy: matrix: node_version: ['18'] @@ -67,6 +68,7 @@ jobs: timeout-minutes: 40 postgres-test: + if: "!contains(github.event.head_commit.message, 'skip server ci'))" strategy: matrix: node_version: ['18'] @@ -131,6 +133,7 @@ jobs: timeout-minutes: 40 mysql-test: + if: "!contains(github.event.head_commit.message, 'skip server ci'))" strategy: matrix: node_version: ['18'] @@ -181,6 +184,7 @@ jobs: DB_TEST_PREFIX: test_ timeout-minutes: 40 mariadb-test: + if: "!contains(github.event.head_commit.message, 'skip server ci'))" strategy: matrix: node_version: ['18'] diff --git a/.github/workflows/nocobase-test-e2e.yml b/.github/workflows/nocobase-test-e2e.yml index 3edbbecf4..a3f6149e0 100644 --- a/.github/workflows/nocobase-test-e2e.yml +++ b/.github/workflows/nocobase-test-e2e.yml @@ -21,6 +21,7 @@ on: jobs: e2e-test-postgres: + if: "!contains(github.event.head_commit.message, 'skip e2e ci'))" strategy: matrix: node_version: ['18'] diff --git a/.github/workflows/nocobase-test-frontend.yml b/.github/workflows/nocobase-test-frontend.yml index 799f84ff2..934b49e5b 100644 --- a/.github/workflows/nocobase-test-frontend.yml +++ b/.github/workflows/nocobase-test-frontend.yml @@ -29,6 +29,7 @@ on: jobs: frontend-test: + if: "!contains(github.event.head_commit.message, 'skip client ci'))" strategy: matrix: node_version: [ '18' ] From e32666b191224a247922a5dff8fed4051b82b949 Mon Sep 17 00:00:00 2001 From: jack zhang <1098626505@qq.com> Date: Wed, 27 Mar 2024 07:45:29 +0800 Subject: [PATCH 30/36] Optimize building tools (#3824) * fix: build support dir * fix: add build retry * fix: vite warning ignore --- packages/core/build/src/build.ts | 12 ++++++++++-- packages/core/build/src/constant.ts | 1 + packages/core/build/src/utils/getPackages.ts | 6 ++++-- packages/core/build/src/utils/utils.ts | 18 +++++++++++++++++- packages/core/cli/src/commands/build.js | 3 +++ 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/core/build/src/build.ts b/packages/core/build/src/build.ts index b8f625c95..46be1144b 100755 --- a/packages/core/build/src/build.ts +++ b/packages/core/build/src/build.ts @@ -14,16 +14,22 @@ import { buildClient } from './buildClient'; import { buildCjs } from './buildCjs'; import { buildPlugin } from './buildPlugin'; import { buildDeclaration } from './buildDeclaration'; -import { PkgLog, getPkgLog, toUnixPath, getPackageJson, getUserConfig, UserConfig } from './utils'; +import { PkgLog, getPkgLog, toUnixPath, getPackageJson, getUserConfig, UserConfig, writeToCache, readFromCache } from './utils'; import { getPackages } from './utils/getPackages'; import { Package } from '@lerna/package'; import { tarPlugin } from './tarPlugin' +const BUILD_ERROR = 'build-error'; + export async function build(pkgs: string[]) { const isDev = process.argv.includes('--development'); process.env.NODE_ENV = isDev ? 'development' : 'production'; - const packages = getPackages(pkgs); + let packages = getPackages(pkgs); + const cachePkg = readFromCache(BUILD_ERROR); + if (process.argv.includes('--retry') && cachePkg?.pkg) { + packages = packages.slice(packages.findIndex((item) => item.name === cachePkg.pkg)); + } if (packages.length === 0) { let msg = ''; if (pkgs.length) { @@ -59,6 +65,7 @@ export async function build(pkgs: string[]) { APP_ROOT: path.join(CORE_APP, 'client'), }); } + writeToCache(BUILD_ERROR, {}); } export async function buildPackages( @@ -67,6 +74,7 @@ export async function buildPackages( doBuildPackage: (cwd: string, userConfig: UserConfig, sourcemap: boolean, log?: PkgLog) => Promise, ) { for await (const pkg of packages) { + writeToCache(BUILD_ERROR, { pkg: pkg.name }) await buildPackage(pkg, targetDir, doBuildPackage); } } diff --git a/packages/core/build/src/constant.ts b/packages/core/build/src/constant.ts index 9b113f84d..f92072aff 100644 --- a/packages/core/build/src/constant.ts +++ b/packages/core/build/src/constant.ts @@ -26,6 +26,7 @@ export const EsbuildSupportExts = [ '.data', ]; export const ROOT_PATH = path.join(__dirname, '../../../../'); +export const NODE_MODULES = path.join(ROOT_PATH, 'node_modules'); export const PACKAGES_PATH = path.join(ROOT_PATH, 'packages'); export const PLUGINS_DIR = ['plugins', 'samples', 'pro-plugins'] .concat((process.env.PLUGINS_DIRS || '').split(',')) diff --git a/packages/core/build/src/utils/getPackages.ts b/packages/core/build/src/utils/getPackages.ts index 5bcd45f44..75231dfa1 100644 --- a/packages/core/build/src/utils/getPackages.ts +++ b/packages/core/build/src/utils/getPackages.ts @@ -10,6 +10,7 @@ import { toUnixPath } from './utils'; * 获取构建包的绝对路径,支持项目路径和 npm 两种形式 * @example * yarn build packages/core/client @nocobase/acl => ['/home/xx/packages/core/client', '/home/xx/packages/core/acl'] + * yarn build packages/plugins/* => ['/home/xx/packages/plugins/a', '/home/xx/packages/plugins/b'] * yarn build => all packages */ function getPackagesPath(pkgs: string[]) { @@ -24,7 +25,6 @@ function getPackagesPath(pkgs: string[]) { return allPackageJson .map(toUnixPath).map(item => path.dirname(item)); } - const allPackageInfo = allPackageJson .map(packageJsonPath => ({ name: require(packageJsonPath).name, path: path.dirname(toUnixPath(packageJsonPath)) })) .reduce((acc, cur) => { @@ -37,7 +37,9 @@ function getPackagesPath(pkgs: string[]) { const relativePaths = pkgNames.length ? pkgs.filter(item => !pkgNames.includes(item)) : pkgs; const pkgPaths = pkgs.map(item => allPackageInfo[item]) const absPaths = allPackagePaths.filter(absPath => relativePaths.some((relativePath) => absPath.endsWith(relativePath))); - return [...pkgPaths, ...absPaths]; + const dirPaths = fg.sync(pkgs, { onlyDirectories: true, absolute: true, cwd: ROOT_PATH }); + const dirMatchPaths = allPackagePaths.filter(pkgPath => dirPaths.some(dirPath => pkgPath.startsWith(dirPath))); + return [...new Set([...pkgPaths, ...absPaths, ...dirMatchPaths])]; } export function getPackages(pkgs: string[]) { diff --git a/packages/core/build/src/utils/utils.ts b/packages/core/build/src/utils/utils.ts index aa616dda9..2af074868 100644 --- a/packages/core/build/src/utils/utils.ts +++ b/packages/core/build/src/utils/utils.ts @@ -1,10 +1,11 @@ import chalk from 'chalk'; import path from 'path'; -import fs from 'fs-extra'; import fg from 'fast-glob'; +import fs from 'fs-extra'; import { Options as TsupConfig } from 'tsup' import { InlineConfig as ViteConfig } from 'vite' import { register } from 'esbuild-register/dist/node'; +import { NODE_MODULES } from '../constant'; let previousColor = ''; function randomColor() { @@ -79,3 +80,18 @@ export function getUserConfig(cwd: string) { } return config; } + +const CACHE_DIR = path.join(NODE_MODULES, '.cache', 'nocobase'); +export function writeToCache(key: string, data: Record) { + const cachePath = path.join(CACHE_DIR, `${key}.json`); + fs.ensureDirSync(path.dirname(cachePath)); + fs.writeJsonSync(cachePath, data, { spaces: 2 }); +} + +export function readFromCache(key: string) { + const cachePath = path.join(CACHE_DIR, `${key}.json`); + if (fs.existsSync(cachePath)) { + return fs.readJsonSync(cachePath); + } + return {}; +} diff --git a/packages/core/cli/src/commands/build.js b/packages/core/cli/src/commands/build.js index d08bd5d7c..267abd837 100644 --- a/packages/core/cli/src/commands/build.js +++ b/packages/core/cli/src/commands/build.js @@ -13,6 +13,7 @@ module.exports = (cli) => { .argument('[packages...]') .option('-v, --version', 'print version') .option('-c, --compile', 'compile the @nocobase/build package') + .option('-r, --retry', 'retry the last failed package') .option('-w, --watch', 'watch compile the @nocobase/build package') .option('-s, --sourcemap', 'generate sourcemap') .option('--no-dts', 'not generate dts') @@ -24,12 +25,14 @@ module.exports = (cli) => { }); if (options.watch) return; } + process.env['VITE_CJS_IGNORE_WARNING'] = 'true'; await run('nocobase-build', [ ...pkgs, options.version ? '--version' : '', !options.dts ? '--no-dts' : '', options.sourcemap ? '--sourcemap' : '', + options.retry ? '--retry' : '', ]); buildIndexHtml(true); }); From c8e334d8ab23d3dd8f2f0f3a3104b836bcc9d1d0 Mon Sep 17 00:00:00 2001 From: katherinehhh Date: Wed, 27 Mar 2024 11:23:34 +0800 Subject: [PATCH 31/36] refactor: action icon (#3831) --- .../plugin-api-keys/src/client/Configuration/schema.tsx | 1 + .../src/client/settings/schemas/applications.ts | 2 ++ .../plugin-verification/src/client/schemas/providers.ts | 2 ++ .../@nocobase/plugin-workflow/src/client/schemas/workflows.ts | 2 ++ 4 files changed, 7 insertions(+) diff --git a/packages/plugins/@nocobase/plugin-api-keys/src/client/Configuration/schema.tsx b/packages/plugins/@nocobase/plugin-api-keys/src/client/Configuration/schema.tsx index b98ab2cb6..aaf6072c4 100644 --- a/packages/plugins/@nocobase/plugin-api-keys/src/client/Configuration/schema.tsx +++ b/packages/plugins/@nocobase/plugin-api-keys/src/client/Configuration/schema.tsx @@ -91,6 +91,7 @@ export const configurationSchema: ISchema = { title: generateNTemplate('Add API key'), 'x-component': 'Action', 'x-component-props': { + icon: 'PlusOutlined', openMode: 'drawer', type: 'primary', }, diff --git a/packages/plugins/@nocobase/plugin-multi-app-manager/src/client/settings/schemas/applications.ts b/packages/plugins/@nocobase/plugin-multi-app-manager/src/client/settings/schemas/applications.ts index ef79bcdd2..93c6bf5b8 100644 --- a/packages/plugins/@nocobase/plugin-multi-app-manager/src/client/settings/schemas/applications.ts +++ b/packages/plugins/@nocobase/plugin-multi-app-manager/src/client/settings/schemas/applications.ts @@ -235,6 +235,7 @@ export const schema: ISchema = { title: '{{ t("Delete") }}', 'x-component': 'Action', 'x-component-props': { + icon: 'DeleteOutlined', useAction: useDestroyAll, confirm: { title: "{{t('Delete')}}", @@ -250,6 +251,7 @@ export const schema: ISchema = { 'x-component': 'Action', 'x-component-props': { type: 'primary', + icon: 'PlusOutlined', }, properties: { drawer: { diff --git a/packages/plugins/@nocobase/plugin-verification/src/client/schemas/providers.ts b/packages/plugins/@nocobase/plugin-verification/src/client/schemas/providers.ts index d294cb8e4..0549ab362 100644 --- a/packages/plugins/@nocobase/plugin-verification/src/client/schemas/providers.ts +++ b/packages/plugins/@nocobase/plugin-verification/src/client/schemas/providers.ts @@ -91,6 +91,7 @@ export default { title: '{{t("Delete")}}', 'x-component': 'Action', 'x-component-props': { + icon: 'DeleteOutlined', useAction: '{{ cm.useBulkDestroyAction }}', confirm: { title: "{{t('Delete')}}", @@ -104,6 +105,7 @@ export default { 'x-component': 'Action', 'x-component-props': { type: 'primary', + icon: 'PlusOutlined', }, properties: { drawer: { diff --git a/packages/plugins/@nocobase/plugin-workflow/src/client/schemas/workflows.ts b/packages/plugins/@nocobase/plugin-workflow/src/client/schemas/workflows.ts index c9708fd81..cb8174216 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/client/schemas/workflows.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/client/schemas/workflows.ts @@ -189,6 +189,7 @@ export const workflowSchema: ISchema = { title: '{{t("Delete")}}', 'x-component': 'Action', 'x-component-props': { + icon: 'DeleteOutlined', useAction: '{{ cm.useBulkDestroyAction }}', confirm: { title: "{{t('Delete record')}}", @@ -202,6 +203,7 @@ export const workflowSchema: ISchema = { 'x-component': 'Action', 'x-component-props': { type: 'primary', + icon: 'PlusOutlined', }, properties: { drawer: { From 78697e02be9224819e160cc82989a5d828eeb3cc Mon Sep 17 00:00:00 2001 From: katherinehhh Date: Wed, 27 Mar 2024 11:37:58 +0800 Subject: [PATCH 32/36] fix: missing button icon (#3832) * refactor: action icon * Revert "refactor: action icon (#3831)" This reverts commit c8e334d8ab23d3dd8f2f0f3a3104b836bcc9d1d0. * refactor: action icon --- .../plugin-auth/src/client/settings/schemas/authenticators.ts | 1 + .../@nocobase/plugin-file-manager/src/client/FileStorage.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/plugins/@nocobase/plugin-auth/src/client/settings/schemas/authenticators.ts b/packages/plugins/@nocobase/plugin-auth/src/client/settings/schemas/authenticators.ts index c2689ba7f..e9225fa5a 100644 --- a/packages/plugins/@nocobase/plugin-auth/src/client/settings/schemas/authenticators.ts +++ b/packages/plugins/@nocobase/plugin-auth/src/client/settings/schemas/authenticators.ts @@ -191,6 +191,7 @@ export const authenticatorsSchema: ISchema = { title: '{{t("Delete")}}', 'x-component': 'Action', 'x-component-props': { + icon: 'DeleteOutlined', useAction: '{{ cm.useBulkDestroyAction }}', confirm: { title: "{{t('Delete')}}", diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/FileStorage.tsx b/packages/plugins/@nocobase/plugin-file-manager/src/client/FileStorage.tsx index 18279f4fc..83ab7d0f9 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/FileStorage.tsx +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/FileStorage.tsx @@ -1,4 +1,4 @@ -import { PlusOutlined } from '@ant-design/icons'; +import { PlusOutlined, DownOutlined } from '@ant-design/icons'; import { uid } from '@formily/shared'; import { ActionContext, SchemaComponent, useCompile, usePlugin, useRecord } from '@nocobase/client'; import { Button, Card, Dropdown } from 'antd'; @@ -73,7 +73,7 @@ export const CreateStorage = () => { }} > From d04a60d386fd4823840e24d567293b118e6daa2a Mon Sep 17 00:00:00 2001 From: chenos Date: Wed, 27 Mar 2024 12:03:00 +0800 Subject: [PATCH 33/36] fix: e2e-failed (#3828) * fix: e2e-failed * fix: ci * fix: ci bug * fix: ci(skip server ci) * fix: ci [skip ci] * fix: ci(skip server ci) * fix: ci * fix: ci * fix: remove skip if --------- Co-authored-by: dream2023 <1098626505@qq.com> --- .github/workflows/nocobase-test-backend.yml | 8 ++--- .github/workflows/nocobase-test-e2e.yml | 1 - .github/workflows/nocobase-test-frontend.yml | 1 - .../antd/input-number/ReadPretty.tsx | 29 ++++++++++++++----- .../__tests__/input-number.test.tsx | 17 +++++++---- 5 files changed, 36 insertions(+), 20 deletions(-) diff --git a/.github/workflows/nocobase-test-backend.yml b/.github/workflows/nocobase-test-backend.yml index 4a0a38009..0224ac2dd 100644 --- a/.github/workflows/nocobase-test-backend.yml +++ b/.github/workflows/nocobase-test-backend.yml @@ -13,6 +13,8 @@ on: - 'packages/core/acl/**' - 'packages/core/actions/**' - 'packages/core/database/**' + - 'packages/core/resourcer/**' + - 'packages/core/data-source-manager/**' - 'packages/core/server/**' - 'packages/core/utils/**' - 'packages/plugins/**/src/server/**' @@ -23,6 +25,8 @@ on: - 'packages/core/acl/**' - 'packages/core/actions/**' - 'packages/core/database/**' + - 'packages/core/resourcer/**' + - 'packages/core/data-source-manager/**' - 'packages/core/server/**' - 'packages/core/utils/**' - 'packages/plugins/**/src/server/**' @@ -30,7 +34,6 @@ on: jobs: sqlite-test: - if: "!contains(github.event.head_commit.message, 'skip server ci'))" strategy: matrix: node_version: ['18'] @@ -68,7 +71,6 @@ jobs: timeout-minutes: 40 postgres-test: - if: "!contains(github.event.head_commit.message, 'skip server ci'))" strategy: matrix: node_version: ['18'] @@ -133,7 +135,6 @@ jobs: timeout-minutes: 40 mysql-test: - if: "!contains(github.event.head_commit.message, 'skip server ci'))" strategy: matrix: node_version: ['18'] @@ -184,7 +185,6 @@ jobs: DB_TEST_PREFIX: test_ timeout-minutes: 40 mariadb-test: - if: "!contains(github.event.head_commit.message, 'skip server ci'))" strategy: matrix: node_version: ['18'] diff --git a/.github/workflows/nocobase-test-e2e.yml b/.github/workflows/nocobase-test-e2e.yml index a3f6149e0..3edbbecf4 100644 --- a/.github/workflows/nocobase-test-e2e.yml +++ b/.github/workflows/nocobase-test-e2e.yml @@ -21,7 +21,6 @@ on: jobs: e2e-test-postgres: - if: "!contains(github.event.head_commit.message, 'skip e2e ci'))" strategy: matrix: node_version: ['18'] diff --git a/.github/workflows/nocobase-test-frontend.yml b/.github/workflows/nocobase-test-frontend.yml index 934b49e5b..799f84ff2 100644 --- a/.github/workflows/nocobase-test-frontend.yml +++ b/.github/workflows/nocobase-test-frontend.yml @@ -29,7 +29,6 @@ on: jobs: frontend-test: - if: "!contains(github.event.head_commit.message, 'skip client ci'))" strategy: matrix: node_version: [ '18' ] diff --git a/packages/core/client/src/schema-component/antd/input-number/ReadPretty.tsx b/packages/core/client/src/schema-component/antd/input-number/ReadPretty.tsx index 91eb12e37..0f5debc66 100644 --- a/packages/core/client/src/schema-component/antd/input-number/ReadPretty.tsx +++ b/packages/core/client/src/schema-component/antd/input-number/ReadPretty.tsx @@ -2,9 +2,9 @@ import { isValid } from '@formily/shared'; import { toFixedByStep } from '@nocobase/utils/client'; import type { InputProps } from 'antd/es/input'; import type { InputNumberProps } from 'antd/es/input-number'; +import { format } from 'd3-format'; import * as math from 'mathjs'; import React, { useMemo } from 'react'; -import { format } from 'd3-format'; function countDecimalPlaces(value) { const number = Number(value); @@ -20,7 +20,7 @@ const separators = { '0.00': { thousands: '', decimal: '.' }, // 没有千位分隔符 }; //分隔符换算 -export function formatNumberWithSeparator(number, format = '0,0.00', step = 1) { +export function formatNumberWithSeparator(number, format = '0.00', step = 1) { let formattedNumber = ''; if (separators[format]) { @@ -75,20 +75,23 @@ export function scientificNotation(number, decimalPlaces, separator = '.') { const result = formattedNumber.replace(/e([+-]?\d+)/, (match, exponent) => { if (exponent.startsWith('+')) { // 正数指数,不显示符号 - return ` × 10${exponent.slice(1)}`; + return `×10${exponent.slice(1)}`; } else { // 负数指数,显示 "-" 符号 - return ` × 10-${exponent.slice(1)}`; + return `×10-${exponent.slice(1)}`; } }); return result; } -export const ReadPretty: React.FC = (props: any) => { - const { step, formatStyle, value, addonBefore, addonAfter, unitConversion, unitConversionType, separator } = props; - if (!isValid(props.value)) { + +export function formatNumber(props) { + const { step, formatStyle, value, unitConversion, unitConversionType, separator = '0.00' } = props; + + if (!isValid(value)) { return null; } + //单位换算 const unitData = formatUnitConversion(value, unitConversionType, unitConversion); //精度换算 @@ -100,10 +103,20 @@ export const ReadPretty: React.FC = (props: any) //科学计数显示 result = scientificNotation(Number(unitData), countDecimalPlaces(step), separators?.[separator]?.['decimal']); } + return result; +} - if (!result) { +export const ReadPretty: React.FC = (props: any) => { + const { step, formatStyle, value, addonBefore, addonAfter, unitConversion, unitConversionType, separator } = props; + + const result = useMemo(() => { + return formatNumber({ step, formatStyle, value, unitConversion, unitConversionType, separator }); + }, [step, formatStyle, value, unitConversion, unitConversionType, separator]); + + if (!isValid(result)) { return null; } + return (
{addonBefore} diff --git a/packages/core/client/src/schema-component/antd/input-number/__tests__/input-number.test.tsx b/packages/core/client/src/schema-component/antd/input-number/__tests__/input-number.test.tsx index 01adf6902..8793b2232 100644 --- a/packages/core/client/src/schema-component/antd/input-number/__tests__/input-number.test.tsx +++ b/packages/core/client/src/schema-component/antd/input-number/__tests__/input-number.test.tsx @@ -1,9 +1,9 @@ import { fireEvent, render, screen } from '@nocobase/test/client'; import React from 'react'; +import { formatNumberWithSeparator, formatUnitConversion, scientificNotation } from '../ReadPretty'; import App2 from '../demos/addonBefore&addonAfter'; import App3 from '../demos/highPrecisionDecimals'; import App1 from '../demos/inputNumber'; -import { formatNumberWithSeparator, formatUnitConversion, scientificNotation } from '../ReadPretty'; describe('InputNumber', () => { it('should display the title', () => { @@ -81,13 +81,13 @@ describe('ReadPretty:formatNumberWithSeparator', () => { // Test case 1: Format a number with default format '0,0.00' test('Format number with default separator', () => { const formatted = formatNumberWithSeparator(1234567.89); - expect(formatted).toBe('1,234,567.9'); + expect(formatted).toBe('1234567.9'); }); // Test case 2: Format a number with custom format '0.00' test('Format number with custom separator', () => { - const formatted = formatNumberWithSeparator(1234567.89, '0.00', 1); - expect(formatted).toBe('1234567.9'); + const formatted = formatNumberWithSeparator(1234567.89, '0,0.00', 1); + expect(formatted).toBe('1,234,567.9'); }); }); describe('ReadPretty:formatUnitConversion', () => { @@ -101,18 +101,23 @@ describe('ReadPretty:formatUnitConversion', () => { const result = formatUnitConversion(10, '/', 0); expect(result).toBe(10); }); + + test('0.1*0.2', () => { + const result = formatUnitConversion(0.1, '*', 0.2); + expect(result).toBe(0.02); + }); }); describe('ReadPretty:scientificNotation', () => { // Test case 1: Format a number into scientific notation with 2 decimal places test('Format number into scientific notation', () => { const formatted = scientificNotation(1234567.89, 2); - expect(formatted).toBe('1.23 × 106'); + expect(formatted).toBe('1.23×106'); }); // Test case 2: Format a number into scientific notation with custom separator '.' test('Format number into scientific notation with custom separator', () => { const formatted = scientificNotation(1234567.89, 2, '.'); - expect(formatted).toBe('1.23 × 106'); + expect(formatted).toBe('1.23×106'); }); }); From d3bd0c71ddd39780be190f1b05d871a4a3960336 Mon Sep 17 00:00:00 2001 From: katherinehhh Date: Wed, 27 Mar 2024 12:03:24 +0800 Subject: [PATCH 34/36] chore: menu permissions & plugins setting permissions (#3822) * chore: menu permissions * chore: plugins setting permissions * chore: plugins setting permissions * chore: plugins setting permissions --- .../client/permissions/GeneralPermissions.tsx | 71 +++++---- .../client/permissions/MenuPermissions.tsx | 135 ++++++++++++------ .../src/client/permissions/Permissions.tsx | 45 +++--- .../client/permissions/PluginPermissions.tsx | 13 +- 4 files changed, 156 insertions(+), 108 deletions(-) diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/GeneralPermissions.tsx b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/GeneralPermissions.tsx index 376262ab1..57ab7b288 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/GeneralPermissions.tsx +++ b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/GeneralPermissions.tsx @@ -5,11 +5,12 @@ import { Checkbox, message } from 'antd'; import uniq from 'lodash/uniq'; import React, { useContext, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; +import { uid } from '@formily/shared'; +import { useMemoizedFn } from 'ahooks'; import { RolesManagerContext } from '../RolesManagerProvider'; import { StrategyActions } from './StrategyActions'; import { useACLTranslation } from '../locale'; -import { uid } from '@formily/shared'; -import { useMemoizedFn } from 'ahooks'; +import { PluginPermissions } from './PluginPermissions'; const SnippetCheckboxGroup = connect((props) => { const { t } = useTranslation(); @@ -61,25 +62,8 @@ export const GeneralPermissions: React.FC<{ const { role, setRole } = useContext(RolesManagerContext); const { t } = useACLTranslation(); const api = useAPIClient(); - const { data } = useRequest( - () => - api - .resource('roles') - .get({ - filterByTk: role?.name, - }) - .then((res) => { - const record = res?.data?.data; - record.snippets?.forEach((key: string) => { - record[key] = true; - }); - return record; - }), - { - ready: active, - refreshDeps: [role?.name], - }, - ); + const pm = role?.snippets?.includes('pm.*'); + const update = useMemoizedFn(async (form: Form) => { await api.resource('roles').update({ filterByTk: role.name, @@ -90,37 +74,48 @@ export const GeneralPermissions: React.FC<{ }); const form = useMemo(() => { return createForm({ - values: data, + values: role, effects() { onFormValuesChange(async (form) => { await update(form); }); }, }); - }, [data, update]); + }, [role, update]); return ( = ({ active }) => { const { styles } = useStyles(); - const { role } = useContext(RolesManagerContext); + const { role, setRole } = useContext(RolesManagerContext); const api = useAPIClient(); const { items } = useMenuItems(); const { t } = useTranslation(); @@ -107,51 +110,89 @@ export const MenuPermissions: React.FC<{ }; }); }; - + const update = useMemoizedFn(async (form: Form) => { + await api.resource('roles').update({ + filterByTk: role.name, + values: form.values, + }); + setRole({ ...role, ...form.values }); + message.success(t('Saved successfully')); + }); + const form = useMemo(() => { + return createForm({ + values: role, + effects() { + onFormValuesChange(async (form) => { + await update(form); + }); + }, + }); + }, [role, update]); return ( - - { - if (allChecked) { - await resource.set({ - values: [], - }); - } else { - await resource.set({ - values: allUids, - }); - } - refresh(); - message.success(t('Saved successfully')); - }} - />{' '} - {t('Accessible')} - - ), - render: (_, schema) => { - const checked = uids.includes(schema.uid); - return handleChange(checked, schema)} />; + <> + + properties: { + allowNewMenu: { + title: t('Menu permissions'), + 'x-decorator': 'FormItem', + 'x-component': 'Checkbox', + 'x-content': t('New menu items are allowed to be accessed by default.'), + }, + }, + }} + /> + +
+ { + if (allChecked) { + await resource.set({ + values: [], + }); + } else { + await resource.set({ + values: allUids, + }); + } + refresh(); + message.success(t('Saved successfully')); + }} + />{' '} + {t('Accessible')} + + ), + render: (_, schema) => { + const checked = uids.includes(schema.uid); + return handleChange(checked, schema)} />; + }, + }, + ]} + dataSource={translateTitle(items)} + /> + ); }; diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/Permissions.tsx b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/Permissions.tsx index 05ed5a0da..8b28cdbda 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/Permissions.tsx +++ b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/Permissions.tsx @@ -1,4 +1,4 @@ -import { useApp } from '@nocobase/client'; +import { useApp, useRequest, useAPIClient } from '@nocobase/client'; import { Tabs } from 'antd'; import React, { useContext, useEffect, useMemo } from 'react'; import { RolesManagerContext } from '../RolesManagerProvider'; @@ -7,7 +7,6 @@ import { AvailableActionsProvider } from './AvailableActions'; import { GeneralPermissions } from './GeneralPermissions'; import { MenuItemsProvider } from './MenuItemsProvider'; import { MenuPermissions } from './MenuPermissions'; -import { PluginPermissions } from './PluginPermissions'; const TabLayout: React.FC = (props) => { return
{props.children}
; @@ -16,7 +15,7 @@ const TabLayout: React.FC = (props) => { export const Permissions: React.FC<{ active: boolean }> = ({ active }) => { const { t } = useACLTranslation(); const [activeKey, setActiveKey] = React.useState('general'); - const { role } = useContext(RolesManagerContext); + const { role, setRole } = useContext(RolesManagerContext); const pm = role?.snippets?.includes('pm.*'); const app = useApp(); const DataSourcePermissionManager = app.getComponent('DataSourcePermissionManager'); @@ -25,7 +24,7 @@ export const Permissions: React.FC<{ active: boolean }> = ({ active }) => { () => [ { key: 'general', - label: t('General'), + label: t('System'), children: ( @@ -43,19 +42,6 @@ export const Permissions: React.FC<{ active: boolean }> = ({ active }) => { ), }, - ...(pm - ? [ - { - key: 'plugin', - label: t('Plugin settings'), - children: ( - - - - ), - }, - ] - : []), ...(DataSourcePermissionManager ? [ { @@ -74,10 +60,33 @@ export const Permissions: React.FC<{ active: boolean }> = ({ active }) => { ], [pm, activeKey, active, t], ); - + const api = useAPIClient(); + const { data } = useRequest( + () => + api + .resource('roles') + .get({ + filterByTk: role?.name, + }) + .then((res) => { + const record = res?.data?.data; + record.snippets?.forEach((key: string) => { + record[key] = true; + }); + return record; + }), + { + ready: active && !!role, + refreshDeps: [role?.name], + }, + ); useEffect(() => { setActiveKey('general'); }, [role?.name]); + + useEffect(() => { + setRole(data); + }, [data]); return ( setActiveKey(key)} items={items} /> diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/PluginPermissions.tsx b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/PluginPermissions.tsx index 301d61bdb..f42090efb 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/client/permissions/PluginPermissions.tsx +++ b/packages/plugins/@nocobase/plugin-acl/src/client/permissions/PluginPermissions.tsx @@ -38,7 +38,7 @@ export const PluginPermissions: React.FC<{ const compile = useCompile(); const settings = app.pluginSettingsManager.getList(false); const allAclSnippets = app.pluginSettingsManager.getAclSnippets(); - const [snippets, setSnippets] = useState([]); + const [snippets, setSnippets] = useState(role?.snippets || []); const flatPlugins = useMemo(() => { return flatMap(settings, (item) => { if (item.children) { @@ -52,12 +52,11 @@ export const PluginPermissions: React.FC<{ () => snippets.includes('pm.*') && snippets.every((item) => !item.startsWith('!pm.')), [snippets], ); - const { t } = useTranslation(); const { loading, refresh } = useRequest( { resource: 'roles.snippets', - resourceOf: role.name, + resourceOf: role?.name, action: 'list', params: { paginate: false, @@ -66,6 +65,7 @@ export const PluginPermissions: React.FC<{ { ready: !!role && active, refreshDeps: [role?.name], + manual: true, onSuccess(data) { setSnippets( data?.data.filter((v) => { @@ -75,7 +75,10 @@ export const PluginPermissions: React.FC<{ }, }, ); - const resource = api.resource('roles.snippets', role.name); + if (!role) { + return; + } + const resource = api.resource('roles.snippets', role?.name); const handleChange = async (checked, record) => { const childrenKeys = getChildrenKeys(record?.children, []); const totalKeys = childrenKeys.concat(record.aclSnippet); @@ -129,7 +132,7 @@ export const PluginPermissions: React.FC<{ refresh(); message.success(t('Saved successfully')); }} - />{' '} + /> {t('Accessible')} ), From e95f4ab123585c0928d5ae9e9b3fb46fa306bba5 Mon Sep 17 00:00:00 2001 From: katherinehhh Date: Wed, 27 Mar 2024 14:34:59 +0800 Subject: [PATCH 35/36] fix: acl e2e failed (#3835) * test: acl e2e test * refactor: test: acle e2e test fix --- .../plugin-acl/src/client/__e2e__/configure.test.ts | 2 +- .../plugin-acl/src/client/__e2e__/menu.test.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/__e2e__/configure.test.ts b/packages/plugins/@nocobase/plugin-acl/src/client/__e2e__/configure.test.ts index 718dc650c..b84d78a38 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/client/__e2e__/configure.test.ts +++ b/packages/plugins/@nocobase/plugin-acl/src/client/__e2e__/configure.test.ts @@ -70,7 +70,7 @@ test('allows to confgiure plugins ', async ({ page, mockPage, mockRole, updateRo .locator('span') .nth(1) .click(); - await expect(page.getByRole('tab').getByText('Plugin settings')).toBeVisible(); + await expect(page.getByText('Plugin settings')).toBeVisible(); await updateRole({ name: roleData.name, snippets: ['!pm.*'], diff --git a/packages/plugins/@nocobase/plugin-acl/src/client/__e2e__/menu.test.ts b/packages/plugins/@nocobase/plugin-acl/src/client/__e2e__/menu.test.ts index 192ab4d78..deb78f6a9 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/client/__e2e__/menu.test.ts +++ b/packages/plugins/@nocobase/plugin-acl/src/client/__e2e__/menu.test.ts @@ -29,8 +29,10 @@ test('menu permission ', async ({ page, mockPage, mockRole, updateRole }) => { .nth(1) .click(); await page.getByRole('tab').getByText('Menu').click(); - await page.waitForSelector('.ant-table'); - await expect(page.getByRole('row', { name: 'page1' }).locator('.ant-checkbox-input')).toBeChecked({ checked: true }); + await page.waitForTimeout(1000); + await expect(page.getByRole('row', { name: 'page1' }).locator('.ant-checkbox-input').last()).toBeChecked({ + checked: true, + }); await expect(page.getByRole('row', { name: 'page2' }).locator('.ant-checkbox-input')).toBeChecked({ checked: false }); //修改菜单权限,page1无权限,page2有权限 await updateRole({ name: roleData.name, menuUiSchemas: [uid2] }); @@ -47,8 +49,10 @@ test('menu permission ', async ({ page, mockPage, mockRole, updateRole }) => { .nth(1) .click(); await page.getByRole('tab').getByText('Menu').click(); - await page.waitForSelector('.ant-table'); - await expect(page.getByRole('row', { name: 'page1' }).locator('.ant-checkbox-input')).toBeChecked({ checked: false }); + await page.waitForTimeout(1000); + await expect(page.getByRole('row', { name: 'page1' }).locator('.ant-checkbox-input').last()).toBeChecked({ + checked: false, + }); await expect(page.getByRole('row', { name: 'page2' }).locator('.ant-checkbox-input')).toBeChecked({ checked: true }); //通过路由访问无权限的菜单,跳到有权限的第一个菜单 await page.goto(`/admin/${uid1}`); From 996e89fb141703510eb7f1f6ffc437ea831eecf4 Mon Sep 17 00:00:00 2001 From: YANG QIA <2013xile@gmail.com> Date: Wed, 27 Mar 2024 15:43:11 +0800 Subject: [PATCH 36/36] fix(data-vi): charts flickers (#3836) --- .../src/client/__tests__/chart-api.test.ts | 26 +++++++------- .../src/client/chart/antd/statistic.ts | 2 +- .../src/client/chart/antd/table.ts | 2 +- .../src/client/chart/chart.ts | 19 ++++------ .../src/client/chart/g2plot/bar.ts | 2 +- .../src/client/chart/g2plot/dualAxes.ts | 2 +- .../src/client/chart/g2plot/g2plot.ts | 4 +-- .../src/client/chart/g2plot/index.ts | 10 +++--- .../src/client/chart/g2plot/pie.ts | 2 +- .../src/client/renderer/ChartRenderer.tsx | 35 +++++++++---------- 10 files changed, 48 insertions(+), 56 deletions(-) diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/chart-api.test.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/chart-api.test.ts index 476716103..907ba76ae 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/chart-api.test.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/chart-api.test.ts @@ -11,38 +11,38 @@ describe('api', () => { }); test('setGroup', () => { - const charts1 = [new Chart({ name: 'test1', title: 'Test1', component: null })]; + const charts1 = [new Chart({ name: 'test1', title: 'Test1', Component: null })]; plugin.charts.setGroup('group', charts1); expect(plugin.charts.charts.get('group')).toEqual(charts1); - const charts2 = [new Chart({ name: 'test2', title: 'Test2', component: null })]; + const charts2 = [new Chart({ name: 'test2', title: 'Test2', Component: null })]; plugin.charts.setGroup('group', charts2); expect(plugin.charts.charts.get('group')).toEqual(charts2); }); test('addGroup', () => { - const charts1 = [new Chart({ name: 'test1', title: 'Test1', component: null })]; + const charts1 = [new Chart({ name: 'test1', title: 'Test1', Component: null })]; plugin.charts.setGroup('group1', charts1); - const charts2 = [new Chart({ name: 'test2', title: 'Test2', component: null })]; + const charts2 = [new Chart({ name: 'test2', title: 'Test2', Component: null })]; plugin.charts.addGroup('group2', charts2); expect(plugin.charts.charts.get('group1')).toEqual(charts1); expect(plugin.charts.charts.get('group2')).toEqual(charts2); }); test('add', () => { - const charts1 = [new Chart({ name: 'test1', title: 'Test1', component: null })]; + const charts1 = [new Chart({ name: 'test1', title: 'Test1', Component: null })]; plugin.charts.setGroup('group', charts1); - const chart = new Chart({ name: 'test2', title: 'Test2', component: null }); + const chart = new Chart({ name: 'test2', title: 'Test2', Component: null }); plugin.charts.add('group', chart); expect(plugin.charts.charts.get('group').length).toEqual(2); expect(plugin.charts.charts.get('group')[1].name).toEqual('test2'); }); test('getChartTypes', () => { - const charts1 = [new Chart({ name: 'test1', title: 'Test1', component: null })]; + const charts1 = [new Chart({ name: 'test1', title: 'Test1', Component: null })]; plugin.charts.setGroup('group1', charts1); - const charts2 = [new Chart({ name: 'test2', title: 'Test2', component: null })]; + const charts2 = [new Chart({ name: 'test2', title: 'Test2', Component: null })]; plugin.charts.setGroup('group2', charts2); expect(plugin.charts.getChartTypes()).toEqual([ { @@ -69,9 +69,9 @@ describe('api', () => { }); test('getCharts', () => { - const charts1 = [new Chart({ name: 'test1', title: 'Test1', component: null })]; + const charts1 = [new Chart({ name: 'test1', title: 'Test1', Component: null })]; plugin.charts.setGroup('group1', charts1); - const charts2 = [new Chart({ name: 'test2', title: 'Test2', component: null })]; + const charts2 = [new Chart({ name: 'test2', title: 'Test2', Component: null })]; plugin.charts.setGroup('group2', charts2); expect(plugin.charts.getCharts()).toEqual({ 'group1.test1': charts1[0], @@ -80,9 +80,9 @@ describe('api', () => { }); test('getChart', () => { - const charts1 = [new Chart({ name: 'test1', title: 'Test1', component: null })]; + const charts1 = [new Chart({ name: 'test1', title: 'Test1', Component: null })]; plugin.charts.setGroup('group1', charts1); - const charts2 = [new Chart({ name: 'test2', title: 'Test2', component: null })]; + const charts2 = [new Chart({ name: 'test2', title: 'Test2', Component: null })]; plugin.charts.setGroup('group2', charts2); expect(plugin.charts.getChart('group1.test1')).toEqual(charts1[0]); expect(plugin.charts.getChart('group2.test2')).toEqual(charts2[0]); @@ -90,7 +90,7 @@ describe('api', () => { }); describe('auto infer', () => { - const chart = new Chart({ name: 'test', title: 'Test', component: null }); + const chart = new Chart({ name: 'test', title: 'Test', Component: null }); const fields = [ { name: 'price', diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts index 3d7298970..144409a9a 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts @@ -8,7 +8,7 @@ export class Statistic extends AntdChart { super({ name: 'statistic', title: 'Statistic', - component: AntdStatistic, + Component: AntdStatistic, config: [ { property: 'field', diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/table.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/table.ts index ba0763916..f0ad9ff03 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/table.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/table.ts @@ -4,7 +4,7 @@ import { Table as AntdTable } from 'antd'; export class Table extends AntdChart { constructor() { - super({ name: 'table', title: 'Table', component: AntdTable }); + super({ name: 'table', title: 'Table', Component: AntdTable }); } getProps({ data, fieldProps, general, advanced }: RenderProps) { diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts index c6f0227b6..4f8f05492 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts @@ -22,7 +22,7 @@ export type RenderProps = { export interface ChartType { name: string; title: string; - component: React.FC; + Component: React.FC; schema: ISchema; init?: ( fields: FieldOption[], @@ -34,7 +34,7 @@ export interface ChartType { general?: any; advanced?: any; }; - render: (props: RenderProps) => React.FC; + getProps(props: RenderProps): any; getReference?: () => { title: string; link: string; @@ -44,21 +44,21 @@ export interface ChartType { export type ChartProps = { name: string; title: string; - component: React.FC; + Component: React.FC; config?: Config[]; }; export class Chart implements ChartType { name: string; title: string; - component: React.FC; + Component: React.FC; config: Config[]; configs = new Map(); - constructor({ name, title, component, config }: ChartProps) { + constructor({ name, title, Component, config }: ChartProps) { this.name = name; this.title = title; - this.component = component; + this.Component = Component; this.config = config; this.addConfigs(configs); } @@ -170,11 +170,4 @@ export class Chart implements ChartType { getProps(props: RenderProps): any { return props; } - - render({ data, general, advanced, fieldProps }: RenderProps) { - return () => - React.createElement(this.component, { - ...this.getProps({ data, general, advanced, fieldProps }), - }); - } } diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/bar.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/bar.ts index 211bb1976..20018cedd 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/bar.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/bar.ts @@ -4,7 +4,7 @@ import { ChartType } from '../chart'; export class Bar extends G2PlotChart { constructor() { - super({ name: 'bar', title: 'Bar Chart', component: G2PlotBar, config: ['isGroup', 'isStack', 'isPercent'] }); + super({ name: 'bar', title: 'Bar Chart', Component: G2PlotBar, config: ['isGroup', 'isStack', 'isPercent'] }); } init: ChartType['init'] = (fields, { measures, dimensions }) => { diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/dualAxes.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/dualAxes.ts index 256948667..656c98c46 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/dualAxes.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/dualAxes.ts @@ -6,7 +6,7 @@ import lodash from 'lodash'; export class DualAxes extends G2PlotChart { constructor() { - super({ name: 'dualAxes', title: 'Dual Axes Chart', component: G2DualAxes }); + super({ name: 'dualAxes', title: 'Dual Axes Chart', Component: G2DualAxes }); this.config = [ 'xField', { diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/g2plot.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/g2plot.ts index e4efec4bb..e28dd2231 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/g2plot.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/g2plot.ts @@ -2,11 +2,11 @@ import { Chart, ChartProps, ChartType, RenderProps } from '../chart'; import configs from './configs'; export class G2PlotChart extends Chart { - constructor({ name, title, component, config }: ChartProps) { + constructor({ name, title, Component, config }: ChartProps) { super({ name, title, - component, + Component, config: ['xField', 'yField', 'seriesField', ...(config || [])], }); this.addConfigs(configs); diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/index.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/index.ts index d19deda50..653ea9148 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/index.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/index.ts @@ -7,13 +7,13 @@ export default [ new G2PlotChart({ name: 'line', title: 'Line Chart', - component: Line, + Component: Line, config: ['smooth', 'isStack'], }), new G2PlotChart({ name: 'area', title: 'Area Chart', - component: Area, + Component: Area, config: [ 'smooth', { @@ -26,16 +26,16 @@ export default [ new G2PlotChart({ name: 'column', title: 'Column Chart', - component: Column, + Component: Column, config: ['isGroup', 'isStack', 'isPercent'], }), new G2PlotChart({ name: 'bar', title: 'Bar Chart', - component: Bar, + Component: Bar, config: ['isGroup', 'isStack', 'isPercent'], }), new Pie(), new DualAxes(), - new G2PlotChart({ name: 'scatter', title: 'Scatter Chart', component: Scatter }), + new G2PlotChart({ name: 'scatter', title: 'Scatter Chart', Component: Scatter }), ]; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/pie.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/pie.ts index 588a33f10..02d77769d 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/pie.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/pie.ts @@ -4,7 +4,7 @@ import { ChartType, RenderProps } from '../chart'; export class Pie extends G2PlotChart { constructor() { - super({ name: 'pie', title: 'Pie Chart', component: G2Pie }); + super({ name: 'pie', title: 'Pie Chart', Component: G2Pie }); this.config = [ { property: 'field', diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx index f95dcc296..d7e38183c 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx @@ -12,7 +12,7 @@ import { useDesignable, } from '@nocobase/client'; import { Empty, Result, Spin, Typography } from 'antd'; -import React, { useContext } from 'react'; +import React, { memo, useContext } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { ChartConfigContext } from '../configure'; import { useData, useFieldTransformer, useFieldsWithAssociation } from '../hooks'; @@ -22,6 +22,8 @@ import { ChartRendererContext } from './ChartRendererProvider'; import { useChart } from '../chart/group'; import { ChartDataContext } from '../block/ChartDataProvider'; const { Paragraph, Text } = Typography; +import lodash from 'lodash'; +import { Line } from '@ant-design/plots'; export const ChartRenderer: React.FC & { Designer: React.FC; @@ -38,7 +40,7 @@ export const ChartRenderer: React.FC & { const chart = useChart(config?.chartType); const locale = api.auth.getLocale(); const transformers = useFieldTransformer(transform, locale); - const Component = chart?.render({ + const chartProps = chart?.getProps({ data, general, advanced, @@ -51,30 +53,27 @@ export const ChartRenderer: React.FC & { return props; }, {}), }); + const C = chart?.Component; - const C = () => - chart ? ( + if (!chart) { + return ; + } + if (!(data && data.length) && !service.loading) { + return ; + } + + return ( + { console.error(error); }} FallbackComponent={ErrorFallback} > - + - ) : ( - - ); - - if (service.loading) { - return ; - } - - if (!(data && data.length)) { - return ; - } - - return ; + + ); }; ChartRenderer.Designer = function Designer() {