From 26b9edabb498b9f0c60aee28fa5b8839ffa0a4bc Mon Sep 17 00:00:00 2001 From: Sebastian Serth Date: Fri, 8 May 2020 15:07:02 +0200 Subject: [PATCH] Add deadline information to submission page and some minor bugfixes --- app/assets/javascripts/editor/editor.js.erb | 1 + app/assets/javascripts/editor/evaluation.js | 395 +++++++++++-------- app/controllers/exercises_controller.rb | 7 +- app/views/exercises/_editor_output.html.slim | 4 + config/locales/de.yml | 12 +- config/locales/en.yml | 10 +- 6 files changed, 251 insertions(+), 178 deletions(-) diff --git a/app/assets/javascripts/editor/editor.js.erb b/app/assets/javascripts/editor/editor.js.erb index f92c7309..a58afae9 100644 --- a/app/assets/javascripts/editor/editor.js.erb +++ b/app/assets/javascripts/editor/editor.js.erb @@ -793,6 +793,7 @@ var CodeOceanEditor = { this.renderScore(); this.showFirstFile(); this.resizeAceEditors(); + this.initializeDeadlines(); window.addEventListener("beforeunload", this.unloadAutoSave.bind(this)); window.addEventListener("page:before-change", this.unloadAutoSave.bind(this)); diff --git a/app/assets/javascripts/editor/evaluation.js b/app/assets/javascripts/editor/evaluation.js index e2959c73..186edc44 100644 --- a/app/assets/javascripts/editor/evaluation.js +++ b/app/assets/javascripts/editor/evaluation.js @@ -1,183 +1,234 @@ CodeOceanEditorEvaluation = { - chunkBuffer: [{streamedResponse: true}], + chunkBuffer: [{streamedResponse: true}], - /** - * Scoring-Functions - */ - scoreCode: function (event) { - event.preventDefault(); - this.clearScoringOutput(); - $('#submit').addClass("d-none"); - this.createSubmission('#assess', null, function (response) { - this.showSpinner($('#assess')); - $('#score_div').removeClass('d-none'); - var url = response.score_url; - this.initializeSocketForScoring(url); - }.bind(this)); - }, + /** + * Scoring-Functions + */ + scoreCode: function (event) { + event.preventDefault(); + this.clearScoringOutput(); + $('#submit').addClass("d-none"); + this.createSubmission('#assess', null, function (response) { + this.showSpinner($('#assess')); + $('#score_div').removeClass('d-none'); + var url = response.score_url; + this.initializeSocketForScoring(url); + }.bind(this)); + }, - handleScoringResponse: function (results) { - this.printScoringResults(results); - var score = _.reduce(results, function (sum, result) { - return sum + result.score * result.weight; - }, 0).toFixed(2); - $('#score').data('score', score); - this.renderScore(); - $('#submit').removeClass("d-none"); - }, + handleScoringResponse: function (results) { + this.printScoringResults(results); + var score = _.reduce(results, function (sum, result) { + return sum + result.score * result.weight; + }, 0).toFixed(2); + $('#score').data('score', score); + this.renderScore(); + this.showSubmitButton(); + }, - printScoringResult: function (result, index) { - $('#results').show(); - var card = $('#dummies').children().first().clone(); - if (card.isPresent()) { - // the card won't be present if @embed_options[:hide_test_results] == true - this.populateCard(card, result, index); - $('#results ul').first().append(card); - } - }, + showSubmitButton: function () { + if (this.submission_deadline || this.late_submission_deadline) { + const now = new Date(); + if (now <= this.submission_deadline) { + // before_deadline + // default is btn-success, so no change in color + $('#submit').get(0).lastChild.nodeValue = I18n.t('exercises.editor.submit_on_time'); + } else if (now > this.submission_deadline && this.late_submission_deadline && now <= this.late_submission_deadline) { + // within_grace_period + $('#submit').removeClass("btn-success btn-warning").addClass("btn-warning"); + $('#submit').get(0).lastChild.nodeValue = I18n.t('exercises.editor.submit_within_grace_period'); + } else if (this.late_submission_deadline && now > this.late_submission_deadline || now > this.submission_deadline) { + // after_late_deadline + debugger; + $('#submit').removeClass("btn-success btn-warning btn-danger").addClass("btn-danger"); + $('#submit').get(0).lastChild.nodeValue = I18n.t('exercises.editor.submit_after_late_deadline'); + } + } + $('#submit').removeClass("d-none"); + }, - printScoringResults: function (response) { - $('#results ul').first().html(''); - $('.test-count .number').html(response.length); - this.clearOutput(); + printScoringResult: function (result, index) { + $('#results').show(); + var card = $('#dummies').children().first().clone(); + if (card.isPresent()) { + // the card won't be present if @embed_options[:hide_test_results] == true + this.populateCard(card, result, index); + $('#results ul').first().append(card); + } + }, - _.each(response, function (result, index) { - this.printOutput(result, false, index); - this.printScoringResult(result, index); - }.bind(this)); + printScoringResults: function (response) { + $('#results ul').first().html(''); + $('.test-count .number').html(response.length); + this.clearOutput(); - if (_.some(response, function (result) { - return result.status === 'timeout'; + _.each(response, function (result, index) { + this.printOutput(result, false, index); + this.printScoringResult(result, index); + }.bind(this)); + + if (_.some(response, function (result) { + return result.status === 'timeout'; })) { - this.showTimeoutMessage(); - } - if (_.some(response, function (result) { - return result.status === 'container_depleted'; + this.showTimeoutMessage(); + } + if (_.some(response, function (result) { + return result.status === 'container_depleted'; })) { - this.showContainerDepletedMessage(); + this.showContainerDepletedMessage(); + } + if (this.qa_api) { + // send test response to QA + this.qa_api.executeCommand('syncOutput', [response]); + } + }, + + renderScore: function () { + var score = parseFloat($('#score').data('score')); + var maximum_score = parseFloat($('#score').data('maximum-score')); + if (score >= 0 && score <= maximum_score && maximum_score > 0) { + var percentage_score = (score / maximum_score * 100).toFixed(0); + $('.score').html(percentage_score + '%'); + } else { + $('.score').html(0 + '%'); + } + this.renderProgressBar(score, maximum_score); + }, + + /** + * Testing-Logic + */ + handleTestResponse: function (result) { + this.clearOutput(); + this.printOutput(result, false, 0); + if (this.qa_api) { + this.qa_api.executeCommand('syncOutput', [result]); + } + this.showStatus(result); + this.showOutputBar(); + }, + + /** + * Stop-Logic + */ + stopCode: function (event) { + event.preventDefault(); + if (this.isActiveFileStoppable()) { + this.websocket.send(JSON.stringify({'cmd': 'client_kill'})); + this.killWebsocket(); + this.cleanUpUI(); + } + }, + + killWebsocket: function () { + if (this.websocket != null && this.websocket.getReadyState() != WebSocket.OPEN) { + return; + } + + this.websocket.killWebSocket(); + this.websocket.onError(_.noop); + this.running = false; + }, + + cleanUpUI: function () { + this.hideSpinner(); + this.toggleButtonStates(); + this.hidePrompt(); + }, + + /** + * Output-Logic + */ + renderWebsocketOutput: function (msg) { + var element = this.findOrCreateRenderElement(0); + element.append(msg.data); + }, + + printWebsocketOutput: function (msg) { + if (!msg.data) { + return; + } + msg.data = msg.data.replace(/(\r)/gm, "\n"); + var stream = {}; + stream[msg.stream] = msg.data; + this.printOutput(stream, true, 0); + }, + + clearOutput: function () { + $('#output pre').remove(); + CodeOceanEditorTurtle.hideCanvas(); + }, + + clearScoringOutput: function () { + $('#results ul').first().html(''); + $('.test-count .number').html(0); + $('#score').data('score', 0); + this.renderScore(); + this.clearOutput(); + }, + + printOutput: function (output, colorize, index) { + if (output.stderr === undefined && output.stdout === undefined) { + // Prevent empty element with no text at all + return; + } + + var element = this.findOrCreateOutputElement(index); + // Switch all four lines below to enable the output of images and render tags + if (!colorize) { + if (output.stdout !== undefined && output.stdout !== '') { + //element.append(output.stdout) + element.text(element.text() + output.stdout) + } + + if (output.stderr !== undefined && output.stderr !== '') { + //element.append('StdErr: ' + output.stderr); + element.text('StdErr: ' + element.text() + output.stderr); + } + + } else if (output.stderr) { + //element.addClass('text-warning').append(output.stderr); + element.addClass('text-warning').text(element.text() + output.stderr); + this.QaApiOutputBuffer.stderr += output.stderr; + } else if (output.stdout) { + //element.addClass('text-success').append(output.stdout); + element.addClass('text-success').text(element.text() + output.stdout); + this.QaApiOutputBuffer.stdout += output.stdout; + } else { + element.addClass('text-muted').text($('#output').data('message-no-output')); + } + }, + + initializeDeadlines: function () { + const deadline = $('#deadline'); + if (deadline) { + const submission_deadline = deadline.data('submission-deadline'); + const late_submission_deadline = deadline.data('late-submission-deadline'); + + const ul = document.createElement("ul"); + + if (submission_deadline) { + this.submission_deadline = new Date(submission_deadline); + const date = `${I18n.l("time.formats.long", this.submission_deadline)}: ${I18n.t('activerecord.attributes.exercise.submission_deadline')}`; + const bullet_point = `${date}
${I18n.t('exercises.editor.hints.submission_deadline')}`; + + let li = document.createElement("li"); + let text = $.parseHTML(bullet_point); + $(li).append(text); + ul.append(li); + } + + if (late_submission_deadline) { + this.late_submission_deadline = new Date(late_submission_deadline); + const date = `${I18n.l("time.formats.long", this.late_submission_deadline)}: ${I18n.t('activerecord.attributes.exercise.late_submission_deadline')}`; + const bullet_point = `${date}
${I18n.t('exercises.editor.hints.late_submission_deadline')}`; + + let li = document.createElement("li"); + let text = $.parseHTML(bullet_point); + $(li).append(text); + ul.append(li); + } + $(ul).insertAfter($(deadline).children()[0]); + } } - if (this.qa_api) { - // send test response to QA - this.qa_api.executeCommand('syncOutput', [response]); - } - }, - - renderScore: function () { - var score = parseFloat($('#score').data('score')); - var maximum_score = parseFloat($('#score').data('maximum-score')); - if (score >= 0 && score <= maximum_score && maximum_score > 0) { - var percentage_score = (score / maximum_score * 100 ).toFixed(0); - $('.score').html(percentage_score + '%'); - } - else { - $('.score').html(0 + '%'); - } - this.renderProgressBar(score, maximum_score); - }, - - /** - * Testing-Logic - */ - handleTestResponse: function (result) { - this.clearOutput(); - this.printOutput(result, false, 0); - if (this.qa_api) { - this.qa_api.executeCommand('syncOutput', [result]); - } - this.showStatus(result); - this.showOutputBar(); - }, - - /** - * Stop-Logic - */ - stopCode: function (event) { - event.preventDefault(); - if (this.isActiveFileStoppable()) { - this.websocket.send(JSON.stringify({'cmd': 'client_kill'})); - this.killWebsocket(); - this.cleanUpUI(); - } - }, - - killWebsocket: function () { - if (this.websocket != null && this.websocket.getReadyState() != WebSocket.OPEN) { - return; - } - - this.websocket.killWebSocket(); - this.websocket.onError(_.noop); - this.running = false; - }, - - cleanUpUI: function() { - this.hideSpinner(); - this.toggleButtonStates(); - this.hidePrompt(); - }, - - /** - * Output-Logic - */ - renderWebsocketOutput: function(msg){ - var element = this.findOrCreateRenderElement(0); - element.append(msg.data); - }, - - printWebsocketOutput: function(msg) { - if (!msg.data) { - return; - } - msg.data = msg.data.replace(/(\r)/gm, "\n"); - var stream = {}; - stream[msg.stream] = msg.data; - this.printOutput(stream, true, 0); - }, - - clearOutput: function() { - $('#output pre').remove(); - CodeOceanEditorTurtle.hideCanvas(); - }, - - clearScoringOutput: function() { - $('#results ul').first().html(''); - $('.test-count .number').html(0); - $('#score').data('score', 0); - this.renderScore(); - this.clearOutput(); - }, - - printOutput: function (output, colorize, index) { - if (output.stderr === undefined && output.stdout === undefined) { - // Prevent empty element with no text at all - return; - } - - var element = this.findOrCreateOutputElement(index); - // Switch all four lines below to enable the output of images and render tags - if (!colorize) { - if (output.stdout !== undefined && output.stdout !== '') { - //element.append(output.stdout) - element.text(element.text() + output.stdout) - } - - if (output.stderr !== undefined && output.stderr !== '') { - //element.append('StdErr: ' + output.stderr); - element.text('StdErr: ' + element.text() + output.stderr); - } - - } else if (output.stderr) { - //element.addClass('text-warning').append(output.stderr); - element.addClass('text-warning').text(element.text() + output.stderr); - this.QaApiOutputBuffer.stderr += output.stderr; - } else if (output.stdout) { - //element.addClass('text-success').append(output.stdout); - element.addClass('text-success').text(element.text() + output.stdout); - this.QaApiOutputBuffer.stdout += output.stdout; - } else { - element.addClass('text-muted').text($('#output').data('message-no-output')); - } - } - - }; diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index 3ffc2221..55afc796 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -420,9 +420,10 @@ class ExercisesController < ApplicationController else final_submissions = Submission.where(user: @external_user, exercise_id: @exercise.id).in_study_group_of(current_user).final @submissions = [] - @submissions.push final_submissions.before_deadline.latest - @submissions.push final_submissions.within_grace_period.latest - @submissions.push final_submissions.after_late_deadline.latest + %i[before_deadline within_grace_period after_late_deadline].each do |filter| + relevant_submission = final_submissions.send(filter).latest + @submissions.push relevant_submission if relevant_submission.present? + end @all_events = @submissions end render 'exercises/external_users/statistics' diff --git a/app/views/exercises/_editor_output.html.slim b/app/views/exercises/_editor_output.html.slim index e0500e31..75855cb2 100644 --- a/app/views/exercises/_editor_output.html.slim +++ b/app/views/exercises/_editor_output.html.slim @@ -32,6 +32,10 @@ div.h-100 id='output_sidebar_uncollapsed' class='d-none col-sm-12 enforce-bottom br - if lti_outcome_service?(@exercise.id, external_user_id, consumer_id) p.text-center = render('editor_button', classes: 'btn-lg btn-success d-none', data: {:'data-url' => submit_exercise_path(@exercise)}, icon: 'fa fa-send', id: 'submit', label: t('exercises.editor.submit')) + - if @exercise.submission_deadline.present? || @exercise.late_submission_deadline.present? + #deadline data-submission-deadline=@exercise.submission_deadline&.rfc2822 data-late-submission-deadline=@exercise.late_submission_deadline&.rfc2822 + h4 = t('exercises.editor.deadline') + = t('exercises.editor.hints.disclaimer') - else p.text-center = render('editor_button', classes: 'btn-lg btn-secondary disabled', data: {:'data-placement' => 'bottom', :'data-tooltip' => true}, icon: 'fa fa-clock-o', id: 'submit_outdated', label: t('exercises.editor.exercise_deadline_passed'), title: t('exercises.editor.tooltips.exercise_deadline_passed')) hr diff --git a/config/locales/de.yml b/config/locales/de.yml index 9aefa613..d628c81d 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -311,6 +311,10 @@ de: start_over_active_file: Diese Datei zurücksetzen stop: Stoppen submit: Code zur Bewertung abgeben + deadline: Abgabefrist + submit_on_time: Code rechtzeitig zur Bewertung abgeben + submit_within_grace_period: Code innerhalb der Gnadenfrist zur Bewertung abgeben + submit_after_late_deadline: Code verspätet zur Bewertung abgeben test: Testen timeout: 'Ausführung gestoppt. Ihr Code hat die erlaubte Ausführungszeit von %{permitted_execution_time} Sekunden überschritten.' exercise_deadline_passed: 'Das Ergebnis kann nicht übertragen werden.' @@ -318,6 +322,10 @@ de: save: Ihr Code wird automatisch gespeichert, wann immer Sie eine Datei herunterladen, ausführen oder testen. Explizites Speichern ist also selten notwendig. exercise_deadline_passed: 'Entweder ist die Abgabefrist bereits abgelaufen oder Sie haben die Aufgabe nicht direkt über die E-Learning Plattform gestartet. (Möglicherweise haben Sie den Zurück Button Ihres Browsers benutzt nachdem Sie Ihre Aufgabe abgegeben haben?)' request_for_comments_sent: "Kommentaranfrage gesendet." + hints: + submission_deadline: Abgaben nach der Abgabefrist werden als verspätet gekennzeichnet. + late_submission_deadline: Einreichungen nach der Abgabefrist und vor der verspäteten Abgabefrist werden als verspätet gekennzeichnet und möglicherweise nur mit Punktabzug gewertet. Abgaben nach der verspäteten Abgabefrist werden möglicherweise nicht berücksichtigt. + disclaimer: Bei Fragen zu der Abgabefrist wenden Sie sich bitte an einen Kursleiter. Die hier angezeigte Abgabefrist dient nur zur Information und Informationen aus der E-Learning Platform sollten immer Vorrang haben. editor_file_tree: file_root: Dateien import_codeharbor: @@ -352,8 +360,8 @@ de: no_execution_environment_selected: Bitte eine Ausführungsumgebung auswählen, bevor die Aufgabe aktiviert wird. none: Keine hints: - submission_deadline: Ein Zeitpunkt in UTC, zu dem die Abgabe geschlossen wird. Einreichungen nach der Abgabefrist werden als verspätet gekennzeichnet. - late_submission_deadline: Eine Gnadenfrist für Abgaben in UTC. Die verlängerte Abgabefrist soll nicht vor der eigentlichen Abgabefrist liegen. Nachdem die Gnadenfrist verstichen ist, werden keine neuen Einreichungen mehr akzeptiert. + submission_deadline: Ein Zeitpunkt in UTC, zu dem die Abgabe geschlossen wird. Abgaben nach der Abgabefrist werden als verspätet gekennzeichnet. + late_submission_deadline: Eine Gnadenfrist für Abgaben in UTC. Die verlängerte Abgabefrist soll nicht vor der eigentlichen Abgabefrist liegen. Abgaben nach der Abgabefrist werden deutlich gekennzeichnet. implement: alert: text: 'Ihr Browser unterstützt nicht alle Funktionalitäten, die %{application_name} benötigt. Bitte nutzen Sie einen modernen Browser, um %{application_name} zu besuchen.' diff --git a/config/locales/en.yml b/config/locales/en.yml index ca7fd39e..30442d38 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -311,6 +311,10 @@ en: start_over_active_file: Reset this file stop: Stop submit: Submit Code For Assessment + deadline: Deadline + submit_on_time: Submit Code for Assessment on Time + submit_within_grace_period: Submit Code for Assessment Within Grace Period + submit_after_late_deadline: Submit Code for Assessment After Deadline Passed test: Test timeout: 'Execution stopped. Your code exceeded the permitted execution time of %{permitted_execution_time} seconds.' exercise_deadline_passed: 'The score cannot be submitted.' @@ -318,6 +322,10 @@ en: save: Your code is automatically saved whenever you download, run, or test it. Therefore, explicitly saving is rarely necessary. exercise_deadline_passed: 'Either the deadline has already passed or you did not directly access this page from the e-learning platform. (Did you use the Back button of your browser after submitting the score?)' request_for_comments_sent: "Request for comments sent." + hints: + submission_deadline: Any submission obtained after the deadline will be considered late. + late_submission_deadline: Any submission obtained after the deadline but before the late submission deadline will be considered as late and might only be scored with a penality. Any submission obtained after the late submission deadline might not be considered. + disclaimer: If unsure about a deadline, please contact a course instructor. The deadline shown here is only informational and information from the e-learning platform should always take precedence. editor_file_tree: file_root: Files import_codeharbor: @@ -353,7 +361,7 @@ en: none: None hints: submission_deadline: A date and time in UTC to close the submission. Any submission obtained after the deadline will be considered late. - late_submission_deadline: A grace period for submissions in UTC. The late submission deadline should not be set or any timestamp before the original submission deadline. After the late submission deadline passed, any new submissions are prevented. + late_submission_deadline: A grace period for submissions in UTC. The late submission deadline should not be set or any timestamp before the original submission deadline. Any submission obtained after the deadline will be clearly marked. implement: alert: text: 'Your browser does not support features required for using %{application_name}. Please access %{application_name} using a modern browser.'