fix: exporting empty values in nested associations (#6845)

This commit is contained in:
ajie
2025-05-12 08:02:23 +08:00
committed by GitHub
parent 2f2df3cbae
commit f004ead6d0
4 changed files with 133 additions and 10 deletions
+1
View File
@@ -125,6 +125,7 @@ export interface CommonFindOptions extends Transactionable {
sort?: Sort;
context?: any;
tree?: boolean;
hooks?: boolean;
}
export type FindOneOptions = Omit<FindOptions, 'limit'> & {
@@ -440,6 +440,105 @@ describe('export to xlsx with preset', () => {
}
});
it('should export with attachment url field and associations', async () => {
const Post = app.db.collection({
name: 'posts',
fields: [
{ type: 'string', name: 'title' },
{
name: 'attachment1',
type: 'belongsTo',
interface: 'm2o',
onDelete: 'SET NULL',
targetKey: 'id',
target: 'attachments',
uiSchema: {
'x-component-props': {
accept: 'image/*',
multiple: false,
},
'x-component': 'AssociationField',
title: 'attachment1',
},
storage: 'local',
foreignKey: 'f_63ni7i8z7cx',
},
],
});
const Tag = app.db.collection({
name: 'tags',
fields: [
{ type: 'string', name: 'name' },
{
name: 'post',
type: 'belongsTo',
target: 'posts',
targetKey: 'id',
foreignKey: 'aaa',
},
],
});
await app.db.sync();
await Post.repository.create({
values: {
title: 'p1',
attachment1: {
title: 'nocobase-logo1',
filename: '682e5ad037dd02a0fe4800a3e91c283b.png',
extname: '.png',
mimetype: 'image/png',
url: '',
storageId: 1,
},
},
});
await Tag.repository.create({
values: [
{
name: 'tag1',
post: {
id: 1,
},
},
],
});
const exporter = new XlsxExporter({
collectionManager: app.mainDataSource.collectionManager,
collection: Tag,
chunkSize: 10,
columns: [
{ dataIndex: ['name'], defaultTitle: 'Name' },
{
dataIndex: ['post', 'attachment1', 'url'],
defaultTitle: 'URL',
},
],
});
const wb = await exporter.run();
const xlsxFilePath = path.resolve(__dirname, `t_${uid()}.xlsx`);
try {
XLSX.writeFile(wb, xlsxFilePath);
// 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', 'URL']);
const result = sheetData[1];
expect(result[1]).toEqual('/storage/uploads/682e5ad037dd02a0fe4800a3e91c283b.png');
} finally {
fs.unlinkSync(xlsxFilePath);
}
});
it('should export with china region field', async () => {
const Post = app.db.collection({
name: 'posts',
@@ -82,19 +82,21 @@ abstract class BaseExporter<T extends ExportOptions = ExportOptions> extends Eve
}
protected getAppendOptionsFromFields(ctx?) {
const fields = this.options.fields.map((x) => x[0]);
const hasPermissionFields = _.isEmpty(ctx?.permission?.can?.params)
? fields
: _.intersection(ctx?.permission?.can?.params?.appends || [], fields);
return hasPermissionFields
.map((field) => {
const fieldInstance = this.options.collection.getField(field);
return this.options.fields
.filter((fieldPath) => {
const field = fieldPath[0];
const hasPermission =
_.isEmpty(ctx?.permission?.can?.params) || (ctx?.permission?.can?.params?.appends || []).includes(field);
return hasPermission;
})
.map((fieldPath) => {
const fieldInstance = this.options.collection.getField(fieldPath[0]);
if (!fieldInstance) {
throw new Error(`Field "${field}" not found: , please check the fields configuration.`);
throw new Error(`Field "${fieldPath[0]}" not found: , please check the fields configuration.`);
}
if (fieldInstance.isRelationField()) {
return field;
return fieldPath.join('.');
}
return null;
@@ -24,6 +24,7 @@ import StorageTypeLocal from './storages/local';
import StorageTypeS3 from './storages/s3';
import StorageTypeTxCos from './storages/tx-cos';
import { encodeURL } from './utils';
import _ from 'lodash';
export type * from './storages';
@@ -312,7 +313,7 @@ export class PluginFileManagerServer extends Plugin {
this.app.db.interfaceManager.registerInterfaceType('attachment', AttachmentInterface);
this.db.on('afterFind', async (instances) => {
this.db.on('afterFind', async (instances, options) => {
if (!instances) {
return;
}
@@ -321,7 +322,27 @@ export class PluginFileManagerServer extends Plugin {
if (name) {
const collection = this.db.getCollection(name);
if (collection?.name === 'attachments' || collection?.options?.template === 'file') {
let recordMap = null;
if (!options?.attributes?.includes('storageId')) {
// 导出时,不会查出来 storageId,这里兼容重新取一下
const ids = records.map((x) => x.id).filter(Boolean);
const fullRecords = await collection.repository.find({
where: { id: ids },
transaction: options.transaction,
hooks: false,
});
recordMap = _.keyBy(fullRecords, (x) => x.id);
}
for (const record of records) {
if (recordMap) {
const fullRecord = recordMap[record.id];
if (fullRecord) {
record.set('storageId', fullRecord.storageId);
record.set('title', fullRecord.title);
record.set('filename', fullRecord.filename);
record.set('extname', fullRecord.extname);
}
}
const url = await this.getFileURL(record);
const previewUrl = await this.getFileURL(record, true);
record.set('url', url);