
Previously, the same runner could be used multiple times with different submissions simultaneously. This, however, yielded errors, for example when one submission time oud (causing the running to be deleted) while another submission was still executed. Admin actions, such as the shell, can be still executed regardless of any other code execution. Fixes CODEOCEAN-HG Fixes openHPI/poseidon#423
306 lines
11 KiB
JavaScript
306 lines
11 KiB
JavaScript
CodeOceanEditorEvaluation = {
|
|
// A list of non-printable characters that are not allowed in the code output.
|
|
// Taken from https://stackoverflow.com/a/69024306
|
|
nonPrintableRegEx: /[\u0000-\u0008\u000B\u000C\u000F-\u001F\u007F-\u009F\u2000-\u200F\u2028-\u202F\u205F-\u206F\u3000\uFEFF]/g,
|
|
sentryTransaction: null,
|
|
|
|
/**
|
|
* Scoring-Functions
|
|
*/
|
|
scoreCode: function (event) {
|
|
const cause = $('#assess');
|
|
this.startSentryTransaction(cause);
|
|
event.preventDefault();
|
|
this.stopCode(event);
|
|
this.clearScoringOutput();
|
|
$('#submit').addClass("d-none");
|
|
this.createSubmission(cause, 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();
|
|
this.showSubmitButton();
|
|
},
|
|
|
|
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
|
|
$('#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");
|
|
},
|
|
|
|
printScoringResult: function (result, index) {
|
|
if (result === undefined || result === null) {
|
|
return;
|
|
}
|
|
|
|
$('#results').show();
|
|
let card;
|
|
if (result.file_role === 'teacher_defined_linter') {
|
|
card = $('#linter-dummies').children().first().clone();
|
|
} else {
|
|
card = $('#test-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);
|
|
}
|
|
},
|
|
|
|
printScoringResults: function (response) {
|
|
response = (Array.isArray(response)) ? response : [response]
|
|
const test_results = response.filter(function(x) {
|
|
if (x === undefined || x === null) {
|
|
return false;
|
|
}
|
|
switch (x.file_role) {
|
|
case 'teacher_defined_test':
|
|
return true;
|
|
case 'teacher_defined_linter':
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
});
|
|
|
|
$('#results ul').first().html('');
|
|
$('.test-count .number').html(test_results.length);
|
|
this.clearOutput();
|
|
|
|
_.each(test_results, function (result, index) {
|
|
// based on https://stackoverflow.com/questions/8511281/check-if-a-value-is-an-object-in-javascript
|
|
if (result === Object(result)) {
|
|
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 === 'out_of_memory';
|
|
})) {
|
|
this.showOutOfMemoryMessage();
|
|
}
|
|
if (_.some(response, function (result) {
|
|
return result.status === 'runner_in_use';
|
|
})) {
|
|
this.showRunnerInUseMessage();
|
|
}
|
|
if (_.some(response, function (result) {
|
|
return result.status === 'container_depleted';
|
|
})) {
|
|
this.showContainerDepletedMessage();
|
|
}
|
|
},
|
|
|
|
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);
|
|
this.showStatus(result);
|
|
this.showOutputBar();
|
|
},
|
|
|
|
/**
|
|
* Stop-Logic
|
|
*/
|
|
stopCode: function (event) {
|
|
event.preventDefault();
|
|
if (this.isActiveFileStoppable() && this.websocket) {
|
|
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
|
|
*/
|
|
printWebsocketOutput: function (msg) {
|
|
if (!msg.data || msg.data === "\r") {
|
|
return;
|
|
}
|
|
var stream = {};
|
|
stream[msg.stream] = msg.data;
|
|
this.printOutput(stream, true, 0);
|
|
},
|
|
|
|
clearOutput: function () {
|
|
$('#output > .output-element').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 === undefined || output === null || output.stderr === undefined && output.stdout === undefined) {
|
|
// Prevent empty element with no text at all
|
|
return;
|
|
}
|
|
|
|
const sanitizedStdout = this.sanitizeOutput(output.stdout);
|
|
const sanitizedStderr = this.sanitizeOutput(output.stderr);
|
|
|
|
const element = this.findOrCreateOutputElement(index);
|
|
const pre = $('<span>');
|
|
|
|
if (sanitizedStdout !== '') {
|
|
if (colorize) {
|
|
pre.addClass('text-success');
|
|
}
|
|
pre.append(sanitizedStdout)
|
|
}
|
|
|
|
if (sanitizedStderr !== '') {
|
|
if (colorize) {
|
|
pre.addClass('text-warning');
|
|
} else {
|
|
pre.append('StdErr: ');
|
|
}
|
|
pre.append(sanitizedStderr);
|
|
}
|
|
|
|
if (sanitizedStdout === '' && sanitizedStderr === '') {
|
|
if (colorize) {
|
|
pre.addClass('text-body-secondary');
|
|
}
|
|
pre.text($('#output').data('message-no-output'))
|
|
}
|
|
|
|
element.append(pre);
|
|
},
|
|
|
|
sanitizeOutput: function (rawContent) {
|
|
let sanitizedContent = _.escape(rawContent).replace(this.nonPrintableRegEx, "");
|
|
|
|
if (rawContent !== undefined && rawContent.trim().startsWith("<img")) {
|
|
const doc = new DOMParser().parseFromString(rawContent, "text/html");
|
|
// Get the parsed element, it is automatically wrapped in a <html><body> document
|
|
const parsedElement = doc.firstChild.lastChild.firstChild;
|
|
|
|
if (parsedElement.src.startsWith("data:image")) {
|
|
const sanitizedImg = document.createElement('img');
|
|
sanitizedImg.src = parsedElement.src;
|
|
sanitizedContent = sanitizedImg.outerHTML;
|
|
}
|
|
}
|
|
|
|
return sanitizedContent;
|
|
},
|
|
|
|
getDeadlineInformation: function(deadline, translation_key, otherwise) {
|
|
if (deadline !== undefined) {
|
|
let li = document.createElement("li");
|
|
this.submission_deadline = new Date(deadline);
|
|
let deadline_text = I18n.l("time.formats.long", this.submission_deadline);
|
|
deadline_text += ` (${this.getUTCTime(this.submission_deadline, I18n.locale === 'en')})`;
|
|
const bullet_point = I18n.t('exercises.editor.hints.' + translation_key,
|
|
{ deadline: deadline_text, otherwise: otherwise })
|
|
let text = $.parseHTML(bullet_point);
|
|
$(li).append(text);
|
|
return li;
|
|
}
|
|
},
|
|
|
|
getUTCTime: function(d, use_am_pm) {
|
|
let hour = d.getUTCHours();
|
|
const pm = hour >= 12;
|
|
let hour12 = hour % 12;
|
|
if (!hour12) {
|
|
hour12 += 12;
|
|
}
|
|
hour = hour.toLocaleString('en-US', {minimumIntegerDigits: 2, useGrouping: false})
|
|
const minute = d.getUTCMinutes().toLocaleString('en-US', {minimumIntegerDigits: 2, useGrouping: false})
|
|
const second = d.getUTCSeconds().toLocaleString('en-US', {minimumIntegerDigits: 2, useGrouping: false})
|
|
if (use_am_pm) {
|
|
return `${hour12}:${minute}:${second} ${pm ? 'pm' : 'am'} UTC`;
|
|
} else {
|
|
return `${hour}:${minute}:${second} UTC`;
|
|
}
|
|
},
|
|
|
|
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 && late_submission_deadline) {
|
|
ul.append(this.getDeadlineInformation(submission_deadline, 'submission_deadline', ''));
|
|
ul.append(this.getDeadlineInformation(late_submission_deadline, 'late_submission_deadline', ''));
|
|
} else {
|
|
const otherwise_no_points = I18n.t('exercises.editor.hints.otherwise');
|
|
ul.append(this.getDeadlineInformation(submission_deadline, 'submission_deadline', otherwise_no_points));
|
|
}
|
|
|
|
$(ul).insertAfter($(deadline).children()[0]);
|
|
}
|
|
}
|
|
};
|