@@ -3,7 +3,7 @@ import fs from 'fs';
33import path from 'path' ;
44import type { INestApplication } from '@nestjs/common' ;
55import type { IFieldVo , IViewRo } from '@teable/core' ;
6- import { FieldType , Colors , Relationship , ViewType , DriverClient } from '@teable/core' ;
6+ import { FieldType , Colors , Relationship , ViewType , DriverClient , SortFunc } from '@teable/core' ;
77import type { INotifyVo } from '@teable/openapi' ;
88import {
99 exportCsvFromTable as apiExportCsvFromTable ,
@@ -315,7 +315,7 @@ describe.skipIf(globalThis.testConfig.driver === DriverClient.Sqlite)(
315315 type : ViewType . Grid ,
316316 } ) ;
317317
318- const exportRes = await apiExportCsvFromTable ( mainTable . id , view2 . id ) ;
318+ const exportRes = await apiExportCsvFromTable ( mainTable . id , { viewId : view2 . id } ) ;
319319 const { data : csvData } = exportRes ;
320320
321321 await apiDeleteTable ( baseId , mainTable . id ) ;
@@ -345,7 +345,7 @@ describe.skipIf(globalThis.testConfig.driver === DriverClient.Sqlite)(
345345 type : ViewType . Grid ,
346346 } ) ;
347347
348- const exportRes = await apiExportCsvFromTable ( mainTable . id , view2 . id ) ;
348+ const exportRes = await apiExportCsvFromTable ( mainTable . id , { viewId : view2 . id } ) ;
349349 const { data : csvData } = exportRes ;
350350
351351 await apiDeleteTable ( baseId , mainTable . id ) ;
@@ -355,5 +355,149 @@ describe.skipIf(globalThis.testConfig.driver === DriverClient.Sqlite)(
355355 `Text 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,true,x,"November 28, 2022",test.txt ${ txtFileData . presignedUrl } ,,Name1,Name1,1.00,true,sub_y\r\ntxt2,,y,"November 28, 2022",,test,,,,,\r\n,true,z,,,,,,,,`
356356 ) ;
357357 } ) ;
358+
359+ it ( `should return a csv stream with filter parameter (personal view filter)` , async ( ) => {
360+ const { mainTable, subTable } = await createTables ( ) ;
361+
362+ const textField = mainTable ?. fields ?. find ( ( f ) => f . name === 'Text field' ) as IFieldVo ;
363+
364+ // Export with filter to only include records where Text field = 'txt1'
365+ const exportRes = await apiExportCsvFromTable ( mainTable . id , {
366+ filter : {
367+ conjunction : 'and' ,
368+ filterSet : [
369+ {
370+ fieldId : textField . id ,
371+ operator : 'is' ,
372+ value : 'txt1' ,
373+ } ,
374+ ] ,
375+ } ,
376+ } ) ;
377+ const { data : csvData } = exportRes ;
378+
379+ await apiDeleteTable ( baseId , mainTable . id ) ;
380+ await apiDeleteTable ( baseId , subTable . id ) ;
381+
382+ // Should only contain the first record with txt1
383+ expect ( csvData ) . toBe (
384+ `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.00,true,x,"November 28, 2022",test.txt ${ txtFileData . presignedUrl } ,,Name1,Name1,1.00,true,sub_y`
385+ ) ;
386+ } ) ;
387+
388+ it ( `should return a csv stream with projection parameter (only specified fields)` , async ( ) => {
389+ const { mainTable, subTable } = await createTables ( ) ;
390+
391+ const textField = mainTable ?. fields ?. find ( ( f ) => f . name === 'Text field' ) as IFieldVo ;
392+ const numberField = mainTable ?. fields ?. find ( ( f ) => f . name === 'Number field' ) as IFieldVo ;
393+ const selectField = mainTable ?. fields ?. find ( ( f ) => f . name === 'Select field' ) as IFieldVo ;
394+
395+ // Export with projection to only include specific fields
396+ const exportRes = await apiExportCsvFromTable ( mainTable . id , {
397+ projection : [ textField . id , numberField . id , selectField . id ] ,
398+ } ) ;
399+ const { data : csvData } = exportRes ;
400+
401+ await apiDeleteTable ( baseId , mainTable . id ) ;
402+ await apiDeleteTable ( baseId , subTable . id ) ;
403+
404+ // Should only contain the specified fields in projection order
405+ expect ( csvData ) . toBe ( `Text field,Number field,Select field\r\ntxt1,1.00,x\r\ntxt2,,y\r\n,,z` ) ;
406+ } ) ;
407+
408+ it ( `should return a csv stream with orderBy parameter (sorted export)` , async ( ) => {
409+ const { mainTable, subTable } = await createTables ( ) ;
410+
411+ const textField = mainTable ?. fields ?. find ( ( f ) => f . name === 'Text field' ) as IFieldVo ;
412+
413+ // Export with orderBy to sort by Text field descending
414+ const exportRes = await apiExportCsvFromTable ( mainTable . id , {
415+ orderBy : [
416+ {
417+ fieldId : textField . id ,
418+ order : SortFunc . Desc ,
419+ } ,
420+ ] ,
421+ projection : [ textField . id ] , // Use projection to simplify test assertion
422+ } ) ;
423+ const { data : csvData } = exportRes ;
424+
425+ await apiDeleteTable ( baseId , mainTable . id ) ;
426+ await apiDeleteTable ( baseId , subTable . id ) ;
427+
428+ // Records should be sorted: txt2, txt1, empty
429+ expect ( csvData ) . toBe ( `Text field\r\ntxt2\r\ntxt1\r\n` ) ;
430+ } ) ;
431+
432+ it ( `should return a csv stream with ignoreViewQuery parameter (ignore view filter)` , async ( ) => {
433+ const { mainTable, subTable } = await createTables ( ) ;
434+
435+ const textField = mainTable ?. fields ?. find ( ( f ) => f . name === 'Text field' ) as IFieldVo ;
436+
437+ // Create a view with filter
438+ const viewWithFilter = await createView ( mainTable . id , {
439+ type : ViewType . Grid ,
440+ filter : {
441+ conjunction : 'and' ,
442+ filterSet : [
443+ {
444+ fieldId : textField . id ,
445+ operator : 'is' ,
446+ value : 'txt1' ,
447+ } ,
448+ ] ,
449+ } ,
450+ } ) ;
451+
452+ // Export with ignoreViewQuery=true should return all records despite view filter
453+ const exportRes = await apiExportCsvFromTable ( mainTable . id , {
454+ viewId : viewWithFilter . id ,
455+ ignoreViewQuery : true ,
456+ projection : [ textField . id ] ,
457+ } ) ;
458+ const { data : csvData } = exportRes ;
459+
460+ await apiDeleteTable ( baseId , mainTable . id ) ;
461+ await apiDeleteTable ( baseId , subTable . id ) ;
462+
463+ // Should return all records since view query is ignored
464+ expect ( csvData ) . toBe ( `Text field\r\ntxt1\r\ntxt2\r\n` ) ;
465+ } ) ;
466+
467+ it ( `should return a csv stream with combined filter and projection (personal view scenario)` , async ( ) => {
468+ const { mainTable, subTable } = await createTables ( ) ;
469+
470+ const textField = mainTable ?. fields ?. find ( ( f ) => f . name === 'Text field' ) as IFieldVo ;
471+ const selectField = mainTable ?. fields ?. find ( ( f ) => f . name === 'Select field' ) as IFieldVo ;
472+ const numberField = mainTable ?. fields ?. find ( ( f ) => f . name === 'Number field' ) as IFieldVo ;
473+
474+ // Simulate personal view export with filter + projection + orderBy
475+ const exportRes = await apiExportCsvFromTable ( mainTable . id , {
476+ filter : {
477+ conjunction : 'and' ,
478+ filterSet : [
479+ {
480+ fieldId : selectField . id ,
481+ operator : 'isAnyOf' ,
482+ value : [ 'x' , 'y' ] ,
483+ } ,
484+ ] ,
485+ } ,
486+ projection : [ textField . id , numberField . id , selectField . id ] ,
487+ orderBy : [
488+ {
489+ fieldId : textField . id ,
490+ order : SortFunc . Asc ,
491+ } ,
492+ ] ,
493+ } ) ;
494+ const { data : csvData } = exportRes ;
495+
496+ await apiDeleteTable ( baseId , mainTable . id ) ;
497+ await apiDeleteTable ( baseId , subTable . id ) ;
498+
499+ // Should only return records with select 'x' or 'y', sorted by text field ascending
500+ expect ( csvData ) . toBe ( `Text field,Number field,Select field\r\ntxt1,1.00,x\r\ntxt2,,y` ) ;
501+ } ) ;
358502 }
359503) ;
0 commit comments