Files
codeocean/app/assets/javascripts/editor/evaluation.js
Sebastian Serth 94404370c4 Upgrade Sentry to v8 and remove custom Dependabot grouping
As part of the upgrade process, we need to rework the tracing instrumentation. Now, we are just wrapping all async functions in a new sentry transaction, which will automatically end once the function returns.

Further, the structure of the Sentry packages got reworked, so that we only need a single package by now. This removes the need to group dependabot updates.

Co-authored-by: Jan Graichen <jgraichen@altimos.de>
2024-05-24 14:52:14 +02:00

289 lines
10 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,
/**
* Scoring-Functions
*/
scoreCode: function (event) {
event.preventDefault();
const cause = $('#assess');
this.newSentryTransaction(cause, async () => {
this.stopCode(event);
this.clearScoringOutput();
$('#submit').addClass("d-none");
const submission = await this.createSubmission(cause, null).catch(this.ajaxError.bind(this));
if (!submission) return;
this.showSpinner($('#assess'));
$('#score_div').removeClass('d-none');
await this.socketScoreCode(submission.id);
});
},
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();
},
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) {
// i18n-tasks-use t('exercises.editor.hints.submission_deadline')
ul.append(this.getDeadlineInformation(submission_deadline, 'submission_deadline', ''));
// i18n-tasks-use t('exercises.editor.hints.late_submission_deadline')
ul.append(this.getDeadlineInformation(late_submission_deadline, 'late_submission_deadline', ''));
} else {
const otherwise_no_points = I18n.t('exercises.editor.hints.otherwise');
// i18n-tasks-use t('exercises.editor.hints.submission_deadline')
ul.append(this.getDeadlineInformation(submission_deadline, 'submission_deadline', otherwise_no_points));
}
$(ul).insertAfter($(deadline).children()[0]);
}
}
};