@@ -72,6 +72,8 @@ class Browser extends DashboardView {
7272 filters : new List ( ) ,
7373 ordering : '-createdAt' ,
7474 selection : { } ,
75+ exporting : false ,
76+ exportingCount : 0 ,
7577
7678 data : null ,
7779 lastMax : - 1 ,
@@ -1296,15 +1298,12 @@ class Browser extends DashboardView {
12961298 } ) ;
12971299 }
12981300
1299- async confirmExportSelectedRows ( rows ) {
1300- this . setState ( { rowsToExport : null } ) ;
1301+ async confirmExportSelectedRows ( rows , type , indentation ) {
1302+ this . setState ( { rowsToExport : null , exporting : true , exportingCount : 0 } ) ;
13011303 const className = this . props . params . className ;
13021304 const query = new Parse . Query ( className ) ;
13031305
1304- if ( rows [ '*' ] ) {
1305- // Export all
1306- query . limit ( 10000 ) ;
1307- } else {
1306+ if ( ! rows [ '*' ] ) {
13081307 // Export selected
13091308 const objectIds = [ ] ;
13101309 for ( const objectId in this . state . rowsToExport ) {
@@ -1314,75 +1313,136 @@ class Browser extends DashboardView {
13141313 query . limit ( objectIds . length ) ;
13151314 }
13161315
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+ }
13291353
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+ }
13631367 } 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+ }
13661401 }
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 } ) ;
13721428 }
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+ ) ;
13761445 }
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 ) ;
13861446 }
13871447
13881448 getClassRelationColumns ( className ) {
@@ -1804,8 +1864,10 @@ class Browser extends DashboardView {
18041864 < ExportSelectedRowsDialog
18051865 className = { className }
18061866 selection = { this . state . rowsToExport }
1867+ count = { this . state . counts [ className ] }
1868+ data = { this . state . data }
18071869 onCancel = { this . cancelExportSelectedRows }
1808- onConfirm = { ( ) => this . confirmExportSelectedRows ( this . state . rowsToExport ) }
1870+ onConfirm = { ( type , indentation ) => this . confirmExportSelectedRows ( this . state . rowsToExport , type , indentation ) }
18091871 />
18101872 ) ;
18111873 }
@@ -1822,6 +1884,11 @@ class Browser extends DashboardView {
18221884 < Notification note = { this . state . lastNote } isErrorNote = { false } />
18231885 ) ;
18241886 }
1887+ else if ( this . state . exporting ) {
1888+ notification = (
1889+ < Notification note = { `Exporting ${ this . state . exportingCount } + objects...` } isErrorNote = { false } />
1890+ ) ;
1891+ }
18251892 return (
18261893 < div >
18271894 < Helmet >
0 commit comments