@@ -72,6 +72,8 @@ class Browser extends DashboardView {
72
72
filters : new List ( ) ,
73
73
ordering : '-createdAt' ,
74
74
selection : { } ,
75
+ exporting : false ,
76
+ exportingCount : 0 ,
75
77
76
78
data : null ,
77
79
lastMax : - 1 ,
@@ -1296,15 +1298,12 @@ class Browser extends DashboardView {
1296
1298
} ) ;
1297
1299
}
1298
1300
1299
- async confirmExportSelectedRows ( rows ) {
1300
- this . setState ( { rowsToExport : null } ) ;
1301
+ async confirmExportSelectedRows ( rows , type , indentation ) {
1302
+ this . setState ( { rowsToExport : null , exporting : true , exportingCount : 0 } ) ;
1301
1303
const className = this . props . params . className ;
1302
1304
const query = new Parse . Query ( className ) ;
1303
1305
1304
- if ( rows [ '*' ] ) {
1305
- // Export all
1306
- query . limit ( 10000 ) ;
1307
- } else {
1306
+ if ( ! rows [ '*' ] ) {
1308
1307
// Export selected
1309
1308
const objectIds = [ ] ;
1310
1309
for ( const objectId in this . state . rowsToExport ) {
@@ -1314,75 +1313,136 @@ class Browser extends DashboardView {
1314
1313
query . limit ( objectIds . length ) ;
1315
1314
}
1316
1315
1317
- const classColumns = this . getClassColumns ( className , false ) ;
1318
- // create object with classColumns as property keys needed for ColumnPreferences.getOrder function
1319
- const columnsObject = { } ;
1320
- classColumns . forEach ( ( column ) => {
1321
- columnsObject [ column . name ] = column ;
1322
- } ) ;
1323
- // get ordered list of class columns
1324
- const columns = ColumnPreferences . getOrder (
1325
- columnsObject ,
1326
- this . context . applicationId ,
1327
- className
1328
- ) . filter ( column => column . visible ) ;
1316
+ const processObjects = ( objects ) => {
1317
+ const classColumns = this . getClassColumns ( className , false ) ;
1318
+ // create object with classColumns as property keys needed for ColumnPreferences.getOrder function
1319
+ const columnsObject = { } ;
1320
+ classColumns . forEach ( ( column ) => {
1321
+ columnsObject [ column . name ] = column ;
1322
+ } ) ;
1323
+ // get ordered list of class columns
1324
+ const columns = ColumnPreferences . getOrder (
1325
+ columnsObject ,
1326
+ this . context . applicationId ,
1327
+ className
1328
+ ) . filter ( ( column ) => column . visible ) ;
1329
+
1330
+ if ( type === '.json' ) {
1331
+ const element = document . createElement ( 'a' ) ;
1332
+ const file = new Blob (
1333
+ [
1334
+ JSON . stringify (
1335
+ objects . map ( ( obj ) => {
1336
+ const json = obj . _toFullJSON ( ) ;
1337
+ delete json . __type ;
1338
+ return json ;
1339
+ } ) ,
1340
+ null ,
1341
+ indentation ? 2 : null ,
1342
+ ) ,
1343
+ ] ,
1344
+ { type : 'application/json' }
1345
+ ) ;
1346
+ element . href = URL . createObjectURL ( file ) ;
1347
+ element . download = `${ className } .json` ;
1348
+ document . body . appendChild ( element ) ; // Required for this to work in FireFox
1349
+ element . click ( ) ;
1350
+ document . body . removeChild ( element ) ;
1351
+ return ;
1352
+ }
1329
1353
1330
- const objects = await query . find ( { useMasterKey : true } ) ;
1331
- let csvString = columns . map ( column => column . name ) . join ( ',' ) + '\n' ;
1332
- for ( const object of objects ) {
1333
- const row = columns . map ( column => {
1334
- const type = columnsObject [ column . name ] . type ;
1335
- if ( column . name === 'objectId' ) {
1336
- return object . id ;
1337
- } else if ( type === 'Relation' || type === 'Pointer' ) {
1338
- if ( object . get ( column . name ) ) {
1339
- return object . get ( column . name ) . id
1340
- } else {
1341
- return ''
1342
- }
1343
- } else {
1344
- let colValue ;
1345
- if ( column . name === 'ACL' ) {
1346
- colValue = object . getACL ( ) ;
1347
- } else {
1348
- colValue = object . get ( column . name ) ;
1349
- }
1350
- // Stringify objects and arrays
1351
- if ( Object . prototype . toString . call ( colValue ) === '[object Object]' || Object . prototype . toString . call ( colValue ) === '[object Array]' ) {
1352
- colValue = JSON . stringify ( colValue ) ;
1353
- }
1354
- if ( typeof colValue === 'string' ) {
1355
- if ( colValue . includes ( '"' ) ) {
1356
- // Has quote in data, escape and quote
1357
- // If the value contains both a quote and delimiter, adding quotes and escaping will take care of both scenarios
1358
- colValue = colValue . split ( '"' ) . join ( '""' ) ;
1359
- return `"${ colValue } "` ;
1360
- } else if ( colValue . includes ( ',' ) ) {
1361
- // Has delimiter in data, surround with quote (which the value doesn't already contain)
1362
- return `"${ colValue } "` ;
1354
+ let csvString = columns . map ( ( column ) => column . name ) . join ( ',' ) + '\n' ;
1355
+ for ( const object of objects ) {
1356
+ const row = columns
1357
+ . map ( ( column ) => {
1358
+ const type = columnsObject [ column . name ] . type ;
1359
+ if ( column . name === 'objectId' ) {
1360
+ return object . id ;
1361
+ } else if ( type === 'Relation' || type === 'Pointer' ) {
1362
+ if ( object . get ( column . name ) ) {
1363
+ return object . get ( column . name ) . id ;
1364
+ } else {
1365
+ return '' ;
1366
+ }
1363
1367
} else {
1364
- // No quote or delimiter, just include plainly
1365
- return `${ colValue } ` ;
1368
+ let colValue ;
1369
+ if ( column . name === 'ACL' ) {
1370
+ colValue = object . getACL ( ) ;
1371
+ } else {
1372
+ colValue = object . get ( column . name ) ;
1373
+ }
1374
+ // Stringify objects and arrays
1375
+ if (
1376
+ Object . prototype . toString . call ( colValue ) ===
1377
+ '[object Object]' ||
1378
+ Object . prototype . toString . call ( colValue ) === '[object Array]'
1379
+ ) {
1380
+ colValue = JSON . stringify ( colValue ) ;
1381
+ }
1382
+ if ( typeof colValue === 'string' ) {
1383
+ if ( colValue . includes ( '"' ) ) {
1384
+ // Has quote in data, escape and quote
1385
+ // If the value contains both a quote and delimiter, adding quotes and escaping will take care of both scenarios
1386
+ colValue = colValue . split ( '"' ) . join ( '""' ) ;
1387
+ return `"${ colValue } "` ;
1388
+ } else if ( colValue . includes ( ',' ) ) {
1389
+ // Has delimiter in data, surround with quote (which the value doesn't already contain)
1390
+ return `"${ colValue } "` ;
1391
+ } else {
1392
+ // No quote or delimiter, just include plainly
1393
+ return `${ colValue } ` ;
1394
+ }
1395
+ } else if ( colValue === undefined ) {
1396
+ // Export as empty CSV field
1397
+ return '' ;
1398
+ } else {
1399
+ return `${ colValue } ` ;
1400
+ }
1366
1401
}
1367
- } else if ( colValue === undefined ) {
1368
- // Export as empty CSV field
1369
- return '' ;
1370
- } else {
1371
- return `${ colValue } ` ;
1402
+ } )
1403
+ . join ( ',' ) ;
1404
+ csvString += row + '\n' ;
1405
+ }
1406
+
1407
+ // Deliver to browser to download file
1408
+ const element = document . createElement ( 'a' ) ;
1409
+ const file = new Blob ( [ csvString ] , { type : 'text/csv' } ) ;
1410
+ element . href = URL . createObjectURL ( file ) ;
1411
+ element . download = `${ className } .csv` ;
1412
+ document . body . appendChild ( element ) ; // Required for this to work in FireFox
1413
+ element . click ( ) ;
1414
+ document . body . removeChild ( element ) ;
1415
+ } ;
1416
+
1417
+ if ( ! rows [ '*' ] ) {
1418
+ const objects = await query . find ( { useMasterKey : true } ) ;
1419
+ processObjects ( objects ) ;
1420
+ this . setState ( { exporting : false , exportingCount : objects . length } ) ;
1421
+ } else {
1422
+ let batch = [ ] ;
1423
+ query . eachBatch (
1424
+ ( obj ) => {
1425
+ batch . push ( ...obj ) ;
1426
+ if ( batch . length % 10 === 0 ) {
1427
+ this . setState ( { exportingCount : batch . length } ) ;
1372
1428
}
1373
- }
1374
- } ) . join ( ',' ) ;
1375
- csvString += row + '\n' ;
1429
+ const one_gigabyte = Math . pow ( 2 , 30 ) ;
1430
+ const size =
1431
+ new TextEncoder ( ) . encode ( JSON . stringify ( batch ) ) . length /
1432
+ one_gigabyte ;
1433
+ if ( size . length > 1 ) {
1434
+ processObjects ( batch ) ;
1435
+ batch = [ ] ;
1436
+ }
1437
+ if ( obj . length !== 100 ) {
1438
+ processObjects ( batch ) ;
1439
+ batch = [ ] ;
1440
+ this . setState ( { exporting : false , exportingCount : 0 } ) ;
1441
+ }
1442
+ } ,
1443
+ { useMasterKey : true }
1444
+ ) ;
1376
1445
}
1377
-
1378
- // Deliver to browser to download file
1379
- const element = document . createElement ( 'a' ) ;
1380
- const file = new Blob ( [ csvString ] , { type : 'text/csv' } ) ;
1381
- element . href = URL . createObjectURL ( file ) ;
1382
- element . download = `${ className } .csv` ;
1383
- document . body . appendChild ( element ) ; // Required for this to work in FireFox
1384
- element . click ( ) ;
1385
- document . body . removeChild ( element ) ;
1386
1446
}
1387
1447
1388
1448
getClassRelationColumns ( className ) {
@@ -1804,8 +1864,10 @@ class Browser extends DashboardView {
1804
1864
< ExportSelectedRowsDialog
1805
1865
className = { className }
1806
1866
selection = { this . state . rowsToExport }
1867
+ count = { this . state . counts [ className ] }
1868
+ data = { this . state . data }
1807
1869
onCancel = { this . cancelExportSelectedRows }
1808
- onConfirm = { ( ) => this . confirmExportSelectedRows ( this . state . rowsToExport ) }
1870
+ onConfirm = { ( type , indentation ) => this . confirmExportSelectedRows ( this . state . rowsToExport , type , indentation ) }
1809
1871
/>
1810
1872
) ;
1811
1873
}
@@ -1822,6 +1884,11 @@ class Browser extends DashboardView {
1822
1884
< Notification note = { this . state . lastNote } isErrorNote = { false } />
1823
1885
) ;
1824
1886
}
1887
+ else if ( this . state . exporting ) {
1888
+ notification = (
1889
+ < Notification note = { `Exporting ${ this . state . exportingCount } + objects...` } isErrorNote = { false } />
1890
+ ) ;
1891
+ }
1825
1892
return (
1826
1893
< div >
1827
1894
< Helmet >
0 commit comments