Skip to content

Commit cd9541c

Browse files
Merge pull request #60 from Simon-Initiative/AUTHORING-2206-repl-activity-improvements-2
HOTFIX 0.40.1 - AUTHORING-2206 Improve REPL activity functionality
2 parents 7037676 + 23a2049 commit cd9541c

2 files changed

Lines changed: 166 additions & 32 deletions

File tree

src/main/java/edu/cmu/oli/content/boundary/managers/ContentResourceManager.java

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,42 @@ public Resource doCreate(String packageIdOrGuid, String resourceTypeId, JsonElem
555555
pathFrom = "organizations/" + random + "/" + "organization.xml";
556556
}
557557

558+
String pathTo = jsonCapable ? pathFrom.substring(0, pathFrom.lastIndexOf(".xml")) + ".json" : pathFrom;
559+
560+
FileNode fileNode = new FileNode(contentPackage.getVolumeLocation(), pathFrom, pathTo,
561+
jsonCapable ? "application/json" : "text/xml");
562+
resource.setFileNode(fileNode);
563+
resource.setContentPackage(contentPackage);
564+
565+
// check if the object is an embed_activity to extract asset content
566+
if (resourceContent.getAsJsonObject().has("embed_activity")) {
567+
JsonObject embedActivity = resourceContent.getAsJsonObject().get("embed_activity").getAsJsonObject();
568+
JsonArray embedActivityChildren = embedActivity.get("#array").getAsJsonArray();
569+
570+
// if embed_activity is a REPL, make sure required assets are in place
571+
boolean isReplActivity = AppUtils.inferEmbedActivityType(embedActivity) == EmbedActivityType.REPL;
572+
if (isReplActivity) {
573+
this.processReplEmbedActivity(resource, embedActivity);
574+
}
575+
576+
for (JsonElement child : embedActivityChildren) {
577+
if (child.getAsJsonObject().has("assets")) {
578+
JsonArray assets = child.getAsJsonObject().get("assets").getAsJsonObject().get("#array").getAsJsonArray();
579+
580+
for (JsonElement asset : assets) {
581+
if (asset.getAsJsonObject().get("asset").getAsJsonObject().has("content")) {
582+
// extract asset content
583+
try {
584+
this.processAssetContent(resource, asset.getAsJsonObject().get("asset").getAsJsonObject());
585+
} catch (Throwable t) {
586+
log.error(t.toString());
587+
}
588+
}
589+
}
590+
}
591+
}
592+
}
593+
558594
TypedQuery<Resource> query = em.createQuery(
559595
"select r from Resource r where r.contentPackage.guid = :pkgGuid and r.id = :id", Resource.class);
560596
query.setParameter("id", resource.getId());
@@ -568,12 +604,6 @@ public Resource doCreate(String packageIdOrGuid, String resourceTypeId, JsonElem
568604

569605
// Parse update payload into final xml and json documents
570606
Map<String, String> contentValues = contentValues(resourceContent, resource, jsonCapable);
571-
String pathTo = jsonCapable ? pathFrom.substring(0, pathFrom.lastIndexOf(".xml")) + ".json" : pathFrom;
572-
573-
FileNode fileNode = new FileNode(contentPackage.getVolumeLocation(), pathFrom, pathTo,
574-
jsonCapable ? "application/json" : "text/xml");
575-
resource.setFileNode(fileNode);
576-
resource.setContentPackage(contentPackage);
577607
validateXmlContent(contentPackage.getGuid(), resource, contentValues.get("xmlContent"), throwErrors);
578608

579609
RevisionBlob revisionBlob = jsonCapable

src/main/resources/repl.js

Lines changed: 130 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -103,28 +103,28 @@ define(function () {
103103
$.get(superClient.webContentFolder + assets.controls, function (controls) {
104104
$('#oli-embed').append(controls);
105105
$("#save_btn").click(function () {
106-
if (!superClient.isCurrentAttemptCompleted() && questionsSaveData.numberOfQuestionsAnswered() < 1) {
106+
if (!superClient.isCurrentAttemptCompleted() && questionsSaveData.numberOfQuestionsAnswered() < 1 && !$('#save_btn').hasClass('disabled')) {
107107
activityEmbed.save();
108108
}
109109
});
110110
$("#submit_btn").click(function () {
111-
if (!superClient.isCurrentAttemptCompleted() && questionsSaveData.numberOfQuestionsAnswered() < 1) {
111+
if (!superClient.isCurrentAttemptCompleted() && questionsSaveData.numberOfQuestionsAnswered() < 1 && !$('#submit_btn').hasClass('disabled')) {
112112
activityEmbed.submit();
113113
}
114114
});
115115
$("#hint_btn").click(function () {
116-
if (!superClient.isCurrentAttemptCompleted()
116+
if (!superClient.isCurrentAttemptCompleted() && !$('#hint_btn').hasClass('disabled')
117117
&& typeof (currentPart) === "undefined" || currentPart === null || currentPart.getHints().length > 0) {
118118
activityEmbed.hint();
119119
}
120120
});
121121
$("#next_btn").click(function () {
122-
if (superClient.isCurrentAttemptCompleted()) {
122+
if (superClient.isCurrentAttemptCompleted() && !$('#next_btn').hasClass('disabled')) {
123123
activityEmbed.nextAttempt();
124124
}
125125
});
126126
$("#solution_btn").click(function () {
127-
if (superClient.isCurrentAttemptCompleted() || activityEmbed.isUngradedActivity()) {
127+
if ((superClient.isCurrentAttemptCompleted() || activityEmbed.isUngradedActivity()) && !$('#solution_btn').hasClass('disabled')) {
128128
$.get(superClient.webContentFolder + assets.solutions, function (data) {
129129
var solutionText = null;
130130
$(data).find("solution").each(function (q) {
@@ -156,7 +156,13 @@ define(function () {
156156
});
157157

158158
$("#run").click(function () {
159-
if (!superClient.isCurrentAttemptCompleted()) {
159+
if (!superClient.isCurrentAttemptCompleted() && !$('#run').hasClass('disabled')) {
160+
activityEmbed.run();
161+
}
162+
});
163+
164+
$("#submit").click(function () {
165+
if (!superClient.isCurrentAttemptCompleted() && !$('#submit').hasClass('disabled')) {
160166
var edit_text = ActivityEmbed.editor.getValue();
161167
activityEmbed.pushInput(edit_text);
162168

@@ -170,7 +176,7 @@ define(function () {
170176
});
171177

172178
$("#clear").click(function () {
173-
if (!superClient.isCurrentAttemptCompleted()) {
179+
if (!superClient.isCurrentAttemptCompleted() && !$('#clear').hasClass('disabled')) {
174180
ActivityEmbed.repl.clearScreen();
175181
ActivityEmbed.repl.writeStdin('\n');
176182
ActivityEmbed.repl.disableStdin(false);
@@ -247,6 +253,8 @@ define(function () {
247253
if (superClient.isCurrentAttemptCompleted()) {
248254
ActivityEmbed.repl.disableStdin();
249255
ActivityEmbed.repl.disableStdout();
256+
257+
$('#oli-embed .question').append('<p>This activity has been submitted. Click Reset to try again.</p>');
250258
}
251259

252260
ActivityEmbed.repl.attach(document.getElementById('console'));
@@ -380,6 +388,9 @@ define(function () {
380388

381389
// restore content to editor
382390
initEditorText = input.value;
391+
if (ActivityEmbed.editor) {
392+
ActivityEmbed.editor.setValue(initEditorText);
393+
}
383394

384395
// write previous input/output content
385396
ActivityEmbed.repl.on('afterconnect', () => {
@@ -405,8 +416,8 @@ define(function () {
405416
$('#save_btn').addClass('disabled');
406417
$('#submit_btn').addClass('disabled');
407418
$('#run').addClass('disabled');
419+
$('#submit').addClass('disabled');
408420
$('#clear').addClass('disabled');
409-
$('#hint_btn').addClass('disabled');
410421

411422
$('#next_btn').removeClass('disabled');
412423
$("#solution_btn").removeClass('disabled');
@@ -418,16 +429,18 @@ define(function () {
418429
$('#save_btn').addClass('disabled');
419430
$('#submit_btn').addClass('disabled');
420431
}
421-
if (typeof (currentPart) === "undefined" || currentPart === null || currentPart.getHints().length === 0) {
422-
$('#hint_btn').addClass('disabled');
423-
} else {
424-
$('#hint_btn').removeClass('disabled');
425-
}
426432
$('#next_btn').addClass('disabled');
427433
$('#run').removeClass('disabled');
434+
$('#submit').removeClass('disabled');
428435
$('#clear').removeClass('disabled');
429436
}
430-
if (Number(superClient.currentAttempt) > 1 || activityEmbed.isUngradedActivity()) {
437+
438+
if (typeof (currentPart) === "undefined" || currentPart === null || currentPart.getHints().length === 0) {
439+
$('#hint_btn').addClass('disabled');
440+
} else {
441+
$('#hint_btn').removeClass('disabled');
442+
}
443+
if (superClient.isCurrentAttemptCompleted() || activityEmbed.isUngradedActivity()) {
431444
$("#solution_btn").removeClass('disabled');
432445
} else {
433446
$("#solution_btn").addClass('disabled');
@@ -456,16 +469,93 @@ define(function () {
456469
superClient.logAction(action);
457470

458471
};
472+
// Run the code in the editor without submitting for a score
473+
this.run = function () {
474+
console.log("run()");
475+
476+
// disable run button
477+
$('#run').addClass('disabled');
478+
$('#run').text('Running...');
479+
480+
// clear out previous feedback
481+
$('#' + currentQuestion.getId() + '_feedback').empty();
482+
$('#' + currentQuestion.getId() + '_feedback').removeAttr("style");
483+
484+
// clear console and disable further input
485+
code = ActivityEmbed.editor.getValue();
486+
ActivityEmbed.repl.clearScreen();
487+
ActivityEmbed.repl.writeln('EXECUTING CODE:');
488+
ActivityEmbed.repl.writeln(code);
489+
ActivityEmbed.repl.write('\n');
490+
ActivityEmbed.repl.disableStdin();
491+
492+
return ReplClient.exec(code, { host: 'repl.oli.cmu.edu', language: 'python3' })
493+
.then(function (response) {
494+
console.log("Ouput from ReplClient.exec " + JSON.stringify(response));
495+
ActivityEmbed.repl.writeln('---------------------------------------');
496+
ActivityEmbed.repl.writeln('RESULT:');
497+
ActivityEmbed.repl.write(response.result);
498+
ActivityEmbed.repl.terminal.scrollToBottom();
499+
500+
// check if there was a question and part to process. If not, then this is treated as
501+
// an ungraded activity, so simply return
502+
if (activityEmbed.isUngradedActivity()) {
503+
console.log("currentQuestion or currentPart not set");
504+
return;
505+
}
506+
507+
// Only score attempt if at least 1 question has been answered
508+
if (questionsSaveData.numberOfQuestionsAnswered() < 1) {
509+
console.log("scoreAttempt() no questions answered ");
510+
return;
511+
}
512+
513+
if (!(response.error)) {
514+
console.log("No error from ReplClient.exec");
515+
516+
var feedback = "Execution succeeded: Code is well formated";
517+
$('#' + currentQuestion.getId() + '_feedback').append('<div style="display: inline-block;">' + feedback + '</div>');
518+
var styles = {
519+
background: '#ddffdd',
520+
borderColor: '#33aa33',
521+
display: 'block'
522+
};
523+
$('#' + currentQuestion.getId() + '_feedback').css(styles);
524+
} else {
525+
var feedback = "Execution failed. Check the console output and your code for errors";
526+
$('#' + currentQuestion.getId() + '_feedback').append('<div style="display: inline-block;">' + feedback + '</div>');
527+
var styles = {
528+
background: '#f4cfc9',
529+
borderColor: '#e75d36',
530+
display: 'block'
531+
};
532+
$('#' + currentQuestion.getId() + '_feedback').css(styles);
533+
}
534+
535+
return response;
536+
})
537+
.catch(function (err) {
538+
console.error(err);
539+
ActivityEmbed.repl.writeln("An unexpected error occurred: " + err);
540+
ActivityEmbed.repl.writeln('Please try reloading the page or contact support if the issue persists');
541+
})
542+
.finally(function () {
543+
$('#run').removeClass('disabled');
544+
$('#run').text('Run');
545+
});
546+
};
547+
// Run the code in the editor and submit for a scoring
459548
this.submit = function () {
460549
console.log("submit()");
461-
if (superClient.isCurrentAttemptCompleted()) {
462-
return;
463-
}
464550

465-
ActivityEmbed.repl.disableStdin();
466-
return activityEmbed.process();
467-
};
468-
this.process = function () {
551+
// disable run button
552+
$('#submit').addClass('disabled');
553+
$('#submit').text('Submitting...');
554+
555+
// clear out previous feedback
556+
$('#' + currentQuestion.getId() + '_feedback').empty();
557+
$('#' + currentQuestion.getId() + '_feedback').removeAttr("style");
558+
469559
var questionData;
470560
var partData;
471561
var code;
@@ -477,11 +567,19 @@ define(function () {
477567
code = partData.getInput().value
478568
}
479569

570+
ActivityEmbed.repl.clearScreen();
571+
ActivityEmbed.repl.writeln('EXECUTING CODE:');
572+
ActivityEmbed.repl.writeln(code);
573+
ActivityEmbed.repl.write('\n');
574+
ActivityEmbed.repl.disableStdin();
575+
480576
return ReplClient.exec(code, { host: 'repl.oli.cmu.edu', language: 'python3' })
481577
.then(function (response) {
482578
console.log("Ouput from ReplClient.exec " + JSON.stringify(response));
483579
ActivityEmbed.repl.writeln('---------------------------------------');
580+
ActivityEmbed.repl.writeln('RESULT:');
484581
ActivityEmbed.repl.write(response.result);
582+
ActivityEmbed.repl.terminal.scrollToBottom();
485583

486584
// check if there was a question and part to process. If not, then this is treated as
487585
// an ungraded activity, so simply return
@@ -510,7 +608,7 @@ define(function () {
510608
}
511609
}
512610
} else {
513-
var feedback = {id: "replt_eval", content: "Correct: code is well formated"};
611+
var feedback = {id: "replt_eval", content: "Execution succeeded: Code is well formated"};
514612
partData.setFeedback(feedback);
515613
partData.setScore(Number(10));
516614
partData.setCorrect(true);
@@ -539,13 +637,19 @@ define(function () {
539637

540638
activityEmbed.saveDataAndScore();
541639
}
640+
641+
$('#oli-embed .question').append('<p>This activity has been submitted. Click Reset to try again.</p>');
542642

543643
return response;
544-
},
545-
function (err) {
644+
})
645+
.catch(function (err) {
546646
console.error(err);
547-
}
548-
);
647+
ActivityEmbed.repl.writeln("An unexpected error occurred: " + err);
648+
ActivityEmbed.repl.writeln('Please try reloading the page or contact support if the issue persists');
649+
})
650+
.finally(function () {
651+
$('#submit').text('Submit');
652+
})
549653
};
550654
this.cloudCoderProcess = function (info) {
551655
var questionData = questionsSaveData.getQuestionData(currentQuestion.getId());

0 commit comments

Comments
 (0)