@ -476,6 +476,7 @@ configureEditors: function () {
|
|||||||
this.clearOutput();
|
this.clearOutput();
|
||||||
$('#hint').fadeOut();
|
$('#hint').fadeOut();
|
||||||
$('#flowrHint').fadeOut();
|
$('#flowrHint').fadeOut();
|
||||||
|
this.clearHints();
|
||||||
this.showOutputBar();
|
this.showOutputBar();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -512,6 +513,30 @@ configureEditors: function () {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearHints: function() {
|
||||||
|
var container = $('#error-hints');
|
||||||
|
container.find('ul.body > li.hint').remove();
|
||||||
|
container.fadeOut();
|
||||||
|
},
|
||||||
|
|
||||||
|
showHint: function(message) {
|
||||||
|
var template = function(description, hint) {
|
||||||
|
return '\
|
||||||
|
<li class="hint">\
|
||||||
|
<div class="description">\
|
||||||
|
' + description + '\
|
||||||
|
</div>\
|
||||||
|
<div class="hint">\
|
||||||
|
' + hint + '\
|
||||||
|
</div>\
|
||||||
|
</li>\
|
||||||
|
'
|
||||||
|
};
|
||||||
|
var container = $('#error-hints');
|
||||||
|
container.find('ul.body').append(template(message.description, message.hint));
|
||||||
|
container.fadeIn();
|
||||||
|
},
|
||||||
|
|
||||||
showContainerDepletedMessage: function() {
|
showContainerDepletedMessage: function() {
|
||||||
$.flash.danger({
|
$.flash.danger({
|
||||||
icon: ['fa', 'fa-clock-o'],
|
icon: ['fa', 'fa-clock-o'],
|
||||||
|
@ -30,6 +30,7 @@ CodeOceanEditorWebsocket = {
|
|||||||
initializeSocketForScoring: function(url) {
|
initializeSocketForScoring: function(url) {
|
||||||
this.initializeSocket(url);
|
this.initializeSocket(url);
|
||||||
this.websocket.on('default',this.handleScoringResponse.bind(this));
|
this.websocket.on('default',this.handleScoringResponse.bind(this));
|
||||||
|
this.websocket.on('hint', this.showHint.bind(this));
|
||||||
this.websocket.on('exit', this.handleExitCommand.bind(this));
|
this.websocket.on('exit', this.handleExitCommand.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ CodeOceanEditorWebsocket = {
|
|||||||
this.websocket.on('exit', this.handleExitCommand.bind(this));
|
this.websocket.on('exit', this.handleExitCommand.bind(this));
|
||||||
this.websocket.on('timeout', this.showTimeoutMessage.bind(this));
|
this.websocket.on('timeout', this.showTimeoutMessage.bind(this));
|
||||||
this.websocket.on('status', this.showStatus.bind(this));
|
this.websocket.on('status', this.showStatus.bind(this));
|
||||||
|
this.websocket.on('hint', this.showHint.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
handleExitCommand: function() {
|
handleExitCommand: function() {
|
||||||
|
@ -193,3 +193,24 @@ button i.fa-spin {
|
|||||||
.enforce-bottom-margin {
|
.enforce-bottom-margin {
|
||||||
margin-bottom: 5px !important;
|
margin-bottom: 5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#error-hints {
|
||||||
|
display: none;
|
||||||
|
background-color: #FAFAFA;
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.body {
|
||||||
|
|
||||||
|
li.hint {
|
||||||
|
.description {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -13,7 +13,7 @@ module SubmissionScoring
|
|||||||
submission.exercise.execution_environment.error_templates.each do |template|
|
submission.exercise.execution_environment.error_templates.each do |template|
|
||||||
pattern = Regexp.new(template.signature).freeze
|
pattern = Regexp.new(template.signature).freeze
|
||||||
if pattern.match(testrun_output)
|
if pattern.match(testrun_output)
|
||||||
StructuredError.create_from_template(template, testrun_output)
|
StructuredError.create_from_template(template, testrun_output, submission)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -197,7 +197,8 @@ class SubmissionsController < ApplicationController
|
|||||||
|
|
||||||
def kill_socket(tubesock)
|
def kill_socket(tubesock)
|
||||||
# search for errors and save them as StructuredError (for scoring runs see submission_scoring.rb)
|
# search for errors and save them as StructuredError (for scoring runs see submission_scoring.rb)
|
||||||
extract_errors
|
errors = extract_errors
|
||||||
|
send_hints(tubesock, errors)
|
||||||
|
|
||||||
# save the output of this "run" as a "testrun" (scoring runs are saved in submission_scoring.rb)
|
# save the output of this "run" as a "testrun" (scoring runs are saved in submission_scoring.rb)
|
||||||
save_run_output
|
save_run_output
|
||||||
@ -284,14 +285,16 @@ class SubmissionsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def extract_errors
|
def extract_errors
|
||||||
|
results = []
|
||||||
unless @raw_output.blank?
|
unless @raw_output.blank?
|
||||||
@submission.exercise.execution_environment.error_templates.each do |template|
|
@submission.exercise.execution_environment.error_templates.each do |template|
|
||||||
pattern = Regexp.new(template.signature).freeze
|
pattern = Regexp.new(template.signature).freeze
|
||||||
if pattern.match(@raw_output)
|
if pattern.match(@raw_output)
|
||||||
StructuredError.create_from_template(template, @raw_output, @submission)
|
results << StructuredError.create_from_template(template, @raw_output, @submission)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
results
|
||||||
end
|
end
|
||||||
|
|
||||||
def score
|
def score
|
||||||
@ -303,11 +306,22 @@ class SubmissionsController < ApplicationController
|
|||||||
# to ensure responsiveness, we therefore open a thread here.
|
# to ensure responsiveness, we therefore open a thread here.
|
||||||
Thread.new {
|
Thread.new {
|
||||||
tubesock.send_data JSON.dump(score_submission(@submission))
|
tubesock.send_data JSON.dump(score_submission(@submission))
|
||||||
|
|
||||||
|
# To enable hints when scoring a submission, uncomment the next line:
|
||||||
|
#send_hints(tubesock, StructuredError.where(submission: @submission))
|
||||||
|
|
||||||
tubesock.send_data JSON.dump({'cmd' => 'exit'})
|
tubesock.send_data JSON.dump({'cmd' => 'exit'})
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def send_hints(tubesock, errors)
|
||||||
|
errors = errors.to_a.uniq { |e| e.hint}
|
||||||
|
errors.each do | error |
|
||||||
|
tubesock.send_data JSON.dump({cmd: 'hint', hint: error.hint, description: error.error_template.description})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def set_docker_client
|
def set_docker_client
|
||||||
@docker_client = DockerClient.new(execution_environment: @submission.execution_environment)
|
@docker_client = DockerClient.new(execution_environment: @submission.execution_environment)
|
||||||
end
|
end
|
||||||
|
@ -3,11 +3,21 @@ class StructuredError < ActiveRecord::Base
|
|||||||
belongs_to :submission
|
belongs_to :submission
|
||||||
belongs_to :file, class_name: 'CodeOcean::File'
|
belongs_to :file, class_name: 'CodeOcean::File'
|
||||||
|
|
||||||
|
has_many :structured_error_attributes
|
||||||
|
|
||||||
def self.create_from_template(template, message_buffer, submission)
|
def self.create_from_template(template, message_buffer, submission)
|
||||||
instance = self.create(error_template: template, submission: submission)
|
instance = self.create(error_template: template, submission: submission)
|
||||||
template.error_template_attributes.each do |attribute|
|
template.error_template_attributes.each do | attribute |
|
||||||
StructuredErrorAttribute.create_from_template(attribute, instance, message_buffer)
|
StructuredErrorAttribute.create_from_template(attribute, instance, message_buffer)
|
||||||
end
|
end
|
||||||
instance
|
instance
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def hint
|
||||||
|
content = error_template.hint
|
||||||
|
structured_error_attributes.each do | attribute |
|
||||||
|
content.sub! "{{#{attribute.error_template_attribute.key}}}", attribute.value if attribute.match
|
||||||
|
end
|
||||||
|
content
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -16,4 +16,5 @@
|
|||||||
.form-group
|
.form-group
|
||||||
= f.label(:hint)
|
= f.label(:hint)
|
||||||
= f.text_field(:hint, class: 'form-control')
|
= f.text_field(:hint, class: 'form-control')
|
||||||
|
.help-block == t('error_templates.hints.hint_templates')
|
||||||
.actions = render('shared/submit_button', f: f, object: @error_template)
|
.actions = render('shared/submit_button', f: f, object: @error_template)
|
||||||
|
@ -47,6 +47,9 @@ div id='output_sidebar_uncollapsed' class='hidden col-sm-12 enforce-bottom-margi
|
|||||||
input#prompt-input.form-control type='text'
|
input#prompt-input.form-control type='text'
|
||||||
span.input-group-btn
|
span.input-group-btn
|
||||||
button#prompt-submit.btn.btn-primary type="button" = t('exercises.editor.send')
|
button#prompt-submit.btn.btn-primary type="button" = t('exercises.editor.send')
|
||||||
|
#error-hints
|
||||||
|
.heading = t('exercises.implement.error_hints.heading')
|
||||||
|
ul.body
|
||||||
#output
|
#output
|
||||||
pre = t('exercises.implement.no_output_yet')
|
pre = t('exercises.implement.no_output_yet')
|
||||||
- if CodeOcean::Config.new(:code_ocean).read[:flowr][:enabled]
|
- if CodeOcean::Config.new(:code_ocean).read[:flowr][:enabled]
|
||||||
|
@ -331,6 +331,8 @@ de:
|
|||||||
break_intervention:
|
break_intervention:
|
||||||
title: "Pause"
|
title: "Pause"
|
||||||
text: "Uns ist aufgefallen, dass du schon lange an dieser Aufgabe arbeitest. Möchtest du vielleicht später weiter machen um erstmal auf neue Gedanken zu kommen?"
|
text: "Uns ist aufgefallen, dass du schon lange an dieser Aufgabe arbeitest. Möchtest du vielleicht später weiter machen um erstmal auf neue Gedanken zu kommen?"
|
||||||
|
error_hints:
|
||||||
|
heading: "Hinweise"
|
||||||
index:
|
index:
|
||||||
clone: Duplizieren
|
clone: Duplizieren
|
||||||
implement: Implementieren
|
implement: Implementieren
|
||||||
@ -728,6 +730,7 @@ de:
|
|||||||
error_templates:
|
error_templates:
|
||||||
hints:
|
hints:
|
||||||
signature: "Ein regulärer Ausdruck in Ruby-Syntax und ohne führende und schließende \"/\""
|
signature: "Ein regulärer Ausdruck in Ruby-Syntax und ohne führende und schließende \"/\""
|
||||||
|
hint_templates: 'Attributnamen in {{doppelten geschweiften Klammern}} werden zur Laufzeit durch die jeweiligen Attributwerte ersetzt. Beispiel: "Der Fehler ist in Zeile {{Line}}." --(StructuredError: {Line: 4})--> "Der Fehler ist in Zeile 4."'
|
||||||
attributes: "Attribute"
|
attributes: "Attribute"
|
||||||
add_attribute: "Attribut hinzufügen"
|
add_attribute: "Attribut hinzufügen"
|
||||||
comments:
|
comments:
|
||||||
|
@ -331,6 +331,8 @@ en:
|
|||||||
break_intervention:
|
break_intervention:
|
||||||
title: "Break"
|
title: "Break"
|
||||||
text: "We recognized that you are already working quite a while on this exercise. We would like to encourage you to take a break and come back later."
|
text: "We recognized that you are already working quite a while on this exercise. We would like to encourage you to take a break and come back later."
|
||||||
|
error_hints:
|
||||||
|
heading: "Hints"
|
||||||
index:
|
index:
|
||||||
clone: Duplicate
|
clone: Duplicate
|
||||||
implement: Implement
|
implement: Implement
|
||||||
@ -728,6 +730,7 @@ en:
|
|||||||
error_templates:
|
error_templates:
|
||||||
hints:
|
hints:
|
||||||
signature: "A regular expression in Ruby syntax without leading and trailing \"/\""
|
signature: "A regular expression in Ruby syntax without leading and trailing \"/\""
|
||||||
|
hint_templates: 'Attribute names in {{double curly braces}} are replaced by the corresponding attribute value at runtime, e.g. "The error occurs in line {{Line}}." --(StructuredError: {Line: 4})--> "The error occurs in line 4."'
|
||||||
attributes: "Attributes"
|
attributes: "Attributes"
|
||||||
add_attribute: "Add attribute"
|
add_attribute: "Add attribute"
|
||||||
comments:
|
comments:
|
||||||
|
Reference in New Issue
Block a user