diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 95ba4829ce3..6501a383329 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,14 +1,19 @@ --- name: Bug report -about: Report a bug to help us improve. Please use discussions for feature requests. +about: Report a bug to help us improve. Please communicate in English, and post content in other languages to NocoBase Forum https://forum.nocobase.com/. title: '' labels: '' assignees: '' --- - - + ## * Describe the bug diff --git a/README.md b/README.md index 892166530b8..cb959ab290b 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,8 @@ https://demo.nocobase.com/new Documents: https://docs.nocobase.com/ -Contact Us: -hello@nocobase.com +Forum: +https://forum.nocobase.com/ ## Distinctive features diff --git a/README.zh-CN.md b/README.zh-CN.md index 1bf204a3de3..7b7fc50ea33 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -27,7 +27,7 @@ NocoBase 是一个极易扩展的开源无代码开发平台。 不必投入几年时间、数百万资金研发,花几分钟时间部署 NocoBase,马上拥有一个私有、可控、极易扩展的无代码开发平台。 中文官网: -https://cn.nocobase.com/ +https://www.nocobase.com/cn 在线体验: https://demo-cn.nocobase.com/new @@ -35,8 +35,8 @@ https://demo-cn.nocobase.com/new 文档: https://docs-cn.nocobase.com/ -联系我们: -hello@nocobase.com +社区: +https://forum.nocobase.com/ ## 与众不同之处 diff --git a/packages/core/database/src/decorators/transaction-decorator.ts b/packages/core/database/src/decorators/transaction-decorator.ts index eb4220e57d7..9195a56b673 100644 --- a/packages/core/database/src/decorators/transaction-decorator.ts +++ b/packages/core/database/src/decorators/transaction-decorator.ts @@ -27,6 +27,17 @@ export function transactionWrapperBuilder(transactionGenerator) { newTransaction = true; } + transaction.afterCommit(() => { + if (transaction.eventCleanupBinded) { + return; + } + + transaction.eventCleanupBinded = true; + if (this.database) { + this.database.removeAllListeners(`transactionRollback:${transaction.id}`); + } + }); + // 需要将 newTransaction 注入到被装饰函数参数内 if (newTransaction) { try { @@ -54,6 +65,11 @@ export function transactionWrapperBuilder(transactionGenerator) { } catch (err) { console.error(err); await transaction.rollback(); + + if (this.database) { + await this.database.emitAsync(`transactionRollback:${transaction.id}`); + await this.database.removeAllListeners(`transactionRollback:${transaction.id}`); + } throw err; } } else { diff --git a/packages/core/database/src/model.ts b/packages/core/database/src/model.ts index 9c56c81cf9a..a7581dece3f 100644 --- a/packages/core/database/src/model.ts +++ b/packages/core/database/src/model.ts @@ -30,8 +30,7 @@ interface JSONTransformerOptions { export class Model extends SequelizeModel - implements IModel -{ + implements IModel { public static database: Database; public static collection: Collection; @@ -47,7 +46,7 @@ export class Model { + return this.rawAttributes[key].autoIncrement; + }); + + if (autoIncrFields.length > 1) { + throw new Error(`Auto increment field can't be more than one: ${autoIncrFields.join(', ')}`); + } + } + async handleUniqueFieldBeforeSync(beforeColumns, options) { if (!this.database.inDialect('sqlite')) { return; diff --git a/packages/plugins/@nocobase/plugin-action-import/src/server/__tests__/xlsx-importer.test.ts b/packages/plugins/@nocobase/plugin-action-import/src/server/__tests__/xlsx-importer.test.ts index e3eafb395b4..d86c5aadc98 100644 --- a/packages/plugins/@nocobase/plugin-action-import/src/server/__tests__/xlsx-importer.test.ts +++ b/packages/plugins/@nocobase/plugin-action-import/src/server/__tests__/xlsx-importer.test.ts @@ -345,6 +345,148 @@ describe('xlsx importer', () => { }); }); + it('should report validation error message on not null validation', async () => { + const User = app.db.collection({ + name: 'users', + fields: [ + { + type: 'string', + name: 'name', + allowNull: false, + }, + { + type: 'string', + name: 'email', + }, + ], + }); + + await app.db.sync(); + + const templateCreator = new TemplateCreator({ + collection: User, + columns: [ + { + dataIndex: ['name'], + defaultTitle: '姓名', + }, + { + dataIndex: ['email'], + defaultTitle: '邮箱', + }, + ], + }); + + const template = await templateCreator.run(); + + const worksheet = template.Sheets[template.SheetNames[0]]; + + XLSX.utils.sheet_add_aoa(worksheet, [[null, 'test@qq.com']], { + origin: 'A2', + }); + + const importer = new XlsxImporter({ + collectionManager: app.mainDataSource.collectionManager, + collection: User, + columns: [ + { + dataIndex: ['name'], + defaultTitle: '姓名', + }, + { + dataIndex: ['email'], + defaultTitle: '邮箱', + }, + ], + workbook: template, + }); + + let error; + + try { + await importer.run(); + } catch (e) { + error = e; + } + + expect(error).toBeTruthy(); + console.log(error.message); + }); + + it('should report validation error message on unique validation', async () => { + const User = app.db.collection({ + name: 'users', + fields: [ + { + type: 'string', + name: 'name', + unique: true, + }, + { + type: 'string', + name: 'email', + }, + ], + }); + + await app.db.sync(); + + const templateCreator = new TemplateCreator({ + collection: User, + columns: [ + { + dataIndex: ['name'], + defaultTitle: '姓名', + }, + { + dataIndex: ['email'], + defaultTitle: '邮箱', + }, + ], + }); + + const template = await templateCreator.run(); + + const worksheet = template.Sheets[template.SheetNames[0]]; + XLSX.utils.sheet_add_aoa( + worksheet, + [ + ['User1', 'test@test.com'], + ['User1', 'test@test.com'], + ], + { + origin: 'A2', + }, + ); + + const importer = new XlsxImporter({ + collectionManager: app.mainDataSource.collectionManager, + collection: User, + columns: [ + { + dataIndex: ['name'], + defaultTitle: '姓名', + }, + { + dataIndex: ['email'], + defaultTitle: '邮箱', + }, + ], + workbook: template, + }); + + let error; + + try { + await importer.run(); + } catch (e) { + error = e; + } + + expect(error).toBeTruthy(); + console.log(error.message); + }); + it('should import china region field', async () => { const Post = app.db.collection({ name: 'posts', diff --git a/packages/plugins/@nocobase/plugin-action-import/src/server/services/xlsx-importer.ts b/packages/plugins/@nocobase/plugin-action-import/src/server/services/xlsx-importer.ts index 4aea0978210..7229447fc30 100644 --- a/packages/plugins/@nocobase/plugin-action-import/src/server/services/xlsx-importer.ts +++ b/packages/plugins/@nocobase/plugin-action-import/src/server/services/xlsx-importer.ts @@ -188,7 +188,9 @@ export class XlsxImporter extends EventEmitter { await new Promise((resolve) => setTimeout(resolve, 5)); } catch (error) { throw new Error( - `failed to import row ${handingRowIndex}, message: ${error.message}, rowData: ${JSON.stringify(rowValues)}`, + `failed to import row ${handingRowIndex}, ${this.renderErrorMessage(error)}, rowData: ${JSON.stringify( + rowValues, + )}`, { cause: error }, ); } @@ -201,6 +203,14 @@ export class XlsxImporter extends EventEmitter { return imported; } + renderErrorMessage(error) { + let message = error.message; + if (error.parent) { + message += `: ${error.parent.message}`; + } + + return message; + } trimString(str: string) { if (typeof str === 'string') { return str.trim(); diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/fields/auto-incr-field.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/fields/auto-incr-field.test.ts new file mode 100644 index 00000000000..4c022cb6742 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/__tests__/fields/auto-incr-field.test.ts @@ -0,0 +1,70 @@ +import Database from '@nocobase/database'; +import { MockServer } from '@nocobase/test'; +import { createApp } from '..'; + +describe('destroy', () => { + let db: Database; + let app: MockServer; + + beforeEach(async () => { + app = await createApp({ + database: { + tablePrefix: '', + }, + }); + db = app.db; + }); + + afterEach(async () => { + await app.destroy(); + }); + + it.runIf(process.env.DB_DIALECT === 'mysql')('should not create auto increment field more than one', async () => { + await db.getRepository('collections').create({ + values: { + name: 'posts', + autoGenId: false, + fields: [ + { + name: 'id', + type: 'integer', + autoIncrement: true, + primaryKey: true, + }, + ], + }, + context: {}, + }); + + const postCollection = db.getCollection('posts'); + expect(postCollection.getField('id')).toBeTruthy(); + + let error = null; + + try { + await db.getRepository('fields').create({ + values: { + name: 'xxx', + type: 'integer', + collectionName: 'posts', + autoIncrement: true, + }, + context: {}, + }); + } catch (e) { + error = e; + } + + expect(error).toBeTruthy(); + expect( + await db.getRepository('fields').count({ + filter: { + collectionName: 'posts', + name: 'xxx', + }, + }), + ).toBe(0); + + expect(postCollection.getField('xxx')).toBeFalsy(); + }); +}); diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/field.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/field.ts index 2acbe446d90..c530389d0e0 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/field.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/server/models/field.ts @@ -54,6 +54,11 @@ export class FieldModel extends MagicAttributeModel { transaction, }); + if (transaction) { + this.db.on('transactionRollback:' + transaction['id'], async () => { + collection.removeField(name); + }); + } return field; }