Skip to content
Snippets Groups Projects
Commit f4929c2d authored by iaibrys's avatar iaibrys
Browse files

Fix bugs (StudentQuizzes issue & related)

* groups_by_course issue in set_client_visibility
* numerous CoffeeScript -> JS issues around making functions global
* StudentQuizzes, QuizPage
* Update pageload event listener issues
* Add missing import for course.js
parent 5b435c66
No related branches found
No related tags found
No related merge requests found
Pipeline #432 failed
Showing
with 154 additions and 133 deletions
require 'json'
class AnswerEventController < WebsocketRails::BaseController
include AnswerHelper
def initialize_session
......
......@@ -65,13 +65,13 @@ class QuizController < ApplicationController
get_quiz
respond_to do |format|
if quiz_instance.can_change?
quiz_instance.hand_in
quiz_instance.save!
if @quiz_content.can_change?
@quiz_content.hand_in
@quiz_content.save!
format.json { render json: {} }
else
logger.debug("Trying to hand in a quiz which cannot change: #{quiz_instance.end_time} #{quiz_instance.handed_in ? 'already handed in' : 'not handed in'}")
logger.debug("Trying to hand in a quiz which cannot change: #{@quiz_content.end_time} #{@quiz_content.handed_in ? 'already handed in' : 'not handed in'}")
format.json { render json: { error: 'cannot hand in' }, status: 400 }
end
end
......
......@@ -15,7 +15,10 @@
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)
import { ClientDebug, stringifySafe, serializerSafe } from 'dtu_core_assets';
const Rails = require('rails-ujs');
Rails.start();
window.Rails = Rails;
......@@ -31,8 +34,6 @@ require('eonasdan-bootstrap-datetimepicker/src/js/bootstrap-datetimepicker');
require('bootstrap-select');
import { ClientDebug, stringifySafe, serializerSafe } from 'dtu_core_assets';
window.ClientDebug = ClientDebug;
window.stringifySafe = stringifySafe;
window.serializerSafe = serializerSafe;
......@@ -41,6 +42,7 @@ $('.datetimepicker').datetimepicker();
require('../src/answers');
require('../src/code');
require('../src/course');
require('../src/connection');
require('../src/crc');
require('../src/dontknow');
......
......@@ -2,10 +2,8 @@
// https://jsfiddle.net/drzaus/9g5qoxwj/
_.mixin({
shallowDiff(a, b) {
return _.omit(a, (v, k) => b[k] === v);
},
diff(a, b) {
shallowDiff: (a, b) => _.omit(a, (v, k) => b[k] === v),
diff: (a, b) => {
const r = {};
_.each(a, (v, k) => {
if (b[k] === v) return;
......@@ -22,7 +20,8 @@ _.mixin({
},
});
function Answer() {
// TODO: turn into a proper ES6 class
window.Answer = function () {
this.settings = {};
this.quiz_id = function (qid) {
......@@ -45,9 +44,9 @@ function Answer() {
this.settings.fail = f;
return this;
};
}
};
function AjaxAnswer() {
window.AjaxAnswer = function () {
Answer.call(this);
const self = this;
......@@ -61,21 +60,22 @@ function AjaxAnswer() {
url: resource_url,
data: JSON.stringify(answer, null, 2),
})
.done(function (data, textStatus, jqXHR) {
self.settings.success.apply(self, arguments);
.done((data, textStatus, jqXHR) => {
console.dir(arguments);
self.settings.success.call(self, data, textStatus, jqXHR);
})
.fail(function (jqXHR, textStatus, errorThrown) {
.fail((jqXHR, textStatus, errorThrown) => {
self.settings.fail.apply(self, arguments);
});
};
}
};
function WebSocketAnswer() {
window.WebSocketAnswer = function () {
Answer.call(this);
const self = this;
this.setAnswer = function (answer) {
this.setAnswer = (answer) => {
answer = {
new_answer: answer,
quiz_id: this.settings.quiz_id,
......@@ -85,9 +85,9 @@ function WebSocketAnswer() {
window.QUIZ_GLOBALS.dispatcher.trigger('answers.answer', answer, this.settings.success, this.settings.fail);
};
}
};
function getIds(target_node) {
window.getIds = (target_node) => {
const question = target_node.closest('.quiz-question');
const quiz = question.closest('article.quiz');
......@@ -99,9 +99,9 @@ function getIds(target_node) {
target_id: target_node.attr('id'),
target: target_node,
};
}
};
function getModelIndices(ids) {
window.getModelIndices = (ids) => {
const question_idx = _.findIndex(window.QUIZ_GLOBALS.quiz_model.sections, q => q._id && (q._id.$oid === ids.question_id));
const answer_idx = _.findIndex(window.QUIZ_GLOBALS.quiz_model.sections[question_idx].answers, a => a._id && (a._id.$oid === ids.target_id));
......@@ -110,10 +110,10 @@ function getModelIndices(ids) {
question_idx,
answer_idx,
};
}
};
// update the internal model with the one from the server
function updateAnswer(ids, new_answer) {
window.updateAnswer = (ids, new_answer) => {
const model_indices = getModelIndices(ids);
const current_question = window.QUIZ_GLOBALS.quiz_model.sections[model_indices.question_idx];
......@@ -138,11 +138,12 @@ function updateAnswer(ids, new_answer) {
window.QUIZ_GLOBALS.last_edit_time = (new Date());
localStorage.setItem('quiz_model', window.QUIZ_GLOBALS.quiz_model);
}
};
function selfCheckAnswer(ids, server_question) {
window.selfCheckAnswer = (ids, server_question) => {
const model_indices = getModelIndices(ids);
console.log('compare %o %o', window.QUIZ_GLOBALS.quiz_model.sections[model_indices.question_idx], server_question);
const diff = _.diff(window.QUIZ_GLOBALS.quiz_model.sections[model_indices.question_idx], server_question);
console.log('question diffs:');
......@@ -152,9 +153,9 @@ function selfCheckAnswer(ids, server_question) {
}
window.QUIZ_GLOBALS.quiz_model.sections[ids.question_idx] = server_question;
}
};
function setAnswer(e, new_answer) {
window.setAnswer = (e, new_answer) => {
const ids = getIds($((e || window.event).target));
ids.question.removeClass('quiz-question-touched');
......@@ -188,14 +189,14 @@ function setAnswer(e, new_answer) {
.success(success)
.fail(fail)
.setAnswer(answer);
}
};
function mcChange(e) {
window.mcChange = (e) => {
setAnswer(e, ids => ({}));
}
};
function cbChange(e) {
window.cbChange = (e) => {
setAnswer(e, (ids) => {
// ISSUE #105 :
// For reasons unknown, it seems that at least in Chrome checked does not go away when the button is unchecked.
......@@ -218,15 +219,15 @@ function cbChange(e) {
checked: ids.target.is(':checked'),
};
});
}
};
function freeTextChanged(e) {
window.freeTextChanged = (e) => {
setAnswer(e, ids => ({
text: ids.target.val(),
}));
}
};
function freeTextEdit(e) {
window.freeTextEdit = (e) => {
const ids = getIds($((e || window.event).target));
if (window.QUIZ_GLOBALS.hasOwnProperty(`textChangeTimer${ids.question_id}`)) {
......@@ -238,20 +239,20 @@ function freeTextEdit(e) {
delete window.QUIZ_GLOBALS[`textChangeTimer${ids.question_id}`];
freeTextChanged(e);
}, 1000);
}
};
function fillInTheBlankEdit(e) {
window.fillInTheBlankEdit = (e) => {
freeTextEdit(e);
}
};
function surveyEdit(e) {
window.surveyEdit = (e) => {
freeTextEdit(e);
}
};
function fillInTheBlankChanged(e) {
window.fillInTheBlankChanged = (e) => {
freeTextChanged(e);
}
};
function surveyChanged(e) {
window.surveyChanged = (e) => {
freeTextChanged(e);
}
};
function connectionTimeout() {
window.connectionTimeout = function () {
if ($('#connection-timeout-modal').hasClass('in')) {
// modal is already open
// TODO: maybe show some indication of continued checking?
......@@ -8,35 +8,35 @@ function connectionTimeout() {
setTimeout(checkConnection, 2000);
}
}
};
function connectionOk() {
window.connectionOk = function () {
if ($('#connection-timeout-modal').hasClass('in')) {
$('#connection-timeout-modal').modal('toggle');
}
}
};
function checkConnection() {
window.checkConnection = function () {
if (window.QUIZ_GLOBALS.dispatcher.connection_stale()) {
const rec = window.QUIZ_GLOBALS.dispatcher.reconnect();
setTimeout(checkConnection, 2000);
} else {
connectionOk();
}
}
};
function connectionProblem(data) {
window.connectionProblem = function (data) {
console.error('Connection has been lost: ', data);
connectionTimeout();
}
};
function preferredConnection() {
window.preferredConnection = function () {
if (window.QUIZ_GLOBALS.preferred_connection === 'websockets' && window.QUIZ_GLOBALS.dispatcher) return 'websockets';
return 'ajax';
}
};
$(document).on('ready page:load', () => {
$(document).ready(() => {
const pathparts = window.location.pathname.split('/');
if (pathparts[1] !== 'quiz') {
......@@ -78,7 +78,7 @@ $(document).on('ready page:load', () => {
// fix issue with Bootstrap navbar: a navigation from the navbar isn't a real navigation, the page doesn't
// get reloaded. So connections and timers don't go away. So we need to remove them explicitly.
// http://stackoverflow.com/questions/9502510/navigation-bar-that-doesnt-reload
function closePage() {
window.closePage = function () {
if (!window.hasOwnProperty('QUIZ_GLOBALS')) return;
if (window.QUIZ_GLOBALS.dispatcher) {
......@@ -92,7 +92,7 @@ function closePage() {
clearTimeout(window.QUIZ_GLOBALS.timer);
window.QUIZ_GLOBALS.timer = null;
}
}
};
$(window).on('hashchange', () => {
console.log('hash change');
......
......@@ -80,6 +80,6 @@ function onShowHintModal(e) {
// page:load required to deal with turbo links issue: http://stackoverflow.com/questions/18770517/rails-4-how-to-use-document-ready-with-turbo-links
$(document).on('ready page:load', () => {
$(document).ready(() => {
$('#take-hint-modal').on('show.bs.modal', onShowHintModal);
});
import $ from 'jquery';
window.$ = $;
window.jQuery = $;
......@@ -6,7 +6,7 @@
// page:load required to deal with turbo links issue:
// http://stackoverflow.com/questions/18770517/rails-4-how-to-use-document-ready-with-turbo-links
$(document).on('ready page:load', () => {
const pageLoaded = () => {
console.log('ready/page:load');
// Fix issue #176 -- all tables get a bootstrap-striped style
......@@ -22,4 +22,6 @@ $(document).on('ready page:load', () => {
return QuizPage.initPage();
}
return console.log('Not a Feature Page');
});
};
$(document).ready(pageLoaded);
......@@ -2,12 +2,12 @@
function disableInputs() {
$('input').prop('readonly', true);
$('input[role=select').prop('disabled', true);
$('input[role=option').prop('disabled', true);
$('input[role=button').prop('disabled', true);
$('input[role=select]').prop('disabled', true);
$('input[role=option]').prop('disabled', true);
$('input[role=button]').prop('disabled', true);
}
function handInClicked(event) {
window.handInClicked = function (event) {
const quiz_id = $('article.quiz').attr('id');
const success = function (d) {
......@@ -29,9 +29,9 @@ function handInClicked(event) {
})
.done(success)
.fail(fail);
}
};
function handIn() {
window.handIn = function () {
const quiz_id = $('article.quiz').attr('id');
const success = function () {
......@@ -63,7 +63,7 @@ function handIn() {
}
}
function beginQuiz() {
window.beginQuiz = function () {
const quiz_id = $('article.quiz').attr('id');
const back_url = $('#back-button').attr('href');
......@@ -97,9 +97,9 @@ function beginQuiz() {
.done(success)
.fail(fail);
}
}
};
function onHandInModal(e) {
window.onHandInModal = function (e) {
const hintButton = e.relatedTarget;
const modal = e.target;
......@@ -108,9 +108,9 @@ function onHandInModal(e) {
handIn();
});
}
};
function saveQuiz(e) {
window.saveQuiz = function (e) {
const saveButton = e.target;
window.QUIZ_GLOBALS.quiz_model.client_save_time = (new Date()).getTime();
......@@ -130,7 +130,7 @@ function saveQuiz(e) {
});
window.saveAs(blob, 'quiz.json');
}
};
const QuizPage = {
isQuizPage() {
......@@ -145,3 +145,5 @@ const QuizPage = {
$('#hand-in-modal').on('show.bs.modal', onHandInModal);
},
};
window.QuizPage = QuizPage;
......@@ -141,13 +141,14 @@ class QuizTemplates {
}
static selected_quiz_dialog(element, url) {
return QuizTemplates.selected_quiz_operation(quiz_id => {
return QuizTemplates.selected_quiz_operation((quiz_id) => {
// Small hack to leverage Bootstrap remote modal code,
// same as with new quiz
$(element).attr('href', url.replace('QUIZ_ID', quiz_id));
$(element)[0].click();
// alternate method of clicking:
/*
return;
const el = $(element).get(0);
event = new CustomEvent('click', {
......@@ -162,6 +163,8 @@ class QuizTemplates {
Rails.fire($(element).get(0), 'click');
//.attr('data-remote', url.replace('QUIZ_ID', quiz_id))
//.click());
*/
});
}
......
var StudentQuizzes = {
quiz(e) {
class StudentQuizzes {
static quiz(e) {
if (!window.QUIZ_GLOBALS.student_quizzes) { return; }
const quiz_number = parseInt(e.target.parentNode.id.match(/Q(\d+)_actions/)[1], 10);
......@@ -16,9 +16,9 @@ var StudentQuizzes = {
ClientDebug.send('info', `Found no in-progress quizzes for Q${quiz_number}`);
document.location = `/new-quiz/${quiz_number}`;
}
},
}
/* this shouldn't be called, unless the page load failed */
default_click(e) {
static default_click(e) {
console.error('default_click called for quiz. page failed to load');
ClientDebug.send('error', {
msg: 'default_click called for quiz. page failed to load ',
......@@ -26,8 +26,9 @@ var StudentQuizzes = {
url: window.location.href,
});
StudentQuizzes.initPage();
},
showResults(e, instance_id) {
}
static showResults(e, instance_id) {
const instances = StudentQuizzes.allInstances().filter(q => q._id.$oid === instance_id);
console.assert(instances.length === 1);
......@@ -52,11 +53,13 @@ var StudentQuizzes = {
} else {
StudentQuizzes.gotoResults(e, instance_id);
}
},
gotoResults(e, instance_id) {
}
static gotoResults(e, instance_id) {
document.location = `/quiz/${instance_id}`;
},
results(e, quiz_number) {
}
static results(e, quiz_number) {
const modalBody = $('#get-results-modal .modal-body');
modalBody.empty();
......@@ -85,16 +88,16 @@ var StudentQuizzes = {
table += '</table></div>';
modalBody.append(table);
},
showResultsModal(e) {
}
static showResultsModal(e) {
const resultsButton = e.relatedTarget;
const modal = e.target;
const quiz_number = resultsButton.parentNode.id.match(/Q(\d+)_actions/)[1];
StudentQuizzes.results(e, quiz_number);
},
getQuizzes() {
}
static getQuizzes() {
const resource_url = window.location.pathname;
console.log(`Get quiz settings from ${resource_url}`);
......@@ -117,8 +120,8 @@ var StudentQuizzes = {
.fail((jqXHR, textStatus, errorThrown) => {
console.log('failed to get settings');
});
},
updateQuizzes() {
}
static updateQuizzes() {
if (!window.QUIZ_GLOBALS.student_quizzes) { return; }
let quizzes_remaining_to_start = 0;
......@@ -159,13 +162,13 @@ var StudentQuizzes = {
if ((quizzes_remaining_to_start + quizzes_remaining_to_end) > 0) {
setTimeout(StudentQuizzes.updateQuizzes, 1000);
}
},
isStudentPage() {
}
static isStudentPage() {
const parts = window.location.pathname.split('/').filter(d => d);
if (parts.length !== 3) return false;
return (parts[0] === 'courses') && (parts[2] === 'student');
},
getChecksumAndTime(cb) {
}
static getChecksumAndTime(cb) {
const success = function (data) {
const payload = JSON.parse(data.json);
......@@ -203,8 +206,8 @@ var StudentQuizzes = {
})
.done(success)
.fail(fail);
},
updateClock() {
}
static updateClock() {
if (!window.hasOwnProperty('QUIZ_GLOBALS')) return;
const poll_interval = (60 * 1000);
......@@ -222,8 +225,8 @@ var StudentQuizzes = {
// TODO there's still a bit of a race condition when we nav out of the page. It's probably harmless
window.QUIZ_GLOBALS.timer = setTimeout(update, wait_ms > 0 ? wait_ms : 0);
},
initPage() {
}
static initPage() {
console.log('Student Quiz Feature Page');
// remove the default handler, if it exists.
......@@ -243,8 +246,9 @@ var StudentQuizzes = {
}
window.QUIZ_GLOBALS = { student_quizzes: studentQuizzes };
// StudentQuizzes.getQuizzes();
},
allInstances() {
}
static allInstances() {
if (!window.QUIZ_GLOBALS.student_quizzes) {
return [];
}
......@@ -252,12 +256,15 @@ var StudentQuizzes = {
window.QUIZ_GLOBALS.student_quizzes.submitted_quizzes,
window.QUIZ_GLOBALS.student_quizzes.expired_quizzes,
);
},
lastTimeCheck: null,
feedbackAfterText: {
}
static lastTimeCheck = null;
static feedbackAfterText = {
0: '',
1: 'show the number of correct answers',
2: 'show which answers are correct',
3: 'show all the answers',
},
};
};
window.StudentQuizzes = StudentQuizzes;
function getServerTimeRemaining(cb, poll_interval) {
window.getServerTimeRemaining = function (cb, poll_interval) {
const quiz_id = $('article.quiz').attr('id');
const success = function (data) {
......@@ -43,13 +43,13 @@ function getServerTimeRemaining(cb, poll_interval) {
.done(success)
.fail(fail);
}
}
};
function getClientTimeRemaining() {
window.getClientTimeRemaining = function () {
return window.QUIZ_GLOBALS.end_time - (new Date());
}
};
function checkAndUpdateTimeRemaining() {
window.checkAndUpdateTimeRemaining = function () {
if (!window.hasOwnProperty('QUIZ_GLOBALS')) return;
if (!window.QUIZ_GLOBALS.hasOwnProperty('time_remaining_ms')) return;
......@@ -129,4 +129,4 @@ function checkAndUpdateTimeRemaining() {
// TODO there's still a bit of a race condition when we nav out of the page. It's probably harmless
window.QUIZ_GLOBALS.timer = setTimeout(updateTime, wait_ms > 0 ? wait_ms : 0);
}
};
import _ from 'underscore';
window._ = _;
// return date as a string in the date format used by the quiz.
function toQuizString(date) {
window.toQuizString = function (date) {
return `${(`0${date.getDate()}`).slice(-2)}/${(`0${date.getMonth() + 1}`).slice(-2)}-${(`0${date.getYear()}`).slice(-2)} ${(`0${date.getHours()}`).slice(-2)}:${(`0${date.getMinutes()}`).slice(-2)}`;
}
};
function addFullScreenListener(fsListener, errListener) {
if (document.addEventListener) {
......@@ -13,7 +13,7 @@ function addFullScreenListener(fsListener, errListener) {
}
}
function fsListener(e) {
window.fsListener = function (e) {
console.log(document.mozFullScreen);
console.log(document.webkitIsFullScreen || document.mozFullScreen || document.msFullscreenElement);
console.log(e);
......@@ -26,9 +26,9 @@ function fsListener(e) {
$('article.quiz').toggleClass('full-screen-blank', true);
$('#enter-full-screen').modal('show');
}
}
};
function callFullScreenEvent(evt) {
window.callFullScreenEvent = function (evt) {
window.QUIZ_GLOBALS.dispatcher.trigger('quiz.start_fullscreen', {
quiz_id: $('article.quiz').attr('id'),
}, (data) => {
......@@ -36,9 +36,9 @@ function callFullScreenEvent(evt) {
}, (data) => {
console.log('fail');
});
}
};
function exitFullScreen() {
window.exitFullScreen = function () {
element = document;
if (element.exitFullscreen) {
......@@ -50,9 +50,9 @@ function exitFullScreen() {
} else if (element.webkitExitFullscreen) {
element.webkitExitFullscreen();
}
}
};
function requestFullScreen() {
window.requestFullScreen = function () {
if (document.documentElement.requestFullscreen) {
document.documentElement.requestFullscreen();
} else if (document.documentElement.msRequestFullscreen) {
......@@ -64,10 +64,10 @@ function requestFullScreen() {
} else if (document.documentElement.webkitRequestFullscreen) {
document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
}
}
};
// https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
function toggleFullScreen() {
window.toggleFullScreen = function () {
addFullScreenListener(fsListener, (e) => {
console.log(e);
});
......@@ -94,9 +94,9 @@ function toggleFullScreen() {
exitFullScreen();
callFullScreenEvent('quiz.stop_fullscreen');
}
}
};
function render_simple_json_response(response, default_success_msg, default_error_message, extra_fields) {
window.render_simple_json_response = function (response, default_success_msg, default_error_message, extra_fields) {
console.log('response ');
console.log(response);
if ((response.hasOwnProperty('success') && !response.success) || default_error_message) {
......@@ -104,4 +104,4 @@ function render_simple_json_response(response, default_success_msg, default_erro
} else if ((response.hasOwnProperty('success') && response.success) || default_success_msg) {
flash('alert-success', response.message || default_success_msg);
}
}
};
......@@ -85,14 +85,17 @@ class SetQuizVisibilityService
quiz.visible_to_students = vis
if quiz.visible_to_students
x = DTUAuth2::CachedAuthorizationManager.groups_by_course(quiz.course_id)
y = x.select{ |g| (g.acl == 'student') && quiz.group_names.include?(g.group) }
quiz.eligeable_students = y.map{ |g| g.user_id }.uniq
eligeable_students = quiz.group_names.map do |group_name|
groups_by_course[group_name]&.select{ |user| user.acl == 'student'}
end.compact.flatten
Rails.logger.info "Eligeable students #{eligeable_students.ai}"
quiz.eligeable_students = eligeable_students.map{ |g| g.user_id }.uniq
quiz.reset_counts
end
return true
true
end
end
Subproject commit 3f72f87c27ecba4a259a92f3f4af876f1c4b75f6
Subproject commit db050e5ec0f146aba969382fa7839c4dbe3199cc
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment