@@ -6,6 +6,7 @@ import { BatchHttpLink } from 'apollo-link-batch-http';
66import fetch , { RequestInit , Response as FetchResponse } from 'node-fetch' ;
77import { retry } from '@lifeomic/attempt' ;
88import gql from 'graphql-tag' ;
9+ import cliProgress from 'cli-progress' ;
910
1011import Logger , { createLogger } from 'bunyan-category' ;
1112
@@ -44,8 +45,6 @@ import {
4445import { query , QueryTypes } from './util/query' ;
4546
4647const QUERY_RESULTS_TIMEOUT = 1000 * 60 * 5 ; // Poll s3 location for 5 minutes before timeout.
47- const J1QL_SKIP_COUNT = 250 ;
48- const J1QL_LIMIT_COUNT = 250 ;
4948
5049const JobStatus = {
5150 IN_PROGRESS : 'IN_PROGRESS' ,
@@ -75,10 +74,8 @@ export class FetchError extends Error {
7574 nameForLogging ?: string ;
7675 } ) {
7776 super (
78- `JupiterOne API error. Response not OK (requestName=${
79- options . nameForLogging || '(none)'
80- } , status=${ options . response . status } , url=${ options . url } , method=${
81- options . method
77+ `JupiterOne API error. Response not OK (requestName=${ options . nameForLogging || '(none)'
78+ } , status=${ options . response . status } , url=${ options . url } , method=${ options . method
8279 } ). Response: ${ options . responseBody } `,
8380 ) ;
8481 this . httpStatusCode = options . response . status ;
@@ -327,7 +324,7 @@ export class JupiterOneClient {
327324 const token = this . accessToken ;
328325 this . headers = {
329326 Authorization : `Bearer ${ token } ` ,
330- 'LifeOmic -Account' : this . account ,
327+ 'JupiterOne -Account' : this . account ,
331328 'content-type' : 'application/json' ,
332329 } ;
333330
@@ -351,84 +348,88 @@ export class JupiterOneClient {
351348 async queryV1 (
352349 j1ql : string ,
353350 options : QueryOptions | Record < string , unknown > = { } ,
351+ /**
352+ * include a progress bar to show progress of getting data.
353+ */
354+ showProgress = false ,
354355 /** because this method queries repeatedly with its own LIMIT,
355- * this limits the looping to stop after at least {stopAfter} results are found */
356+ * this limits the looping to stop after at least {stopAfter} results are found
357+ * @deprecated This property is no longer supported.
358+ */
356359 stopAfter = Number . MAX_SAFE_INTEGER ,
357360 /** same as above, this gives more fine-grained control over the starting point of the query,
358361 * since this method controls the `SKIP` clause in the query
362+ * @deprecated This property is no longer supported.
359363 */
360364 startPage = 0 ,
361365 ) {
366+
367+ let cursor : string ;
362368 let complete = false ;
363- let page = startPage ;
364369 let results : any [ ] = [ ] ;
365370
366- while ( ! complete && results . length < stopAfter ) {
367- const j1qlForPage = `${ j1ql } SKIP ${
368- page * J1QL_SKIP_COUNT
369- } LIMIT ${ J1QL_LIMIT_COUNT } `;
371+ const limitCheck = j1ql . match ( / l i m i t ( \d + ) / i) ;
372+
373+ let progress : any ;
370374
375+ do {
371376 const res = await this . graphClient . query ( {
372377 query : QUERY_V1 ,
373378 variables : {
374- query : j1qlForPage ,
379+ query : j1ql ,
375380 deferredResponse : 'FORCE' ,
381+ flags : {
382+ variableResultSize : true
383+ } ,
384+ cursor
376385 } ,
377386 ...options ,
378387 } ) ;
379- page ++ ;
380388 if ( res . errors ) {
381389 throw new Error ( `JupiterOne returned error(s) for query: '${ j1ql } '` ) ;
382390 }
383391
384392 const deferredUrl = res . data . queryV1 . url ;
385393 let status = JobStatus . IN_PROGRESS ;
386- let statusFile ;
394+ let statusFile : any ;
387395 const startTimeInMs = Date . now ( ) ;
388396 do {
389397 if ( Date . now ( ) - startTimeInMs > QUERY_RESULTS_TIMEOUT ) {
390398 throw new Error (
391- `Exceeded request timeout of ${
392- QUERY_RESULTS_TIMEOUT / 1000
399+ `Exceeded request timeout of ${ QUERY_RESULTS_TIMEOUT / 1000
393400 } seconds.`,
394401 ) ;
395402 }
396403 this . logger . trace ( 'Sleeping to wait for JobCompletion' ) ;
397- await sleep ( 200 ) ;
404+ await sleep ( 100 ) ;
398405 statusFile = await networkRequest ( deferredUrl ) ;
399406 status = statusFile . status ;
407+ cursor = statusFile . cursor ;
400408 } while ( status === JobStatus . IN_PROGRESS ) ;
401409
402- let result ;
403- if ( status === JobStatus . COMPLETED ) {
404- result = await networkRequest ( statusFile . url ) ;
405- } else {
406- // JobStatus.FAILED
407- throw new Error (
408- statusFile . error || 'Job failed without an error message.' ,
409- ) ;
410+ if ( status === JobStatus . FAILED ) {
411+ throw new Error ( `JupiterOne returned error(s) for query: '${ statusFile . error } '` ) ;
410412 }
411413
412- const { data } = result ;
414+ const result = statusFile . data ;
413415
414- // data will assume tree shape if you specify 'return tree' in J1QL
415- const isTree = data . vertices && data . edges ;
416+ if ( showProgress && ! limitCheck ) {
417+ if ( results . length === 0 ) {
418+ progress = new cliProgress . SingleBar ( { } , cliProgress . Presets . shades_classic ) ;
419+ progress . start ( Number ( statusFile . totalCount ) , 0 ) ;
420+ }
421+ progress . update ( results . length ) ;
422+ }
423+
424+ if ( result ) {
425+ results = results . concat ( result )
426+ }
416427
417- if ( isTree ) {
428+ if ( status === JobStatus . COMPLETED && ( cursor == null || limitCheck ) ) {
418429 complete = true ;
419- results = data ;
420- } else {
421- // data is array-shaped, possibly paginated
422- if ( data . length < J1QL_SKIP_COUNT ) {
423- complete = true ;
424- }
425- results = results . concat ( data ) ;
426430 }
427- this . logger . debug (
428- { resultsCount : results . length , pageCount : data . length } ,
429- 'Query received page of results' ,
430- ) ;
431- }
431+
432+ } while ( complete === false ) ;
432433 return results ;
433434 }
434435
@@ -887,7 +888,7 @@ export class JupiterOneClient {
887888 const headers = this . headers ;
888889 const response = await makeFetchRequest (
889890 this . apiUrl +
890- `/persister/synchronization/jobs/${ options . syncJobId } /upload` ,
891+ `/persister/synchronization/jobs/${ options . syncJobId } /upload` ,
891892 {
892893 method : 'POST' ,
893894 headers,
0 commit comments