diff --git a/apps/nestjs-backend/src/features/export/open-api/export-open-api.service.ts b/apps/nestjs-backend/src/features/export/open-api/export-open-api.service.ts index 3a91bd04f..fbca24ac6 100644 --- a/apps/nestjs-backend/src/features/export/open-api/export-open-api.service.ts +++ b/apps/nestjs-backend/src/features/export/open-api/export-open-api.service.ts @@ -33,11 +33,10 @@ export class ExportOpenApiService { throw new BadRequestException('table is not found'); }); + const fileName = tableRaw?.name ? encodeURIComponent(tableRaw?.name) : 'export'; + response.setHeader('Content-Type', 'text/csv'); - response.setHeader( - 'Content-Disposition', - `attachment; filename=${tableRaw?.name || 'export'}.csv` - ); + response.setHeader('Content-Disposition', `attachment; filename=${fileName}.csv`); csvStream.pipe(response); diff --git a/apps/nestjs-backend/src/features/import/open-api/import.class.ts b/apps/nestjs-backend/src/features/import/open-api/import.class.ts index e7f3f85ec..2169f5877 100644 --- a/apps/nestjs-backend/src/features/import/open-api/import.class.ts +++ b/apps/nestjs-backend/src/features/import/open-api/import.class.ts @@ -51,6 +51,15 @@ export abstract class Importer { public static DEFAULT_COLUMN_TYPE: IValidateTypes = FieldType.SingleLineText; + // order make sence + public static readonly SUPPORTEDTYPE: IValidateTypes[] = [ + FieldType.Checkbox, + FieldType.Number, + FieldType.Date, + FieldType.LongText, + FieldType.SingleLineText, + ]; + constructor(public config: IImportConstructorParams) {} abstract parse( @@ -64,8 +73,6 @@ export abstract class Importer { ] ): Promise; - abstract getSupportedFieldTypes(): IValidateTypes[]; - async getFile() { const { url, type } = this.config; const { body: stream, headers } = await fetch(url); @@ -93,7 +100,7 @@ export abstract class Importer { } async genColumns() { - const supportTypes = this.getSupportedFieldTypes(); + const supportTypes = Importer.SUPPORTEDTYPE; const parseResult = await this.parse(); const result: IAnalyzeVo['worksheets'] = {}; @@ -160,17 +167,6 @@ export abstract class Importer { export class CsvImporter extends Importer { public static readonly CHECK_LINES = 5000; public static readonly DEFAULT_SHEETKEY = 'Import Table'; - // order make sence - public static readonly SUPPORTEDTYPE: IValidateTypes[] = [ - FieldType.Checkbox, - FieldType.Number, - FieldType.Date, - FieldType.LongText, - FieldType.SingleLineText, - ]; - getSupportedFieldTypes() { - return CsvImporter.SUPPORTEDTYPE; - } parse(): Promise; parse( @@ -330,9 +326,6 @@ export class ExcelImporter extends Importer { return parseResult; } - getSupportedFieldTypes() { - return CsvImporter.SUPPORTEDTYPE; - } } export const importerFactory = (type: SUPPORTEDTYPE, config: IImportConstructorParams) => { diff --git a/apps/nestjs-backend/test/table-export.e2e-spec.ts b/apps/nestjs-backend/test/table-export.e2e-spec.ts index 7253aedf6..0063dcfab 100644 --- a/apps/nestjs-backend/test/table-export.e2e-spec.ts +++ b/apps/nestjs-backend/test/table-export.e2e-spec.ts @@ -125,6 +125,45 @@ afterAll(async () => { await app.close(); }); +const createRecordsWithLink = async (mainTableId: string, subTableId: string) => { + return apiCreateRecords(mainTableId, { + records: [ + { + fields: { + ['Attachment field']: [{ ...txtFileData, id: 'actxxxxxx', name: 'test.txt' }], + ['Date field']: '2022-11-28T16:00:00.000Z', + ['Text field']: 'txt1', + ['Number field']: 1, + ['Checkbox field']: true, + ['Select field']: 'x', + ['Link field']: [ + { + id: subTableId, + }, + ], + }, + }, + { + fields: { + ['Date field']: '2022-11-28T16:00:00.000Z', + ['Text field']: 'txt2', + ['Select field']: 'y', + ['User Field']: { + title: 'test', + id: userId, + }, + }, + }, + { + fields: { + ['Select field']: 'z', + ['Checkbox field']: true, + }, + }, + ], + }); +}; + describe('/export/${tableId} OpenAPI ExportController (e2e) Get csv stream from table (Get) ', () => { it(`should return a csv stream from table and compatible all fields`, async () => { const mainTable = await apiCreateTable(baseId, { @@ -197,43 +236,96 @@ describe('/export/${tableId} OpenAPI ExportController (e2e) Get csv stream from }); } - await apiCreateRecords(mainTable.data.id, { + await createRecordsWithLink(mainTable.data.id, subTable.data.records[0].id); + + const exportRes = await apiExportCsvFromTable(mainTable.data.id); + const disposition = exportRes?.headers['content-disposition']; + const contentType = exportRes?.headers['content-type']; + const { data: csvData } = exportRes; + + await apiDeleteTable(baseId, mainTable.data.id); + await apiDeleteTable(baseId, subTable.data.id); + + expect(disposition).toBe(`attachment; filename=${encodeURIComponent(mainTable.data.name)}.csv`); + expect(contentType).toBe('text/csv'); + expect(csvData).toBe( + `Text field,Number field,Checkbox field,Select field,Date field,Attachment field,User Field,Link field,Link field from lookups sub_Name,Link field from lookups sub_Number,Link field from lookups sub_Checkbox,Link field from lookups sub_SingleSelect\r\ntxt1,1,true,x,2022-11-28T16:00:00.000Z,test.txt ${txtFileData.presignedUrl},,Name1,Name1,1,true,sub_y\r\ntxt2,,,y,2022-11-28T16:00:00.000Z,,test,,,,,\r\n,,true,z,,,,,,,,` + ); + }); + + it(`should return a csv stream from table with special character table name`, async () => { + const mainTable = await apiCreateTable(baseId, { + name: '测试😄', + fields: [ + { + type: FieldType.SingleLineText, + name: 'Text field', + }, + ], + records: [], + }); + + for (let i = 0; i < mainFields.length; i++) { + await apiCreateField(mainTable.data.id, mainFields[i]); + } + + const subTable = await apiCreateTable(baseId, { + name: 'subTable', + fields: subFields, records: [ { fields: { - ['Attachment field']: [{ ...txtFileData, id: 'actxxxxxx', name: 'test.txt' }], - ['Date field']: '2022-11-28T16:00:00.000Z', - ['Text field']: 'txt1', - ['Number field']: 1, - ['Checkbox field']: true, - ['Select field']: 'x', - ['Link field']: [ - { - id: subTable.data.records[0].id, - }, - ], + ['sub_Name']: 'Name1', + ['sub_Number']: 1, + ['sub_Checkbox']: true, + ['sub_SingleSelect']: 'sub_y', }, }, { fields: { - ['Date field']: '2022-11-28T16:00:00.000Z', - ['Text field']: 'txt2', - ['Select field']: 'y', - ['User Field']: { - title: 'test', - id: userId, - }, + ['sub_Name']: 'Name2', + ['sub_Number']: 2, + ['sub_Checkbox']: true, + ['sub_SingleSelect']: 'sub_x', }, }, { fields: { - ['Select field']: 'z', - ['Checkbox field']: true, + ['sub_Name']: 'Name3', + ['sub_Number']: 3, }, }, ], }); + const { + data: { id: linkFieldId }, + } = await apiCreateField(mainTable.data.id, { + type: FieldType.Link, + name: 'Link field', + options: { + relationship: Relationship.ManyMany, + foreignTableId: subTable.data.id, + isOneWay: false, + }, + }); + + for (let i = 0; i < subFields.length; i++) { + const { name, type } = subFields[i]; + await apiCreateField(mainTable.data.id, { + name: `Link field from lookups ${name}`, + type: type, + isLookup: true, + lookupOptions: { + foreignTableId: subTable.data.id, + lookupFieldId: subTable.data.fields[i].id, + linkFieldId: linkFieldId, + }, + }); + } + + await createRecordsWithLink(mainTable.data.id, subTable.data.records[0].id); + const exportRes = await apiExportCsvFromTable(mainTable.data.id); const disposition = exportRes?.headers['content-disposition']; const contentType = exportRes?.headers['content-type']; @@ -242,7 +334,7 @@ describe('/export/${tableId} OpenAPI ExportController (e2e) Get csv stream from await apiDeleteTable(baseId, mainTable.data.id); await apiDeleteTable(baseId, subTable.data.id); - expect(disposition).toBe(`attachment; filename=${mainTable.data.name}.csv`); + expect(disposition).toBe(`attachment; filename=${encodeURIComponent(mainTable.data.name)}.csv`); expect(contentType).toBe('text/csv'); expect(csvData).toBe( `Text field,Number field,Checkbox field,Select field,Date field,Attachment field,User Field,Link field,Link field from lookups sub_Name,Link field from lookups sub_Number,Link field from lookups sub_Checkbox,Link field from lookups sub_SingleSelect\r\ntxt1,1,true,x,2022-11-28T16:00:00.000Z,test.txt ${txtFileData.presignedUrl},,Name1,Name1,1,true,sub_y\r\ntxt2,,,y,2022-11-28T16:00:00.000Z,,test,,,,,\r\n,,true,z,,,,,,,,`