chore(action-import): validate association & select field value (#4643)

* chore: validate association value in import

* chore: validate value in select field

* fix: test
This commit is contained in:
ChengLei Shao 2024-06-14 12:02:03 +08:00 committed by GitHub
parent 8260cfc817
commit e56e52ad49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 362 additions and 9 deletions

View File

@ -16,12 +16,22 @@ export class MultipleSelectInterface extends BaseInterface {
const enumConfig = this.options.uiSchema?.enum || [];
return items.map((item) => {
const option = enumConfig.find((option) => option.label === item);
return option ? option.value : item;
if (option) {
return option.value;
}
const valueOption = enumConfig.find((option) => option.value === item);
if (valueOption) {
return valueOption.value;
}
throw new Error(`"${item}" is not a valid option in ${ctx.field.name} field.`);
});
}
toString(value: any, ctx?: any) {
const enumConfig = this.options.uiSchema?.enum || [];
return lodash
.castArray(value)
.map((value) => {

View File

@ -11,9 +11,22 @@ import { BaseInterface } from './base-interface';
export class SelectInterface extends BaseInterface {
async toValue(str: string, ctx?: any): Promise<any> {
if (!str) {
return null;
}
const enumConfig = this.options.uiSchema?.enum || [];
const option = enumConfig.find((item) => item.label === str);
return option?.value || str;
if (option) {
return option.value;
}
const valueOption = enumConfig.find((item) => item.value === str);
if (valueOption) {
return valueOption.value;
}
throw new Error(`"${str}" is not a valid option in ${ctx.field.name} field.`);
}
toString(value: any, ctx?: any) {

View File

@ -26,6 +26,13 @@ export class ToManyInterface extends BaseInterface {
transaction,
});
// check if all items are found
items.forEach((item) => {
if (!targetInstances.find((targetInstance) => targetInstance[filterKey] === item)) {
throw new Error(`"${item}" not found in ${targetCollection.model.name} ${filterKey}`);
}
});
const primaryKeyAttribute = targetCollection.model.primaryKeyAttribute;
return targetInstances.map((targetInstance) => targetInstance[primaryKeyAttribute]);

View File

@ -15,6 +15,10 @@ export class ToOneInterface extends BaseInterface {
}
async toValue(str: string, ctx?: any) {
if (!str) {
return null;
}
const { filterKey, targetCollection, transaction } = ctx;
const targetInstance = await targetCollection.repository.findOne({
@ -24,12 +28,11 @@ export class ToOneInterface extends BaseInterface {
transaction,
});
if (!targetInstance) {
throw new Error(`"${str}" not found in ${targetCollection.model.name} ${filterKey}`);
}
const primaryKeyAttribute = targetCollection.model.primaryKeyAttribute;
if (targetInstance) {
return targetInstance[primaryKeyAttribute];
}
return null;
return targetInstance[primaryKeyAttribute];
}
}

View File

@ -25,6 +25,326 @@ describe('xlsx importer', () => {
await app.destroy();
});
describe('import with select fields', () => {
let User;
beforeEach(async () => {
User = app.db.collection({
name: 'users',
fields: [
{ type: 'string', name: 'name', title: '姓名' },
{
uiSchema: {
enum: [
{
value: '123',
label: 'Label123',
color: 'orange',
},
{
value: '223',
label: 'Label223',
color: 'lime',
},
],
type: 'array',
'x-component': 'Select',
'x-component-props': {
mode: 'multiple',
},
title: 'multi-select',
},
defaultValue: [],
name: 'multiSelect',
type: 'array',
interface: 'multipleSelect',
},
{
uiSchema: {
enum: [
{
value: '123',
label: 'Label123',
color: 'orange',
},
{
value: '223',
label: 'Label223',
color: 'lime',
},
],
type: 'string',
'x-component': 'Select',
title: 'select',
},
name: 'select',
type: 'string',
interface: 'select',
},
],
});
await app.db.sync();
});
it('should import select field with label and value', async () => {
const columns = [
{
dataIndex: ['name'],
defaultTitle: '姓名',
},
{
dataIndex: ['multiSelect'],
defaultTitle: '多选',
},
{
dataIndex: ['select'],
defaultTitle: '单选',
},
];
const templateCreator = new TemplateCreator({
collection: User,
columns,
});
const template = await templateCreator.run();
const worksheet = template.Sheets[template.SheetNames[0]];
XLSX.utils.sheet_add_aoa(worksheet, [['test', 'Label123,223', 'Label123']], {
origin: 'A2',
});
const importer = new XlsxImporter({
collectionManager: app.mainDataSource.collectionManager,
collection: User,
columns,
workbook: template,
});
await importer.run();
expect(await User.repository.count()).toBe(1);
const user = await User.repository.findOne();
expect(user.get('multiSelect')).toEqual(['123', '223']);
expect(user.get('select')).toBe('123');
});
it('should validate values in multiple select field', async () => {
const columns = [
{
dataIndex: ['name'],
defaultTitle: '姓名',
},
{
dataIndex: ['multiSelect'],
defaultTitle: '多选',
},
];
const templateCreator = new TemplateCreator({
collection: User,
columns,
});
const template = await templateCreator.run();
const worksheet = template.Sheets[template.SheetNames[0]];
XLSX.utils.sheet_add_aoa(worksheet, [['test', 'abc']], {
origin: 'A2',
});
const importer = new XlsxImporter({
collectionManager: app.mainDataSource.collectionManager,
collection: User,
columns,
workbook: template,
});
let error;
try {
await importer.run();
} catch (e) {
error = e;
}
expect(error).toBeTruthy();
});
it('should validate values in select field', async () => {
const columns = [
{
dataIndex: ['name'],
defaultTitle: '姓名',
},
{
dataIndex: ['select'],
defaultTitle: '单选',
},
];
const templateCreator = new TemplateCreator({
collection: User,
columns,
});
const template = await templateCreator.run();
const worksheet = template.Sheets[template.SheetNames[0]];
XLSX.utils.sheet_add_aoa(worksheet, [['test', 'abc']], {
origin: 'A2',
});
const importer = new XlsxImporter({
collectionManager: app.mainDataSource.collectionManager,
collection: User,
columns,
workbook: template,
});
let error;
try {
await importer.run();
} catch (e) {
error = e;
}
expect(error).toBeTruthy();
});
});
describe('import with associations', () => {
let User;
let Post;
beforeEach(async () => {
User = app.db.collection({
name: 'users',
fields: [
{
type: 'string',
name: 'name',
},
{
type: 'hasMany',
name: 'posts',
target: 'posts',
interface: 'o2m',
foreignKey: 'userId',
},
],
});
Post = app.db.collection({
name: 'posts',
fields: [
{
type: 'string',
name: 'title',
},
{
type: 'belongsTo',
name: 'user',
target: 'users',
interface: 'm2o',
},
],
});
await app.db.sync();
});
it('should validate to many association', async () => {
const columns = [
{
dataIndex: ['name'],
defaultTitle: '名称',
},
{
dataIndex: ['posts', 'title'],
defaultTitle: '标题',
},
];
const templateCreator = new TemplateCreator({
collection: User,
columns,
});
const template = await templateCreator.run();
const worksheet = template.Sheets[template.SheetNames[0]];
XLSX.utils.sheet_add_aoa(worksheet, [['test', '测试标题']], {
origin: 'A2',
});
const importer = new XlsxImporter({
collectionManager: app.mainDataSource.collectionManager,
collection: User,
columns,
workbook: template,
});
let error;
try {
await importer.run();
} catch (e) {
error = e;
}
expect(error).toBeTruthy();
});
it('should validate to one association', async () => {
const columns = [
{
dataIndex: ['title'],
defaultTitle: '标题',
},
{
dataIndex: ['user', 'name'],
defaultTitle: '用户名',
},
];
const templateCreator = new TemplateCreator({
collection: Post,
columns,
});
const template = await templateCreator.run();
const worksheet = template.Sheets[template.SheetNames[0]];
XLSX.utils.sheet_add_aoa(worksheet, [['test title', 'test user']], {
origin: 'A2',
});
const importer = new XlsxImporter({
collectionManager: app.mainDataSource.collectionManager,
collection: Post,
columns,
workbook: template,
});
let error;
try {
await importer.run();
} catch (e) {
error = e;
}
expect(error).toBeTruthy();
});
});
it('should import china region field', async () => {
const Post = app.db.collection({
name: 'posts',
@ -652,7 +972,7 @@ describe('xlsx importer', () => {
[
['Post1', 'Content1', 'User1', 'Tag1,Tag2', 'Comment1,Comment2'],
['Post2', 'Content2', 'User1', 'Tag2,Tag3', 'Comment3'],
['Post3', 'Content3', 'UserNotExist', 'Tag3,TagNotExist', ''],
['Post3', 'Content3', 'User1', 'Tag3', ''],
['Post4', '', '', ''],
['Post5', null, null, null],
],

View File

@ -188,7 +188,7 @@ export class XlsxImporter extends EventEmitter {
await new Promise((resolve) => setTimeout(resolve, 5));
} catch (error) {
throw new Error(
`failed to import row ${handingRowIndex}, rowData: ${JSON.stringify(rowValues)} message: ${error.message}`,
`failed to import row ${handingRowIndex}, message: ${error.message}, rowData: ${JSON.stringify(rowValues)}`,
{ cause: error },
);
}