Skip to content

Commit bd83c75

Browse files
abhihari010awhicks
authored andcommitted
Converted get time tracking data to use a range
query instead of fetching all data by day since last fetch date. Also, fixed a bug with exporting exercise date to CSV.
1 parent 5fd2f62 commit bd83c75

File tree

3 files changed

+140
-99
lines changed

3 files changed

+140
-99
lines changed

app/assets/javascripts/odsa_tools.js

Lines changed: 96 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -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) {

app/controllers/course_offerings_controller.rb

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ def get_time_tracking_lookup
279279
}
280280
end
281281

282-
# GET /course_offerings/time_tracking_data/:id
282+
# GET /course_offerings/time_tracking_data/:id/date/:date
283283
def get_time_tracking_data
284284
if current_user.blank?
285285
render :json => {
@@ -303,6 +303,48 @@ def get_time_tracking_data
303303
render :json => userTimeTrackings.as_json()
304304
end
305305

306+
# GET /course_offerings/time_tracking_data/:id/range?from=yyyymmdd&to=yyyymmdd
307+
def get_time_tracking_data_range
308+
if current_user.blank?
309+
render json: {
310+
message: 'You are not logged in. Please make sure your browser is set to allow third-party cookies',
311+
}, status: :forbidden
312+
return
313+
end
314+
315+
course_offering = CourseOffering.find(params[:id])
316+
unless course_offering.is_instructor?(current_user) || current_user.global_role.is_admin?
317+
render json: {
318+
message: "You are not an instructor for this course offering. Your user id: #{current_user.id}",
319+
}, status: :forbidden
320+
return
321+
end
322+
323+
instBook = course_offering.odsa_books.first
324+
325+
from = params[:from] # 'yyyymmdd'
326+
to = params[:to] # 'yyyymmdd'
327+
328+
if from.blank? || to.blank?
329+
render json: { message: "Missing 'from' or 'to' query params" }, status: :bad_request
330+
return
331+
end
332+
333+
userTimeTrackings = OdsaUserTimeTracking.where(
334+
inst_book_id: instBook.id,
335+
session_date: from..to
336+
).select(
337+
'user_id as usr_id,
338+
inst_module_id as mod_id,
339+
inst_chapter_id as ch_id,
340+
total_time as tt,
341+
session_date as dt,
342+
sections_time as st'
343+
)
344+
345+
render json: userTimeTrackings.as_json
346+
end
347+
306348
# -------------------------------------------------------------
307349
# GET /course_offerings/new
308350
def new

config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
post '/course_offerings' => 'course_offerings#create', as: :create_course_offerings
111111
get '/course_offerings/:id' => 'course_offerings#show', as: :show_course_offerings
112112
get '/course_offerings/time_tracking_lookup/:id' => 'course_offerings#get_time_tracking_lookup', as: :get_time_tracking_lookup
113+
get '/course_offerings/time_tracking_data/:id/range' => 'course_offerings#get_time_tracking_data_range', as: :get_time_tracking_data_range
113114
get '/course_offerings/time_tracking_data/:id/date/:date' => 'course_offerings#get_time_tracking_data', as: :get_time_tracking_data
114115
get '/course_offerings/(:user_id)/:inst_section_id/section' => 'course_offerings#find_attempts', as: :find_attempts
115116
get '/course_offerings/:id/modules/:inst_chapter_module_id/progresses' => 'course_offerings#find_module_progresses', as: :find_module_progresses

0 commit comments

Comments
 (0)