Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Fix #96: Add ability to retrieve timing history #132

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 10 additions & 93 deletions public/scripts/history.js
Original file line number Diff line number Diff line change
@@ -1,99 +1,16 @@
// global reference to the history; shouldn't be directly accessed outside
// this file.
//
// If we are using a remote backend cache the value here for easy access once
// we've done an initial retrieval
var _history = null;

// this tracks if we're using local storage or some remote user system to
// keep timing data
var _historyUsesLocalStorage = true;

// the key we store timing data under when in local storage mode
var _historyKey = 'response_history';

///////////////////////////////////////////////////////////////////////////////

// If we are storing things remotely the code to get them goes here
function getRemoteHistory() {
console.error('User accounts not yet implemented.');
return null;
}

// as above except saving
function saveRemoteHistory() {
console.error('User accounts not yet implementhed.');
return false;
}

// ensures that history has been loaded; returns true on success
function loadHistory() {
if (_history !== null) {
return true;
}

if (!_historyUsesLocalStorage) {
var history = getRemoteHistory();
if (history === null) {
console.error('Falling back to local history');
} else {
_history = history;
return true;
}
}

var history = localStorage.getItem(_historyKey);
if (history == null) {
localStorage.setItem(_historyKey, JSON.stringify({}));
history = "{}";
}
_history = JSON.parse(history);
return true;
}

// saves history; returns true on success
function saveHistory() {
if (!_historyUsesLocalStorage) {
if (saveRemoteHistory()) {
return true;
}
console.error('Falling back to local history');
}

localStorage.setItem(_historyKey, JSON.stringify(_history));
return true;
}

// clears historic timing data and saves the cleared state; returns true on
// success
function clearHistory() {
_history = {};
return saveHistory();
}

// returns an array with timing data for a given question number
function getHistory(questionNo) {
if (!loadHistory()) {
console.error('Unable to load history for question ' + questionNo);
async function getHistory(questionNo) {
let url = new URL(document.URL + 'user/answers/question/' + questionNo);

try{
let response = await fetch(url);
let body = await response.json();
return body.previousTimingMs;

}catch(error){
console.error(error);
return [];
}

var record = _history[questionNo];
return !!record ? record : [];
}

// record the time taken to answer a given question based on the question
// number
function recordAnswer(questionNo, timeSpentMS) {
if (!loadHistory()) {
console.error('Unable to record new time for question ' + questionNo);
return false;
}

// check for existing timing data, initialize if none found
if (!_history[questionNo]) {
_history[questionNo] = [];
}
_history[questionNo].push(timeSpentMS);
return true;
}
89 changes: 44 additions & 45 deletions public/scripts/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,19 +280,19 @@ function updateTimingDisplay() {
$('#timing-feedback').html('');
var questionNo = localStorage.getItem('questionNo');
// grab the last bits of timing data
var timings = getHistory(questionNo).slice(-3);

// and then drop them into the boxes
timings.forEach(function(t, idx) {
var element = $('#timing-' + idx);
element.html(t / 1000 + ' sec');
element.show();
})

// hide the boxes if we don't have timing data
for (var i = timings.length; i < 3; i++) {
$('#timing-' + i).hide();
}
getHistory(questionNo).then(timings => {
// and then drop them into the boxes
timings.forEach(function(t, idx) {
var element = $('#timing-' + idx);
element.html(t / 1000 + ' sec');
element.show();
})

// hide the boxes if we don't have timing data
for (var i = timings.length; i < 3; i++) {
$('#timing-' + i).hide();
}
});
}

function onIncorrect() {
Expand All @@ -303,45 +303,44 @@ function onIncorrect() {
};

function handleTimingFeedback(questionNo, curMS) {
var previousTimings = getHistory(questionNo);
if (previousTimings.length == 0) {
return;
}

var average = previousTimings.reduce(
function(acc, cur) { return acc + cur },
0,
) / previousTimings.length;

var delta = average - curMS;

var template = null;
if (delta > 0) {
template = "<br/>You were <span style='color:green;'>faster</span> by ${delta} sec!";
}
if (delta < 0) {
template = "<br/>You were <span style='color:red;'>slower</span> by ${delta} sec.";
}
if (template === null) {
return;
}

// convert MS to S
delta = Math.abs(delta) / 1000;
// now we want to trunate to 2 decimals; the `+` will let us only use 2
// decimals if we actually need them, e.g., we want 1.5 not 1.50
// cf. https://stackoverflow.com/a/12830454
delta = +delta.toFixed(2);
$('#timing-feedback').html(template.replace('${delta}', delta));
getHistory(questionNo).then(previousTimings => {
if (previousTimings.length == 0) {
return;
}

var average = previousTimings.reduce(
function(acc, cur) { return acc + cur },
0,
) / previousTimings.length;

var delta = average - curMS;

var template = null;
if (delta > 0) {
template = "<br/>You were <span style='color:green;'>faster</span> by ${delta} sec!";
}
if (delta < 0) {
template = "<br/>You were <span style='color:red;'>slower</span> by ${delta} sec.";
}
if (template === null) {
return;
}

// convert MS to S
delta = Math.abs(delta) / 1000;
// now we want to trunate to 2 decimals; the `+` will let us only use 2
// decimals if we actually need them, e.g., we want 1.5 not 1.50
// cf. https://stackoverflow.com/a/12830454
delta = +delta.toFixed(2);
$('#timing-feedback').html(template.replace('${delta}', delta));
});
}

// Function to execute when correct keys are pressed.
function onSuccess() {
var questionNo = localStorage.getItem("questionNo");
var thisAnswerMS = Date.now() - questionStartMS;
handleTimingFeedback(questionNo, thisAnswerMS);
recordAnswer(questionNo, thisAnswerMS);
saveHistory();
document.querySelector("#textdiv span").textContent = 'Correct Keys pressed!';
clearPromptKeys();
clearPressedKeys();
Expand Down
24 changes: 23 additions & 1 deletion routes/routes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
const express = require('express');
const router = express.Router();

const{ User, UserAnswers } = require('../JS/orm');
const { User, UserAnswers } = require('../JS/orm');

const ANSWER_HISTORY_LIMIT = 3;


router.get('/', (req, res) => {
res.render('index');
Expand Down Expand Up @@ -38,5 +41,24 @@ router.post('/user/answers/question/:questionNumber', (req, res) => {
})
})

router.get('/user/answers/question/:questionNumber', (req, res) => {
// TODO: When issue #74 be done, the userId should be handled from req object
let userId = 'guest';

UserAnswers.findAll({
where: {question_number: req.params.questionNumber, user_id: userId},
order: [
['created_at', 'DESC']
],
limit: ANSWER_HISTORY_LIMIT
}).then(userAnswers => {
return res.json({
previousTimingMs: userAnswers.map(userAnswer => userAnswer.elapsed_time_ms)
})
}).catch(error => {
console.log(error);
return res.status(500).json(error.errors) // TODO: handle better error messages
})
})

module.exports = router;