11/* eslint-disable valid-jsdoc */
22import fs from 'fs' ;
33import stream from 'stream' ;
4+ import { promisify } from 'util' ;
45
56import PROCESS_STATUS from '../constants/process-status' ;
67import EXPORT_STEP from '../constants/export-step' ;
@@ -322,16 +323,51 @@ export const changeExportStep = (status) => ({
322323 status : status
323324} ) ;
324325
326+ const fetchDocumentCount = async ( dataService , ns , query ) => {
327+ // When there is no filter/limit/skip try to use the estimated count.
328+ if (
329+ ( ! query . filter || Object . keys ( query . filter ) . length < 1 )
330+ && ! query . limit
331+ && ! query . skip
332+ ) {
333+ try {
334+ const runEstimatedDocumentCount = promisify ( dataService . estimatedCount . bind ( dataService ) ) ;
335+ const count = await runEstimatedDocumentCount ( ns , { } ) ;
336+
337+ return count ;
338+ } catch ( estimatedCountErr ) {
339+ // `estimatedDocumentCount` is currently unsupported for
340+ // views and time-series collections, so we can fallback to a full
341+ // count in these cases and ignore this error.
342+ }
343+ }
344+
345+ const runCount = promisify ( dataService . count . bind ( dataService ) ) ;
346+
347+ const count = await runCount (
348+ ns ,
349+ query . filter || { } ,
350+ {
351+ ...( query . limit ? { limit : query . limit } : { } ) ,
352+ ...( query . skip ? { skip : query . skip } : { } )
353+ }
354+ ) ;
355+ return count ;
356+ } ;
357+
325358/**
326359 * Open the export modal.
327360 *
361+ * @param {number } [count] - optional pre supplied count to shortcut and
362+ * avoid a possibly expensive re-count.
363+ *
328364 * Counts the documents to be exported given the current query on modal open to
329365 * provide user with accurate export data
330366 *
331367 * @api public
332368 */
333- export const openExport = ( ) => {
334- return ( dispatch , getState ) => {
369+ export const openExport = ( count ) => {
370+ return async ( dispatch , getState ) => {
335371 const {
336372 ns,
337373 exportData,
@@ -340,12 +376,16 @@ export const openExport = () => {
340376
341377 const spec = exportData . query ;
342378
343- dataService . estimatedCount ( ns , { query : spec . filter } , function ( countErr , count ) {
344- if ( countErr ) {
345- return onError ( countErr ) ;
346- }
347- dispatch ( onModalOpen ( count , spec ) ) ;
348- } ) ;
379+ if ( count ) {
380+ return dispatch ( onModalOpen ( count , spec ) ) ;
381+ }
382+
383+ try {
384+ const docCount = await fetchDocumentCount ( dataService , ns , spec ) ;
385+ dispatch ( onModalOpen ( docCount , spec ) ) ;
386+ } catch ( e ) {
387+ dispatch ( onError ( e ) ) ;
388+ }
349389 } ;
350390} ;
351391
@@ -389,7 +429,7 @@ export const sampleFields = () => {
389429 * @api public
390430 */
391431export const startExport = ( ) => {
392- return ( dispatch , getState ) => {
432+ return async ( dispatch , getState ) => {
393433 const {
394434 ns,
395435 exportData,
@@ -400,87 +440,85 @@ export const startExport = () => {
400440 ? { filter : { } }
401441 : exportData . query ;
402442
443+ const numDocsToExport = exportData . isFullCollection
444+ ? await fetchDocumentCount ( dataService , ns , spec )
445+ : exportData . count ;
446+
403447 // filter out only the fields we want to include in our export data
404448 const projection = Object . fromEntries (
405449 Object . entries ( exportData . fields )
406450 . filter ( ( keyAndValue ) => keyAndValue [ 1 ] === 1 ) ) ;
407451
408- dataService . estimatedCount ( ns , { query : spec . filter } , function ( countErr , numDocsToExport ) {
409- if ( countErr ) {
410- return onError ( countErr ) ;
411- }
452+ debug ( 'count says to expect %d docs in export' , numDocsToExport ) ;
453+ const source = createReadableCollectionStream ( dataService , ns , spec , projection ) ;
412454
413- debug ( 'count says to expect %d docs in export' , numDocsToExport ) ;
414- const source = createReadableCollectionStream ( dataService , ns , spec , projection ) ;
455+ const progress = createProgressStream ( {
456+ objectMode : true ,
457+ length : numDocsToExport ,
458+ time : 250 /* ms */
459+ } ) ;
415460
416- const progress = createProgressStream ( {
417- objectMode : true ,
418- length : numDocsToExport ,
419- time : 250 /* ms */
420- } ) ;
461+ progress . on ( 'progress' , function ( info ) {
462+ dispatch ( onProgress ( info . percentage , info . transferred ) ) ;
463+ } ) ;
421464
422- progress . on ( 'progress' , function ( info ) {
423- dispatch ( onProgress ( info . percentage , info . transferred ) ) ;
424- } ) ;
465+ // Pick the columns that are going to be matched by the projection,
466+ // where some prefix the field (e.g. ['a', 'a.b', 'a.b.c'] for 'a.b.c')
467+ // has an entry in the projection object.
468+ const columns = Object . keys ( exportData . allFields )
469+ . filter ( field => field . split ( '.' ) . some (
470+ ( _part , index , parts ) => projection [ parts . slice ( 0 , index + 1 ) . join ( '.' ) ] ) ) ;
471+ let formatter ;
472+ if ( exportData . fileType === 'csv' ) {
473+ formatter = createCSVFormatter ( { columns } ) ;
474+ } else {
475+ formatter = createJSONFormatter ( ) ;
476+ }
425477
426- // Pick the columns that are going to be matched by the projection,
427- // where some prefix the field (e.g. ['a', 'a.b', 'a.b.c'] for 'a.b.c')
428- // has an entry in the projection object.
429- const columns = Object . keys ( exportData . allFields )
430- . filter ( field => field . split ( '.' ) . some (
431- ( _part , index , parts ) => projection [ parts . slice ( 0 , index + 1 ) . join ( '.' ) ] ) ) ;
432- let formatter ;
433- if ( exportData . fileType === 'csv' ) {
434- formatter = createCSVFormatter ( { columns } ) ;
435- } else {
436- formatter = createJSONFormatter ( ) ;
437- }
478+ const dest = fs . createWriteStream ( exportData . fileName ) ;
438479
439- const dest = fs . createWriteStream ( exportData . fileName ) ;
480+ debug ( 'executing pipeline' ) ;
481+ dispatch ( onStarted ( source , dest , numDocsToExport ) ) ;
482+ stream . pipeline ( source , progress , formatter , dest , function ( err ) {
483+ if ( err ) {
484+ debug ( 'error running export pipeline' , err ) ;
485+ return dispatch ( onError ( err ) ) ;
486+ }
487+ debug (
488+ 'done. %d docs exported to %s' ,
489+ numDocsToExport ,
490+ exportData . fileName
491+ ) ;
492+ dispatch ( onFinished ( numDocsToExport ) ) ;
493+ dispatch (
494+ appRegistryEmit (
495+ 'export-finished' ,
496+ numDocsToExport ,
497+ exportData . fileType
498+ )
499+ ) ;
440500
441- debug ( 'executing pipeline' ) ;
442- dispatch ( onStarted ( source , dest , numDocsToExport ) ) ;
443- stream . pipeline ( source , progress , formatter , dest , function ( err ) {
444- if ( err ) {
445- debug ( 'error running export pipeline' , err ) ;
446- return dispatch ( onError ( err ) ) ;
447- }
448- debug (
449- 'done. %d docs exported to %s' ,
501+ /**
502+ * TODO: lucas: For metrics:
503+ *
504+ * "resource": "Export",
505+ * "action": "completed",
506+ * "file_type": "<csv|json_array>",
507+ * "num_docs": "<how many docs exported>",
508+ * "full_collection": true|false
509+ * "filter": true|false,
510+ * "projection": true|false,
511+ * "skip": true|false,
512+ * "limit": true|false,
513+ * "fields_selected": true|false
514+ */
515+ dispatch (
516+ globalAppRegistryEmit (
517+ 'export-finished' ,
450518 numDocsToExport ,
451- exportData . fileName
452- ) ;
453- dispatch ( onFinished ( numDocsToExport ) ) ;
454- dispatch (
455- appRegistryEmit (
456- 'export-finished' ,
457- numDocsToExport ,
458- exportData . fileType
459- )
460- ) ;
461-
462- /**
463- * TODO: lucas: For metrics:
464- *
465- * "resource": "Export",
466- * "action": "completed",
467- * "file_type": "<csv|json_array>",
468- * "num_docs": "<how many docs exported>",
469- * "full_collection": true|false
470- * "filter": true|false,
471- * "projection": true|false,
472- * "skip": true|false,
473- * "limit": true|false,
474- * "fields_selected": true|false
475- */
476- dispatch (
477- globalAppRegistryEmit (
478- 'export-finished' ,
479- numDocsToExport ,
480- exportData . fileType
481- )
482- ) ;
483- } ) ;
519+ exportData . fileType
520+ )
521+ ) ;
484522 } ) ;
485523 } ;
486524} ;
0 commit comments