From 04a73e6707637571c38dfe6c217325e2fe8d4a85 Mon Sep 17 00:00:00 2001 From: gchust Date: Tue, 24 Sep 2024 09:54:26 +0800 Subject: [PATCH] fix: during export action, fields from referenced tables are not rendered by field interface (#5296) * fix: during export action, fields from referenced tables are not rendered by field interface * chore: add test case for associations interface --- .../server/__tests__/export-to-xlsx.test.ts | 145 ++++++++++++++++++ .../src/server/xlsx-exporter.ts | 40 ++--- 2 files changed, 160 insertions(+), 25 deletions(-) diff --git a/packages/plugins/@nocobase/plugin-action-export/src/server/__tests__/export-to-xlsx.test.ts b/packages/plugins/@nocobase/plugin-action-export/src/server/__tests__/export-to-xlsx.test.ts index 9887d35763..a8ce50061f 100644 --- a/packages/plugins/@nocobase/plugin-action-export/src/server/__tests__/export-to-xlsx.test.ts +++ b/packages/plugins/@nocobase/plugin-action-export/src/server/__tests__/export-to-xlsx.test.ts @@ -1045,4 +1045,149 @@ describe('export to xlsx', () => { fs.unlinkSync(xlsxFilePath); } }); + + it('should export with different ui schema in associations', async () => { + const User = app.db.collection({ + name: 'users', + fields: [ + { type: 'string', name: 'name' }, + { type: 'integer', name: 'age' }, + { + type: 'hasMany', + name: 'posts', + target: 'posts', + }, + { + type: 'belongsToMany', + name: 'groups', + target: 'groups', + through: 'usersGroups', + }, + { + name: 'createdAt', + type: 'date', + interface: 'createdAt', + field: 'createdAt', + uiSchema: { + type: 'datetime', + title: '{{t("Created at")}}', + 'x-component': 'DatePicker', + 'x-component-props': {}, + 'x-read-pretty': true, + }, + }, + ], + }); + + const Post = app.db.collection({ + name: 'posts', + fields: [ + { type: 'string', name: 'title' }, + { type: 'belongsTo', name: 'user', target: 'users' }, + ], + }); + + const Group = app.db.collection({ + name: 'groups', + fields: [ + { type: 'string', name: 'name' }, + { + type: 'integer', + name: 'testInterface', + interface: 'testInterface', + title: 'Associations interface 测试', + uiSchema: { test: 'testValue' }, + }, + ], + }); + + class TestInterface extends BaseInterface { + toString(value, ctx) { + return `${this.options.uiSchema.test}.${value}`; + } + } + + app.db.interfaceManager.registerInterfaceType('testInterface', TestInterface); + + await app.db.sync(); + + const [group1, group2, group3] = await Group.repository.create({ + values: [ + { name: 'group1', testInterface: 1 }, + { name: 'group2', testInterface: 2 }, + { name: 'group3', testInterface: 3 }, + ], + }); + + const values = Array.from({ length: 22 }).map((_, index) => { + return { + name: `user${index}`, + age: index % 100, + groups: [ + { + id: group1.get('id'), + }, + { + id: group2.get('id'), + }, + { + id: group3.get('id'), + }, + ], + posts: Array.from({ length: 3 }).map((_, postIndex) => { + return { + title: `post${postIndex}`, + }; + }), + }; + }); + + await User.repository.create({ + values, + }); + + const exporter = new XlsxExporter({ + collectionManager: app.mainDataSource.collectionManager, + collection: User, + chunkSize: 10, + columns: [ + { dataIndex: ['name'], defaultTitle: 'Name' }, + { + dataIndex: ['groups', 'testInterface'], + defaultTitle: 'Test Field', + }, + ], + }); + + const wb = await exporter.run(); + + const xlsxFilePath = path.resolve(__dirname, `t_${uid()}.xlsx`); + try { + await new Promise((resolve, reject) => { + XLSX.writeFileAsync( + xlsxFilePath, + wb, + { + type: 'array', + }, + () => { + resolve(123); + }, + ); + }); + + // read xlsx file + const workbook = XLSX.readFile(xlsxFilePath); + const firstSheet = workbook.Sheets[workbook.SheetNames[0]]; + const sheetData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 }); + + const header = sheetData[0]; + expect(header).toEqual(['Name', 'Associations interface 测试']); + + const firstUser = sheetData[1]; + expect(firstUser).toEqual(['user0', 'testValue.1,testValue.2,testValue.3']); + } finally { + fs.unlinkSync(xlsxFilePath); + } + }); }); diff --git a/packages/plugins/@nocobase/plugin-action-export/src/server/xlsx-exporter.ts b/packages/plugins/@nocobase/plugin-action-export/src/server/xlsx-exporter.ts index 71201bb952..c46043efd7 100644 --- a/packages/plugins/@nocobase/plugin-action-export/src/server/xlsx-exporter.ts +++ b/packages/plugins/@nocobase/plugin-action-export/src/server/xlsx-exporter.ts @@ -152,7 +152,7 @@ class XlsxExporter { if (dataIndex.length > 1) { let targetCollection: ICollection; - for (let i = 0; i < dataIndex.length - 1; i++) { + for (let i = 0; i < dataIndex.length; i++) { const isLast = i === dataIndex.length - 1; if (isLast) { @@ -185,42 +185,32 @@ class XlsxExporter { return value; } + private getFieldRenderer(field?: IField, ctx?): (value) => any { + const InterfaceClass = this.options.collectionManager.getFieldInterface(field?.options?.interface); + if (!InterfaceClass) { + return this.renderRawValue; + } + const fieldInternface = new InterfaceClass(field?.options); + return (value) => fieldInternface.toString(value, ctx); + } + private renderCellValue(rowData: IModel, column: ExportColumn, ctx?) { const { dataIndex } = column; rowData = rowData.toJSON(); const value = rowData[dataIndex[0]]; + const field = this.findFieldByDataIndex(dataIndex); + const render = this.getFieldRenderer(field, ctx); if (dataIndex.length > 1) { const deepValue = deepGet(rowData, dataIndex); if (Array.isArray(deepValue)) { - return deepValue.join(','); + return deepValue.map(render).join(','); } - return deepValue; + return render(deepValue); } - - const field = this.findFieldByDataIndex(dataIndex); - - if (!field) { - return this.renderRawValue(value); - } - - const fieldOptions = field.options; - const interfaceName = fieldOptions['interface']; - - if (!interfaceName) { - return this.renderRawValue(value); - } - - const InterfaceClass = this.options.collectionManager.getFieldInterface(interfaceName); - - if (!InterfaceClass) { - return this.renderRawValue(value); - } - - const interfaceInstance = new InterfaceClass(fieldOptions); - return interfaceInstance.toString(value, ctx); + return render(value); } }