From a985bc46a837eff7590d5981202f735709d151e2 Mon Sep 17 00:00:00 2001 From: ChengLei Shao Date: Thu, 31 Oct 2024 14:42:10 +0800 Subject: [PATCH 1/8] fix: remove data source in failed status (#5554) --- .../@nocobase/plugin-data-source-manager/src/server/plugin.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts index 02ea988b45..f0ab8cb5f6 100644 --- a/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts +++ b/packages/plugins/@nocobase/plugin-data-source-manager/src/server/plugin.ts @@ -363,7 +363,9 @@ export class PluginDataSourceManagerServer extends Plugin { this.app.db.on('dataSourcesCollections.afterDestroy', async (model: DataSourcesCollectionModel) => { const dataSource = this.app.dataSourceManager.dataSources.get(model.get('dataSourceKey')); - dataSource.collectionManager.removeCollection(model.get('name')); + if (dataSource) { + dataSource.collectionManager.removeCollection(model.get('name')); + } }); this.app.db.on('dataSourcesFields.afterSaveWithAssociations', async (model: DataSourcesFieldModel) => { From eec0f30d40f5047f30f80bb8df801318eca04595 Mon Sep 17 00:00:00 2001 From: ChengLei Shao Date: Thu, 31 Oct 2024 17:38:07 +0800 Subject: [PATCH 2/8] fix: sequelize primary key field with multi filter target keys (#5556) --- packages/core/database/src/collection.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/core/database/src/collection.ts b/packages/core/database/src/collection.ts index f05eb39529..31866cf1a8 100644 --- a/packages/core/database/src/collection.ts +++ b/packages/core/database/src/collection.ts @@ -189,10 +189,6 @@ export class Collection< return this.model.primaryKeyAttribute; } - isMultiFilterTargetKey() { - return Array.isArray(this.filterTargetKey) && this.filterTargetKey.length > 1; - } - get name() { return this.options.name; } @@ -225,6 +221,10 @@ export class Collection< } } + isMultiFilterTargetKey() { + return Array.isArray(this.filterTargetKey) && this.filterTargetKey.length > 1; + } + tableName() { const { name, tableName } = this.options; const tName = tableName || name; @@ -313,6 +313,20 @@ export class Collection< }, }); + Object.defineProperty(this.model, 'primaryKeyField', { + get: function () { + if (this.primaryKeyAttribute) { + return this.rawAttributes[this.primaryKeyAttribute].field || this.primaryKeyAttribute; + } + + return null; + }.bind(this.model), + + set(val) { + this._primaryKeyField = val; + }, + }); + this.model.init(null, this.sequelizeModelOptions()); this.model.options.modelName = this.options.name; From 809ae5c011e7d2d9a66aaec9e4d80e293f8ae8f1 Mon Sep 17 00:00:00 2001 From: YANG QIA <2013xile@gmail.com> Date: Thu, 31 Oct 2024 20:16:19 +0800 Subject: [PATCH 3/8] feat(notification-manager): add UserSelect and UserAddition components (#5553) * feat(notification-manager): add UserSelect and UserAddition components * refactor(notification-manager): update type of 'reason' field to 'text' * refactor(notification-manager): update error logging in NotificationManager * refactor: refactor notification manager exports --------- Co-authored-by: Sheldon Guo --- .../src/client/index.tsx | 1 + .../components/MessageConfigForm/index.tsx | 5 +- .../receiver/components/User/UserAddition.tsx | 61 +++++++++++++ .../receiver/components/User/UserSelect.tsx | 88 +++++++++++++++++++ .../receiver/components/User/index.tsx | 11 +++ .../src/collections/messageLog.ts | 2 +- .../plugin-notification-manager/src/index.ts | 11 ++- .../src/server/index.ts | 1 + .../src/server/manager.ts | 4 +- .../server/utils/parseUserSelectionConfig.ts | 30 +++++++ 10 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/receiver/components/User/UserAddition.tsx create mode 100644 packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/receiver/components/User/UserSelect.tsx create mode 100644 packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/receiver/components/User/index.tsx create mode 100644 packages/plugins/@nocobase/plugin-notification-manager/src/server/utils/parseUserSelectionConfig.ts diff --git a/packages/plugins/@nocobase/plugin-notification-manager/src/client/index.tsx b/packages/plugins/@nocobase/plugin-notification-manager/src/client/index.tsx index c1a563f586..01cfa033b0 100644 --- a/packages/plugins/@nocobase/plugin-notification-manager/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-notification-manager/src/client/index.tsx @@ -60,4 +60,5 @@ export class PluginNotificationManagerClient extends Plugin { export { NotificationVariableContext, NotificationVariableProvider, useNotificationVariableOptions } from './hooks'; export { MessageConfigForm } from './manager/message/components/MessageConfigForm'; export { ContentConfigForm } from './manager/message/components/ContentConfigForm'; +export { UserSelect, UserAddition } from './manager/receiver/components/User'; export default PluginNotificationManagerClient; diff --git a/packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/message/components/MessageConfigForm/index.tsx b/packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/message/components/MessageConfigForm/index.tsx index af5c69afe8..8e17b9a54a 100644 --- a/packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/message/components/MessageConfigForm/index.tsx +++ b/packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/message/components/MessageConfigForm/index.tsx @@ -8,6 +8,7 @@ */ import React, { useState, useEffect } from 'react'; +import { ArrayItems } from '@formily/antd-v5'; import { SchemaComponent } from '@nocobase/client'; import { observer, useField } from '@formily/react'; import { useAPIClient } from '@nocobase/client'; @@ -75,7 +76,9 @@ export const MessageConfigForm = observer<{ variableOptions: any }>( }, }, }; - return ; + return ( + + ); }, { displayName: 'MessageConfigForm' }, ); diff --git a/packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/receiver/components/User/UserAddition.tsx b/packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/receiver/components/User/UserAddition.tsx new file mode 100644 index 0000000000..958b9e4b64 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/receiver/components/User/UserAddition.tsx @@ -0,0 +1,61 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { useField, useForm } from '@formily/react'; +import { ArrayField as ArrayFieldModel } from '@formily/core'; +import { ArrayItems } from '@formily/antd-v5'; +import { Button, Popover, Space } from 'antd'; +import { PlusOutlined } from '@ant-design/icons'; +import React, { useCallback, useState } from 'react'; +import { useNotificationTranslation } from '../../../../locale'; + +export default function UsersAddition() { + const [open, setOpen] = useState(false); + const { t } = useNotificationTranslation(); + const array = ArrayItems.useArray(); + const form = useForm(); + const disabled = form?.disabled === true; + + const onAddSelect = useCallback(() => { + array.field.push(''); + setOpen(false); + }, [array.field]); + + const onAddQuery = useCallback(() => { + array.field.push({ filter: {} }); + setOpen(false); + }, [array.field]); + + const button = ( + + ); + + return disabled ? ( + button + ) : ( + + + + + } + > + {button} + + ); +} diff --git a/packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/receiver/components/User/UserSelect.tsx b/packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/receiver/components/User/UserSelect.tsx new file mode 100644 index 0000000000..5b3836cf8d --- /dev/null +++ b/packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/receiver/components/User/UserSelect.tsx @@ -0,0 +1,88 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This program is offered under a commercial license. + * For more information, see + */ + +import React, { useCallback } from 'react'; +import { RemoteSelect, SchemaComponent, Variable, useCollectionFilterOptions, useToken } from '@nocobase/client'; +import { useField } from '@formily/react'; + +function InternalUsersSelect({ value, onChange, variableOptions }) { + return ( + + + + ); +} + +function UsersQuery(props) { + const field = useField(); + const options = useCollectionFilterOptions('users'); + const { token } = useToken(); + const FilterDynamicComponent = useCallback( + ({ value, onChange, renderSchemaComponent }) => { + return ( + + {renderSchemaComponent()} + + ); + }, + [props.variableOptions], + ); + + return ( +
+ +
+ ); +} + +export default function UserSelect(props) { + const valueType = typeof props.value; + + return valueType === 'object' && props.value ? : ; +} diff --git a/packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/receiver/components/User/index.tsx b/packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/receiver/components/User/index.tsx new file mode 100644 index 0000000000..ea6e7d73e1 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-notification-manager/src/client/manager/receiver/components/User/index.tsx @@ -0,0 +1,11 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export { default as UserSelect } from './UserSelect'; +export { default as UserAddition } from './UserAddition'; diff --git a/packages/plugins/@nocobase/plugin-notification-manager/src/collections/messageLog.ts b/packages/plugins/@nocobase/plugin-notification-manager/src/collections/messageLog.ts index 49946955aa..4e151eb289 100644 --- a/packages/plugins/@nocobase/plugin-notification-manager/src/collections/messageLog.ts +++ b/packages/plugins/@nocobase/plugin-notification-manager/src/collections/messageLog.ts @@ -95,7 +95,7 @@ export default { }, { name: 'reason', - type: 'string', + type: 'text', interface: 'input', uiSchema: { type: 'string', diff --git a/packages/plugins/@nocobase/plugin-notification-manager/src/index.ts b/packages/plugins/@nocobase/plugin-notification-manager/src/index.ts index bcfbfb546b..4b860e13fd 100644 --- a/packages/plugins/@nocobase/plugin-notification-manager/src/index.ts +++ b/packages/plugins/@nocobase/plugin-notification-manager/src/index.ts @@ -7,7 +7,10 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -export { default } from './server'; -export { BaseNotificationChannel } from './server/base-notification-channel'; - -export * from './server'; +export { + default, + COLLECTION_NAME, + ChannelsCollectionDefinition, + BaseNotificationChannel, + parseUserSelectionConfig, +} from './server'; diff --git a/packages/plugins/@nocobase/plugin-notification-manager/src/server/index.ts b/packages/plugins/@nocobase/plugin-notification-manager/src/server/index.ts index 199b7f6983..6ae666589f 100644 --- a/packages/plugins/@nocobase/plugin-notification-manager/src/server/index.ts +++ b/packages/plugins/@nocobase/plugin-notification-manager/src/server/index.ts @@ -10,5 +10,6 @@ export { BaseNotificationChannel } from './base-notification-channel'; export { default } from './plugin'; export { COLLECTION_NAME, ChannelsCollectionDefinition } from '../constant'; +export { parseUserSelectionConfig } from './utils/parseUserSelectionConfig'; export * from './types'; diff --git a/packages/plugins/@nocobase/plugin-notification-manager/src/server/manager.ts b/packages/plugins/@nocobase/plugin-notification-manager/src/server/manager.ts index 799f8d65b4..06a6c9847f 100644 --- a/packages/plugins/@nocobase/plugin-notification-manager/src/server/manager.ts +++ b/packages/plugins/@nocobase/plugin-notification-manager/src/server/manager.ts @@ -61,7 +61,7 @@ export class NotificationManager implements NotificationManager { return logData; } catch (error) { logData.status = 'failure'; - this.plugin.logger.error('notification send failed', JSON.stringify(error)); + this.plugin.logger.error(`notification send failed, options: ${JSON.stringify(error)}`); logData.reason = JSON.stringify(error); this.createSendingRecord(logData); return logData; @@ -69,7 +69,7 @@ export class NotificationManager implements NotificationManager { } async sendToUsers(options: SendUserOptions) { const { userIds, channels, message, data } = options; - this.plugin.logger.info('notificationManager.sendToUsers options', JSON.stringify(options)); + this.plugin.logger.info(`notificationManager.sendToUsers options: ${JSON.stringify(options)}`); return await Promise.all( channels.map((channelName) => this.send({ channelName, message, triggerFrom: 'sendToUsers', receivers: { value: userIds, type: 'userId' } }), diff --git a/packages/plugins/@nocobase/plugin-notification-manager/src/server/utils/parseUserSelectionConfig.ts b/packages/plugins/@nocobase/plugin-notification-manager/src/server/utils/parseUserSelectionConfig.ts new file mode 100644 index 0000000000..c54f42e651 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-notification-manager/src/server/utils/parseUserSelectionConfig.ts @@ -0,0 +1,30 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Repository } from '@nocobase/database'; +export async function parseUserSelectionConfig( + userSelectionConfig: Array | string>, + UserRepo: Repository, +) { + const SelectionConfigs = userSelectionConfig.flat().filter(Boolean); + const users = new Set(); + for (const item of SelectionConfigs) { + if (typeof item === 'object') { + const result = await UserRepo.find({ + ...item, + fields: ['id'], + }); + result.forEach((item) => users.add(item.id)); + } else { + users.add(item); + } + } + + return [...users]; +} From e818195dd17d5a5e92b59b7bd20c18ab3e927a0f Mon Sep 17 00:00:00 2001 From: ChengLei Shao Date: Thu, 31 Oct 2024 21:41:10 +0800 Subject: [PATCH 4/8] fix: merge option with filter target key (#5558) --- packages/core/database/src/collection.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/core/database/src/collection.ts b/packages/core/database/src/collection.ts index 31866cf1a8..a887c246ab 100644 --- a/packages/core/database/src/collection.ts +++ b/packages/core/database/src/collection.ts @@ -653,9 +653,14 @@ export class Collection< updateOptions(options: CollectionOptions, mergeOptions?: any) { let newOptions = lodash.cloneDeep(options); newOptions = merge(this.options, newOptions, mergeOptions); - this.context.database.emit('beforeUpdateCollection', this, newOptions); - this.options = newOptions; + if (options.filterTargetKey) { + newOptions.filterTargetKey = options.filterTargetKey; + } + + this.context.database.emit('beforeUpdateCollection', this, newOptions); + + this.options = newOptions; this.setFields(options.fields, false); if (options.repository) { this.setRepository(options.repository); From 056d46c6803e95f30eb7954da0c79de73d12921a Mon Sep 17 00:00:00 2001 From: ChengLei Shao Date: Fri, 1 Nov 2024 06:46:55 +0800 Subject: [PATCH 5/8] chore: load collection with schema (#5541) * chore: load collection with schema * chore: load collection with schema * chore: test * chore: test * chore: test * chore: test * fix: test --------- Co-authored-by: CHENGLEI SHAO --- .../20240802141435-collection-tree.ts | 17 +++++++++++++---- .../__tests__/collections.repository.test.ts | 15 ++++++--------- .../__tests__/fields/belongs-to-many.test.ts | 8 +++++--- .../src/server/models/collection.ts | 4 ++++ .../src/server/server.ts | 13 ------------- .../server/__tests__/collection-sync.test.ts | 6 ++++-- 6 files changed, 32 insertions(+), 31 deletions(-) diff --git a/packages/plugins/@nocobase/plugin-collection-tree/src/server/migrations/20240802141435-collection-tree.ts b/packages/plugins/@nocobase/plugin-collection-tree/src/server/migrations/20240802141435-collection-tree.ts index 2eee5b68a4..61772f45ab 100644 --- a/packages/plugins/@nocobase/plugin-collection-tree/src/server/migrations/20240802141435-collection-tree.ts +++ b/packages/plugins/@nocobase/plugin-collection-tree/src/server/migrations/20240802141435-collection-tree.ts @@ -47,11 +47,18 @@ export default class extends Migration { }, ], }; - if (treeCollection.options.schema) { - collectionOptions['schema'] = treeCollection.options.schema; + + const collectionInstance = this.db.getCollection(treeCollection.name); + const treeCollectionSchema = collectionInstance.collectionSchema(); + + if (this.app.db.inDialect('postgres') && treeCollectionSchema != this.app.db.options.schema) { + collectionOptions['schema'] = treeCollectionSchema; } + this.app.db.collection(collectionOptions); + const treeExistsInDb = await this.app.db.getCollection(name).existsInDb({ transaction }); + if (!treeExistsInDb) { await this.app.db.getCollection(name).sync({ transaction } as SyncOptions); const opts = { @@ -63,9 +70,11 @@ export default class extends Migration { { type: 'integer', name: 'parentId' }, ], }; - if (treeCollection.options.schema) { - opts['schema'] = treeCollection.options.schema; + + if (treeCollectionSchema != this.app.db.options.schema) { + opts['schema'] = treeCollectionSchema; } + this.app.db.collection(opts); const chunkSize = 1000; await this.app.db.getRepository(treeCollection.name).chunk({ diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/collections.repository.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/collections.repository.test.ts index 52680993c4..1650391393 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/collections.repository.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/collections.repository.test.ts @@ -7,10 +7,11 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import Database, { CollectionGroupManager, Collection as DBCollection, HasManyRepository } from '@nocobase/database'; +import Database, { Collection as DBCollection, CollectionGroupManager, HasManyRepository } from '@nocobase/database'; import Application from '@nocobase/server'; import { createApp } from '.'; -import CollectionManagerPlugin, { CollectionRepository } from '../index'; +import { CollectionRepository } from '../index'; +import { isPg } from '@nocobase/test'; describe('collections repository', () => { let db: Database; @@ -26,6 +27,7 @@ describe('collections repository', () => { }); afterEach(async () => { + vi.unstubAllEnvs(); await app.destroy(); }); @@ -380,13 +382,8 @@ describe('collections repository', () => { expect(afterRepository.load).toBeTruthy(); }); - it('should set collection schema from env', async () => { - if (!db.inDialect('postgres')) { - return; - } - - const plugin = app.getPlugin('data-source-main'); - plugin.schema = 'testSchema'; + it.runIf(isPg())('should set collection schema from env', async () => { + vi.stubEnv('COLLECTION_MANAGER_SCHEMA', 'testSchema'); await Collection.repository.create({ values: { diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/fields/belongs-to-many.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/fields/belongs-to-many.test.ts index dd1896b108..9706b5c9d2 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/fields/belongs-to-many.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/fields/belongs-to-many.test.ts @@ -206,18 +206,20 @@ describe('belongsToMany', () => { context: {}, }); - const throughCollection = await Collection.repository.findOne({ + const throughCollectionRecord = await Collection.repository.findOne({ filter: { name: 'post_tags', }, }); - expect(throughCollection.get('sortable')).toEqual(false); + expect(throughCollectionRecord.get('sortable')).toEqual(false); const collectionManagerSchema = process.env.COLLECTION_MANAGER_SCHEMA; const mainSchema = process.env.DB_SCHEMA || 'public'; + const throughCollection = db.getCollection('post_tags'); + if (collectionManagerSchema && mainSchema != collectionManagerSchema && db.inDialect('postgres')) { - expect(throughCollection.get('schema')).toEqual(collectionManagerSchema); + expect(throughCollection.options.schema).toEqual(collectionManagerSchema); const tableName = db.getCollection('post_tags').model.tableName; diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/collection.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/collection.ts index 18efa02366..1f46535ab7 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/collection.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/collection.ts @@ -63,6 +63,10 @@ export class CollectionModel extends MagicAttributeModel { delete collectionOptions.schema; } + if (this.db.inDialect('postgres') && !collectionOptions.schema && collectionOptions.from !== 'db2cm') { + collectionOptions.schema = process.env.COLLECTION_MANAGER_SCHEMA || this.db.options.schema || 'public'; + } + if (this.db.hasCollection(name)) { collection = this.db.getCollection(name); diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/server.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/server.ts index bcd7320ab2..6ce490b326 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/server.ts @@ -13,7 +13,6 @@ import { Plugin } from '@nocobase/server'; import { Mutex } from 'async-mutex'; import lodash from 'lodash'; import path from 'path'; -import * as process from 'process'; import { CollectionRepository } from '.'; import { afterCreateForForeignKeyField, @@ -33,8 +32,6 @@ import { FieldIsDependedOnByOtherError } from './errors/field-is-depended-on-by- import { beforeCreateCheckFieldInMySQL } from './hooks/beforeCreateCheckFieldInMySQL'; export class PluginDataSourceMainServer extends Plugin { - public schema: string; - private loadFilter: Filter = {}; setLoadFilter(filter: Filter) { @@ -55,10 +52,6 @@ export class PluginDataSourceMainServer extends Plugin { } async beforeLoad() { - if (this.app.db.inDialect('postgres')) { - this.schema = process.env.COLLECTION_MANAGER_SCHEMA || this.db.options.schema || 'public'; - } - this.app.db.registerRepositories({ CollectionRepository, }); @@ -76,12 +69,6 @@ export class PluginDataSourceMainServer extends Plugin { }, }); - this.app.db.on('collections.beforeCreate', async (model) => { - if (this.app.db.inDialect('postgres') && this.schema && model.get('from') != 'db2cm' && !model.get('schema')) { - model.set('schema', this.schema); - } - }); - this.app.db.on('collections.beforeCreate', beforeCreateForViewCollection(this.db)); this.app.db.on( diff --git a/packages/plugins/@nocobase/plugin-multi-app-share-collection/src/server/__tests__/collection-sync.test.ts b/packages/plugins/@nocobase/plugin-multi-app-share-collection/src/server/__tests__/collection-sync.test.ts index 8a9ecf42d9..7a68661efc 100644 --- a/packages/plugins/@nocobase/plugin-multi-app-share-collection/src/server/__tests__/collection-sync.test.ts +++ b/packages/plugins/@nocobase/plugin-multi-app-share-collection/src/server/__tests__/collection-sync.test.ts @@ -9,7 +9,7 @@ import { BelongsToManyRepository, Database } from '@nocobase/database'; import { AppSupervisor } from '@nocobase/server'; -import { MockServer, createMockServer, isPg } from '@nocobase/test'; +import { createMockServer, isPg, MockServer } from '@nocobase/test'; import * as process from 'process'; describe.runIf(isPg())('enable plugin', () => { @@ -491,6 +491,8 @@ describe.runIf(isPg())('collection sync', () => { context: {}, }); + const mainCollectionInstance = mainDb.getCollection('mainCollection'); + await subApp1.runCommand('restart'); const subAppMainCollectionRecord = await subApp1.db.getRepository('collections').findOne({ @@ -504,7 +506,7 @@ describe.runIf(isPg())('collection sync', () => { const subAppMainCollection = subApp1.db.getCollection('mainCollection'); expect(subAppMainCollection).toBeTruthy(); - expect(subAppMainCollection.options.schema).toBe(mainCollection.options.schema || 'public'); + expect(subAppMainCollection.options.schema).toBe(mainCollectionInstance.collectionSchema()); await mainApp.db.getRepository('fields').create({ values: { From e8c232db2f491480cc85c3740462d1a91869cf11 Mon Sep 17 00:00:00 2001 From: YANG QIA <2013xile@gmail.com> Date: Fri, 1 Nov 2024 11:36:34 +0800 Subject: [PATCH 6/8] fix(api-keys): fix the URL path for API keys settings page (#5562) --- .../plugins/@nocobase/plugin-api-keys/src/client/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/plugins/@nocobase/plugin-api-keys/src/client/index.tsx b/packages/plugins/@nocobase/plugin-api-keys/src/client/index.tsx index cd7b22ea71..37c63e8d41 100644 --- a/packages/plugins/@nocobase/plugin-api-keys/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-api-keys/src/client/index.tsx @@ -8,12 +8,11 @@ */ import { Plugin } from '@nocobase/client'; -import { NAMESPACE } from '../constants'; import { Configuration } from './Configuration'; export class PluginAPIKeysClient extends Plugin { async load() { - this.pluginSettingsManager.add(NAMESPACE, { + this.pluginSettingsManager.add('api-keys', { icon: 'KeyOutlined', title: this.t('API keys'), Component: Configuration, From c639e7d403ee61022cf7b228a5fbaa2dcea7b5ef Mon Sep 17 00:00:00 2001 From: Katherine Date: Fri, 1 Nov 2024 14:28:46 +0800 Subject: [PATCH 7/8] feat: configure fields support serach and filter fields (#5471) * feat: configure fields support serach and filter fields * feat: configure fields support serach and filter fields * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: test * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug * fix: bug --- .../useGetSchemaInitializerMenuItems.test.tsx | 9 + .../components/SchemaInitializerItemGroup.tsx | 8 +- .../SchemaInitializerItemSearchFields.tsx | 180 ++++++++++++++++++ .../useGetSchemaInitializerMenuItems.tsx | 1 + .../@nocobase/plugin-acl/src/server/server.ts | 3 +- 5 files changed, 196 insertions(+), 5 deletions(-) create mode 100644 packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemSearchFields.tsx diff --git a/packages/core/client/src/application/__tests__/schema-initializer/hooks/useGetSchemaInitializerMenuItems.test.tsx b/packages/core/client/src/application/__tests__/schema-initializer/hooks/useGetSchemaInitializerMenuItems.test.tsx index 57a9e9d496..7ead4902f6 100644 --- a/packages/core/client/src/application/__tests__/schema-initializer/hooks/useGetSchemaInitializerMenuItems.test.tsx +++ b/packages/core/client/src/application/__tests__/schema-initializer/hooks/useGetSchemaInitializerMenuItems.test.tsx @@ -71,11 +71,13 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "parent-2-item1-0", "label": "item1", "onClick": [Function], + "style": undefined, }, { "key": "parent-2-item2-1", "label": "item2", "onClick": [Function], + "style": undefined, }, { "associationField": "a.b", @@ -139,6 +141,7 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "group-0-Item 1-0", "label": "Item 1", "onClick": [Function], + "style": undefined, }, ], "key": "group-0", @@ -151,6 +154,7 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "parent-item-group-1-Item 1-0", "label": "Item 1", "onClick": [Function], + "style": undefined, }, ], "key": "parent-item-group-1", @@ -204,6 +208,7 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "submenu-1-SubItem 1-0", "label": "SubItem 1", "onClick": [Function], + "style": undefined, }, ], "key": "submenu-1", @@ -215,6 +220,7 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "submenu-2-SubItem 1-0", "label": "SubItem 1", "onClick": [Function], + "style": undefined, }, ], "key": "submenu-2", @@ -226,6 +232,7 @@ describe('useGetSchemaInitializerMenuItems', () => { "key": "submenu-3-SubItem 1-0", "label": "SubItem 1", "onClick": [Function], + "style": undefined, }, ], "key": "submenu-3", @@ -289,11 +296,13 @@ describe('useSchemaInitializerMenuItems', () => { "key": 1, "label": "item1", "onClick": [Function], + "style": undefined, }, { "key": 2, "label": "item2", "onClick": [Function], + "style": undefined, }, ] `); diff --git a/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemGroup.tsx b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemGroup.tsx index 9181354869..2163255598 100644 --- a/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemGroup.tsx +++ b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemGroup.tsx @@ -15,7 +15,7 @@ import { SchemaInitializerOptions } from '../types'; import { SchemaInitializerChildren } from './SchemaInitializerChildren'; import { SchemaInitializerDivider } from './SchemaInitializerDivider'; import { useSchemaInitializerStyles } from './style'; - +import { useMenuSearch } from './SchemaInitializerItemSearchFields'; export interface SchemaInitializerItemGroupProps { title: string; children?: SchemaInitializerOptions['items']; @@ -45,6 +45,8 @@ export const SchemaInitializerItemGroup: FC = ( * @internal */ export const SchemaInitializerItemGroupInternal = () => { - const itemConfig = useSchemaInitializerItem(); - return ; + const itemConfig: any = useSchemaInitializerItem(); + const searchedChildren = useMenuSearch(itemConfig); + /* eslint-disable react/no-children-prop */ + return ; }; diff --git a/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemSearchFields.tsx b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemSearchFields.tsx new file mode 100644 index 0000000000..3f8866461e --- /dev/null +++ b/packages/core/client/src/application/schema-initializer/components/SchemaInitializerItemSearchFields.tsx @@ -0,0 +1,180 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { uid } from '@formily/shared'; +import { Divider, Empty, Input, MenuProps } from 'antd'; +import React, { useEffect, useMemo, useState, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; + +function getPrefixAndCompare(a, b) { + const prefixA = a.replace(/-displayCollectionFields$/, ''); + const prefixB = b.replace(/-displayCollectionFields$/, ''); + + // 判断 a 是否包含 b,如果包含则返回 false,否则返回 true + return !prefixA.includes(prefixB); +} + +export const SearchFields = ({ value: outValue, onChange, name }) => { + const { t } = useTranslation(); + const [value, setValue] = useState(outValue); + const inputRef = useRef(''); + + // 生成唯一的ID用于区分不同层级的SearchFields + const uniqueId = useRef(`${name || Math.random().toString(10).substr(2, 9)}`); + + useEffect(() => { + setValue(outValue); + }, [outValue]); + + useEffect(() => { + const focusInput = () => { + if ( + document.activeElement?.id !== inputRef.current.input.id && + getPrefixAndCompare(document.activeElement?.id, inputRef.current.input.id) + ) { + inputRef.current?.focus(); + } + }; + + // 观察当前元素是否在视图中 + const observer = new IntersectionObserver((entries) => { + if (entries.some((v) => v.isIntersecting)) { + focusInput(); + } + }); + if (inputRef.current?.input) { + inputRef.current.input.id = uniqueId.current; // 设置唯一ID + observer.observe(inputRef.current.input); + } + + return () => { + observer.disconnect(); + }; + }, []); + + const compositionRef = useRef(false); + + const handleChange = (e: React.ChangeEvent) => { + if (!compositionRef.current) { + onChange(e.target.value); + setValue(e.target.value); + } + }; + const Composition = (e: React.CompositionEvent | any) => { + if (e.type === 'compositionend') { + compositionRef.current = false; + handleChange(e); + } else { + compositionRef.current = true; + } + }; + return ( +
e.stopPropagation()}> + { + e.stopPropagation(); + }} + onChange={handleChange} + onCompositionStart={Composition} + onCompositionEnd={Composition} + onCompositionUpdate={Composition} + /> + +
+ ); +}; + +export const useMenuSearch = (props: { children: any[]; showType?: boolean; hideSearch?: boolean; name?: string }) => { + const { children, showType, hideSearch, name } = props; + const items = children?.concat?.() || []; + const [searchValue, setSearchValue] = useState(null); + + // 处理搜索逻辑 + const limitedSearchedItems = useMemo(() => { + if (!searchValue || searchValue === '') { + return items; + } + const lowerSearchValue = searchValue.toLocaleLowerCase(); + return items.filter( + (item) => + (item.label || item.title) && + String(item.label || item.title) + .toLocaleLowerCase() + .includes(lowerSearchValue), + ); + }, [searchValue, items]); + + // 最终结果项 + const resultItems = useMemo(() => { + const res = []; + if (!hideSearch && (items.length > 10 || searchValue)) { + res.push({ + key: `search-${uid()}`, + Component: () => ( + { + setSearchValue(val); + }} + /> + ), + onClick({ domEvent }) { + domEvent.stopPropagation(); + }, + ...(showType ? { isMenuType: true } : {}), + }); + } + + if (limitedSearchedItems.length > 0) { + res.push(...limitedSearchedItems); + } else { + res.push({ + key: 'empty', + style: { + height: 150, + }, + Component: () => ( +
e.stopPropagation()}> + +
+ ), + ...(showType ? { isMenuType: true } : {}), + }); + } + + return res; + }, [hideSearch, limitedSearchedItems, searchValue, showType]); + + const result = processedResult(resultItems, showType, hideSearch, name); + + return children ? result : undefined; +}; + +// 处理嵌套子菜单 +const processedResult = (resultItems, showType, hideSearch, name) => { + return resultItems.map((item: any) => { + if (['subMenu', 'itemGroup'].includes(item.type)) { + const childItems = useMenuSearch({ + children: item.children, + showType, + hideSearch, + name: item.name, + }); + return { ...item, children: childItems }; + } + return item; + }); +}; diff --git a/packages/core/client/src/application/schema-initializer/hooks/useGetSchemaInitializerMenuItems.tsx b/packages/core/client/src/application/schema-initializer/hooks/useGetSchemaInitializerMenuItems.tsx index 10573983f5..6d374ba82d 100644 --- a/packages/core/client/src/application/schema-initializer/hooks/useGetSchemaInitializerMenuItems.tsx +++ b/packages/core/client/src/application/schema-initializer/hooks/useGetSchemaInitializerMenuItems.tsx @@ -101,6 +101,7 @@ export function useGetSchemaInitializerMenuItems(onClick?: (args: any) => void) onClick: handleClick, } : { + style: item.style, key, label, onClick: handleClick, diff --git a/packages/plugins/@nocobase/plugin-acl/src/server/server.ts b/packages/plugins/@nocobase/plugin-acl/src/server/server.ts index 3b4fd98414..151b89ab83 100644 --- a/packages/plugins/@nocobase/plugin-acl/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-acl/src/server/server.ts @@ -547,7 +547,7 @@ export class PluginACLServer extends Plugin { const hasFilterByTk = (params) => { return JSON.stringify(params).includes('filterByTk'); - } + }; if (!hasFilterByTk(ctx.permission.mergedParams) || !hasFilterByTk(ctx.permission.rawParams)) { await next(); @@ -574,7 +574,6 @@ export class PluginACLServer extends Plugin { }, ); - const withACLMeta = createWithACLMetaMiddleware(); // append allowedActions to list & get response From e383ee733a8c7c193f7b79d598a061aa9bfda2cd Mon Sep 17 00:00:00 2001 From: ChengLei Shao Date: Fri, 1 Nov 2024 15:36:08 +0800 Subject: [PATCH 8/8] Update Dockerfile.pro --- Dockerfile.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.pro b/Dockerfile.pro index cb4d035223..7568d7736f 100644 --- a/Dockerfile.pro +++ b/Dockerfile.pro @@ -41,7 +41,7 @@ RUN cd /app \ && tar -zcf ./nocobase.tar.gz -C /app/my-nocobase-app . FROM node:20.13-bullseye-slim -RUN apt-get update && apt-get install -y nginx +RUN apt-get update && apt-get install -y nginx libaio1 RUN rm -rf /etc/nginx/sites-enabled/default COPY ./docker/nocobase/nocobase.conf /etc/nginx/sites-enabled/nocobase.conf