@@ -304,10 +304,11 @@ $(function () {
304304
305305 if ( ! _keys . length ) {
306306 resolve ( { data : null , date : null } ) ;
307+ return ;
307308 }
308309 // sort in descending order
309310 _keys . sort ( function ( a , b ) {
310- return a - b ;
311+ return b - a ;
311312 } ) ;
312313
313314 // get the most recent record
@@ -390,60 +391,99 @@ $(function () {
390391 return promise ;
391392 }
392393
393- // Gets visualizations data form the local storage if found. Otherwise get it from the server
394394 function getTimeTrackingData ( odsaStore , lookups , count ) {
395- var currentDate = getTimestamp ( new Date ( ) , "yyyymmdd" ) ;
396- var trackingEndDate = lookups [ "trackingEndDate" ] ;
395+ const currentDate = getTimestamp ( new Date ( ) , "yyyymmdd" ) ;
396+ const trackingEndDate = lookups [ "trackingEndDate" ] ; // 'yyyymmdd'
397397
398- var promise = new Promise ( ( resolve , reject ) => {
398+ return new Promise ( ( resolve , reject ) => {
399399 getStoreData ( odsaStore , "odsaTimeTrackingData" )
400400 . then ( ( result ) => {
401- if (
402- result &&
403- Object . keys ( result ) . includes ( "date" ) &&
404- result [ "date" ] == currentDate
405- ) {
406- resolve ( result [ "data" ] ) ;
407- } else {
408- // else recursively fetch all the days until today and
409- var date = result [ "date" ]
410- ? addDay ( result [ "date" ] )
411- : lookups [ "term" ] [ "starts_on" ] . replace ( / - / g, "" ) ;
401+ // Up-to-date cache
402+ if ( result && result . date === currentDate && result . data ) {
403+ resolve ( result . data ) ;
404+ return ;
405+ }
412406
413- // show the overlay
414- $ . LoadingOverlay ( "show" , {
415- text : "Downloading OpenDSA Analytics Data" ,
416- textResizeFactor : 0.3 ,
417- progress : true ,
418- } ) ;
407+ const hasExistingData = ! ! ( result && result . data ) ;
419408
420- var progressEnd = parseInt ( trackingEndDate ) - parseInt ( date ) ;
421- _getTimeTrackingData (
422- odsaStore ,
423- "/course_offerings/time_tracking_data/" +
424- ODSA_DATA . course_offering_id +
425- "/date/" ,
426- date ,
427- result [ "data" ] ,
428- lookups ,
429- progressEnd ,
430- count
431- )
432- . then ( ( odsaTimeTrackingData ) => {
433- resolve ( odsaTimeTrackingData ) ;
434- } )
435- . catch ( ( err ) => {
436- reject ( err ) ;
437- } ) ;
409+ const lastFetchedDate =
410+ hasExistingData && result . data && result . data . lastFetchedDate
411+ ? result . data . lastFetchedDate
412+ : null ;
413+
414+ const fallbackKeyDate = result ? result . date : null ;
415+
416+ const fromDate =
417+ hasExistingData && ( lastFetchedDate || fallbackKeyDate )
418+ ? addDay ( lastFetchedDate || fallbackKeyDate )
419+ : lookups [ "term" ] [ "starts_on" ] . replace ( / - / g, "" ) ;
420+
421+ // Nothing to fetch (already caught up)
422+ if ( parseInt ( fromDate , 10 ) > parseInt ( trackingEndDate , 10 ) ) {
423+ resolve ( result . data ) ;
424+ return ;
438425 }
426+
427+ _getTimeTrackingDataRange (
428+ odsaStore ,
429+ `/course_offerings/time_tracking_data/${ ODSA_DATA . course_offering_id } /range` ,
430+ fromDate ,
431+ trackingEndDate ,
432+ hasExistingData ? result . data : null ,
433+ lookups ,
434+ count
435+ )
436+ . then ( ( combined ) => resolve ( combined ) )
437+ . catch ( reject ) ;
439438 } )
440- . catch ( ( err ) => {
441- reject ( err ) ;
442- } ) ;
439+ . catch ( reject ) ;
443440 } ) ;
444- return promise ;
445441 }
446442
443+ const _getTimeTrackingDataRange = async function (
444+ odsaStore ,
445+ urlBase ,
446+ fromDate ,
447+ toDate ,
448+ existingStoreData ,
449+ lookups ,
450+ count
451+ ) {
452+ const backoff = 1000 * count ;
453+
454+ $ . LoadingOverlay ( "show" , {
455+ text : "Downloading OpenDSA Analytics Data" ,
456+ textResizeFactor : 0.3 ,
457+ progress : true ,
458+ } ) ;
459+
460+ try {
461+ const res = await fetch ( `${ urlBase } ?from=${ fromDate } &to=${ toDate } ` ) . then (
462+ sleeper ( backoff )
463+ ) ;
464+
465+ if ( ! res . ok ) {
466+ const text = await res . text ( ) ;
467+ throw new Error ( `Range fetch failed (${ res . status } ): ${ text } ` ) ;
468+ }
469+
470+ const rows = await res . json ( ) ; // array of {usr_id, mod_id, ch_id, tt, dt, st}
471+
472+ // Merge new rows into existing aggregated object
473+ let combined = formatTimeTrackingData ( existingStoreData , rows , lookups ) ;
474+ combined = enrichStoreData ( combined ) ;
475+
476+ combined . lastFetchedDate = toDate ;
477+ await updateStoreData ( odsaStore , "odsaTimeTrackingData" , combined ) ;
478+
479+ $ . LoadingOverlay ( "hide" ) ;
480+ return combined ;
481+ } catch ( err ) {
482+ $ . LoadingOverlay ( "hide" ) ;
483+ throw err ;
484+ }
485+ } ;
486+
447487 // Adds one day to the given date and returns a new date
448488 function addDay ( date ) {
449489 //TODO: refactor
@@ -463,54 +503,6 @@ $(function () {
463503 } ;
464504 }
465505
466- // Gets time tracking data form the server for one day
467- const _getTimeTrackingDataPerDay = async function ( url , date , backoff ) {
468- var data = await fetch ( url + date )
469- . then ( sleeper ( backoff ) )
470- . then ( ( res ) => res . json ( ) ) ;
471- return data ;
472- } ;
473-
474- // Gets time tracking data recursively form the server
475- const _getTimeTrackingData = async function (
476- odsaStore ,
477- url ,
478- date ,
479- storeData ,
480- lookups ,
481- progressEnd ,
482- count
483- ) {
484- var backoff = 1000 * count ;
485- var storeData = storeData || null ;
486- var trackingEndDate = lookups [ "trackingEndDate" ] ;
487- var progress =
488- 100 - ( ( parseInt ( trackingEndDate ) - parseInt ( date ) ) * 100 ) / progressEnd ;
489- var progressPercent = progress . toFixed ( 2 ) ;
490-
491- if ( parseInt ( date ) <= parseInt ( trackingEndDate ) ) {
492- var text = `${ progressPercent } % Downloading OpenDSA Analytics Data.` ;
493- $ . LoadingOverlay ( "progress" , progress ) ;
494- $ . LoadingOverlay ( "text" , text ) ;
495- var data = await _getTimeTrackingDataPerDay ( url , date , backoff ) ;
496- storeData = formatTimeTrackingData ( storeData , data , lookups ) ;
497- return await _getTimeTrackingData (
498- odsaStore ,
499- url ,
500- addDay ( date ) ,
501- storeData ,
502- lookups ,
503- progressEnd ,
504- count
505- ) ;
506- } else {
507- storeData = enrichStoreData ( storeData ) ;
508- updateStoreData ( odsaStore , "odsaTimeTrackingData" , storeData ) ;
509- $ . LoadingOverlay ( "hide" ) ;
510- return storeData ;
511- }
512- } ;
513-
514506 // Calculates q1, median, and q3 for array of numbers
515507 function stats ( arr ) {
516508 var sortedArr = [ ...arr ] . sort ( Plotly . d3 . ascending ) ;
@@ -3010,18 +3002,24 @@ $(function () {
30103002 a . remove ( ) ;
30113003 }
30123004
3013- // ONE click handler (matches HAML id)
3014- $ ( document )
3015- . off ( "click" , "#btn-exercise-overview-csv" )
3016- . on ( "click" , "#btn-exercise-overview-csv" , async function ( e ) {
3005+ $ ( "#btn-exercise-overview-csv" )
3006+ . off ( "click.exportcsv" )
3007+ . on ( "click.exportcsv" , async function ( e ) {
30173008 e . preventDefault ( ) ;
3009+
30183010 if ( $ ( this ) . prop ( "disabled" ) ) return ;
3019- var sectionId = getSelectedExerciseSectionId ( ) ;
3011+
3012+ const sectionId = getSelectedExerciseSectionId ( ) ;
30203013 if ( ! sectionId ) {
30213014 alert ( "Pick an exercise first." ) ;
30223015 return ;
30233016 }
3024- await exportExerciseCSV ( sectionId ) ;
3017+
3018+ try {
3019+ await exportExerciseCSV ( sectionId ) ;
3020+ } catch ( err ) {
3021+ console . error ( "EXPORT failed:" , err ) ;
3022+ }
30253023 } ) ;
30263024
30273025 function handleModuleDisplay ( mod_id ) {
0 commit comments