mirror of
https://gitee.com/nocobase/nocobase.git
synced 2024-11-30 03:08:31 +08:00
Merge branch 'main' into next
This commit is contained in:
commit
41e08c6d29
11
.github/ISSUE_TEMPLATE/bug_report.md
vendored
11
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Note: Please do not clear the contents of the issue template. Items marked with * are required. Issues not filled out according to the template will be closed. -->
|
||||
<!-- 注意:请不要将 issue 模板内容清空,带 * 的项目为必填项,没有按照模板填写的issue将被关闭。-->
|
||||
<!--
|
||||
First off, thank you for reporting bugs.
|
||||
|
||||
Please do not clear the contents of the issue template. Items marked with * are required. Issues not filled out according to the template will be closed.
|
||||
|
||||
Please communicate in English, and post content in other languages to NocoBase Forum https://forum.nocobase.com/. Non-English issues will be closed.
|
||||
-->
|
||||
|
||||
## * Describe the bug
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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/
|
||||
|
||||
## 与众不同之处
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -30,8 +30,7 @@ interface JSONTransformerOptions {
|
||||
|
||||
export class Model<TModelAttributes extends {} = any, TCreationAttributes extends {} = TModelAttributes>
|
||||
extends SequelizeModel<TModelAttributes, TCreationAttributes>
|
||||
implements IModel
|
||||
{
|
||||
implements IModel {
|
||||
public static database: Database;
|
||||
public static collection: Collection;
|
||||
|
||||
@ -47,7 +46,7 @@ export class Model<TModelAttributes extends {} = any, TCreationAttributes extend
|
||||
|
||||
static async sync(options) {
|
||||
const runner = new SyncRunner(this);
|
||||
return runner.runSync(options);
|
||||
return await runner.runSync(options);
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
@ -76,6 +76,7 @@ export class SyncRunner {
|
||||
|
||||
try {
|
||||
const beforeColumns = await this.queryInterface.describeTable(this.tableName, options);
|
||||
await this.checkAutoIncrementField(beforeColumns, options);
|
||||
await this.handlePrimaryKeyBeforeSync(beforeColumns, options);
|
||||
await this.handleUniqueFieldBeforeSync(beforeColumns, options);
|
||||
} catch (e) {
|
||||
@ -94,6 +95,20 @@ export class SyncRunner {
|
||||
return syncResult;
|
||||
}
|
||||
|
||||
async checkAutoIncrementField(beforeColumns, options) {
|
||||
// if there is auto increment field, throw error
|
||||
if (!this.database.isMySQLCompatibleDialect()) {
|
||||
return;
|
||||
}
|
||||
const autoIncrFields = Object.keys(this.rawAttributes).filter((key) => {
|
||||
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;
|
||||
|
@ -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',
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
@ -54,6 +54,11 @@ export class FieldModel extends MagicAttributeModel {
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (transaction) {
|
||||
this.db.on('transactionRollback:' + transaction['id'], async () => {
|
||||
collection.removeField(name);
|
||||
});
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user