Merge pull request #1 from nwittstruck/master

CodePilot integration
This commit is contained in:
Felix Wolff
2015-03-28 17:18:59 +01:00
8 changed files with 182 additions and 89 deletions

View File

@ -13,9 +13,10 @@ $(function() {
var THEME = 'ace/theme/textmate'; var THEME = 'ace/theme/textmate';
var editors = []; var editors = [];
var active_file; var active_file = undefined;
var active_frame; var active_frame = undefined;
var running = false; var running = false;
var qa_api = undefined;
var flowrResultHtml = '<div class="panel panel-default"><div id="{{headingId}}" role="tab" class="panel-heading"><h4 class="panel-title"><a data-toggle="collapse" data-parent="#flowrHint" href="#{{collapseId}}" aria-expanded="true" aria-controls="{{collapseId}}"></a></h4></div><div id="{{collapseId}}" role="tabpanel" aria-labelledby="{{headingId}}" class="panel-collapse collapse"><div class="panel-body"></div></div></div>' var flowrResultHtml = '<div class="panel panel-default"><div id="{{headingId}}" role="tab" class="panel-heading"><h4 class="panel-title"><a data-toggle="collapse" data-parent="#flowrHint" href="#{{collapseId}}" aria-expanded="true" aria-controls="{{collapseId}}"></a></h4></div><div id="{{collapseId}}" role="tabpanel" aria-labelledby="{{headingId}}" class="panel-collapse collapse"><div class="panel-body"></div></div></div>'
@ -141,11 +142,20 @@ $(function() {
event_source.addEventListener('close', handleStderrOutputForFlowr); event_source.addEventListener('close', handleStderrOutputForFlowr);
} }
if (qa_api) {
event_source.addEventListener('close', handleStreamedResponseForCodePilot);
}
event_source.addEventListener('status', function(event) { event_source.addEventListener('status', function(event) {
showStatus(JSON.parse(event.data)); showStatus(JSON.parse(event.data));
}); });
}; };
var handleStreamedResponseForCodePilot = function(event) {
qa_api.executeCommand('syncOutput', [chunkBuffer]);
chunkBuffer = [{streamedResponse: true}];
}
var evaluateCodeWithoutStreamedResponse = function(url, callback) { var evaluateCodeWithoutStreamedResponse = function(url, callback) {
var jqxhr = ajax({ var jqxhr = ajax({
method: 'GET', method: 'GET',
@ -264,6 +274,9 @@ $(function() {
var handleTestResponse = function(response) { var handleTestResponse = function(response) {
clearOutput(); clearOutput();
printOutput(response[0], false, 0); printOutput(response[0], false, 0);
if (qa_api) {
qa_api.executeCommand('syncOutput', [response]);
}
showStatus(response[0]); showStatus(response[0]);
showTab(2); showTab(2);
}; };
@ -276,6 +289,19 @@ $(function() {
var initializeEditors = function() { var initializeEditors = function() {
$('.editor').each(function(index, element) { $('.editor').each(function(index, element) {
var editor = ace.edit(element); var editor = ace.edit(element);
if (qa_api) {
editor.getSession().on("change", function (deltaObject) {
qa_api.executeCommand('syncEditor', [active_file, deltaObject]);
});
}
var document = editor.getSession().getDocument();
// insert pre-existing code into editor. we have to use insertLines, otherwise the deltas are not properly added
var file_id = $(element).data('file-id');
var content = $('.editor-content[data-file-id=' + file_id + ']');
setActiveFile($(element).parent().data('filename'), file_id);
document.insertLines(0, content.text().split(/\n/));
editor.setReadOnly($(element).data('read-only') !== undefined); editor.setReadOnly($(element).data('read-only') !== undefined);
editor.setShowPrintMargin(false); editor.setShowPrintMargin(false);
editor.setTheme(THEME); editor.setTheme(THEME);
@ -498,6 +524,13 @@ $(function() {
return 'executable' in active_frame.data(); return 'executable' in active_frame.data();
}; };
var setActiveFile = function (filename, fileId) {
active_file = {
filename: filename,
id: fileId
};
}
var isActiveFileRenderable = function() { var isActiveFileRenderable = function() {
return 'renderable' in active_frame.data(); return 'renderable' in active_frame.data();
}; };
@ -530,10 +563,17 @@ $(function() {
panel.find('.row .col-sm-9').eq(3).find('a').attr('href', '#output-' + index); panel.find('.row .col-sm-9').eq(3).find('a').attr('href', '#output-' + index);
}; };
var chunkBuffer = [{streamedResponse: true}];
var printChunk = function(event) { var printChunk = function(event) {
var output = JSON.parse(event.data); var output = JSON.parse(event.data);
if (output) { if (output) {
printOutput(output, true, 0); printOutput(output, true, 0);
// send test response to QA
// we are expecting an array of outputs:
if (qa_api) {
chunkBuffer.push(output);
}
} else { } else {
clearOutput(); clearOutput();
$('#hint').fadeOut(); $('#hint').fadeOut();
@ -580,6 +620,10 @@ $(function() {
})) { })) {
showTimeoutMessage(); showTimeoutMessage();
} }
if (qa_api) {
// send test response to QA
qa_api.executeCommand('syncOutput', [response]);
}
}; };
var renderCode = function(event) { var renderCode = function(event) {
@ -703,10 +747,7 @@ $(function() {
var showFirstFile = function() { var showFirstFile = function() {
var frame = $('.frame[data-role="main_file"]').isPresent() ? $('.frame[data-role="main_file"]') : $('.frame').first(); var frame = $('.frame[data-role="main_file"]').isPresent() ? $('.frame[data-role="main_file"]') : $('.frame').first();
var file_id = frame.find('.editor').data('file-id'); var file_id = frame.find('.editor').data('file-id');
active_file = { setActiveFile(frame.data('filename'), file_id);
filename: frame.data('filename'),
id: file_id
};
$('#files').jstree().select_node(file_id); $('#files').jstree().select_node(file_id);
showFrame(frame); showFrame(frame);
toggleButtonStates(); toggleButtonStates();
@ -865,19 +906,32 @@ $(function() {
}) })
} }
if ($('#editor').isPresent()) { var initializeCodePilot = function() {
if (isBrowserSupported()) { if ($('#questions-column').isPresent() && QaApi.isBrowserSupported()) {
$('.score, #development-environment').show(); $('#editor-column').addClass('col-md-8').removeClass('col-md-10');
configureEditors(); $('#questions-column').addClass('col-md-3');
initializeEditors();
initializeEventHandlers(); var node = document.getElementById('questions-holder');
initializeFileTree(); var url = $('#questions-holder').data('url');
initializeTooltips();
renderScore(); qa_api = new QaApi(node, url);
showFirstFile(); }
showRequestedTab();
} else {
$('#alert').show();
} }
if ($('#editor').isPresent()) {
if (isBrowserSupported()) {
initializeCodePilot();
$('.score, #development-environment').show();
configureEditors();
initializeEditors();
initializeEventHandlers();
initializeFileTree();
initializeTooltips();
renderScore();
showFirstFile();
showRequestedTab();
} else {
$('#alert').show();
}
} }
}); });

View File

@ -86,6 +86,12 @@ class ExercisesController < ApplicationController
@submission = current_user.submissions.where(exercise_id: @exercise.id).order('created_at DESC').first @submission = current_user.submissions.where(exercise_id: @exercise.id).order('created_at DESC').first
@files = (@submission ? @submission.collect_files : @exercise.files).select(&:visible).sort_by(&:name_with_extension) @files = (@submission ? @submission.collect_files : @exercise.files).select(&:visible).sort_by(&:name_with_extension)
@paths = collect_paths(@files) @paths = collect_paths(@files)
if current_user.respond_to? :external_id
@user_id = current_user.external_id
else
@user_id = '00000001-3100-4444-9999-000000000001'
end
end end
def index def index

View File

@ -2,4 +2,17 @@ module ExerciseHelper
def embedding_parameters(exercise) def embedding_parameters(exercise)
"locale=#{I18n.locale}&token=#{exercise.token}" "locale=#{I18n.locale}&token=#{exercise.token}"
end end
def qa_js_tag
javascript_include_tag qa_url + "/assets/qa_api.js"
end
def qa_url
config = CodeOcean::Config.new(:code_ocean)
enabled = config.read[:code_pilot][:enabled]
if enabled
config.read[:code_pilot][:url]
end
end
end end

View File

@ -11,4 +11,5 @@
- else - else
= link_to(file.native_file.file.name_with_extension, file.native_file.url) = link_to(file.native_file.file.name_with_extension, file.native_file.url)
- else - else
.editor data-file-id=file.ancestor_id data-indent-size=file.file_type.indent_size data-mode=file.file_type.editor_mode data-read-only=file.read_only = file.content .editor-content.hidden data-file-id=file.ancestor_id = file.content
.editor data-file-id=file.ancestor_id data-indent-size=file.file_type.indent_size data-mode=file.file_type.editor_mode data-read-only=file.read_only

View File

@ -1,73 +1,79 @@
h1 = @exercise .row
#editor-column.col-md-10.col-md-offset-1
h1 = @exercise
span.badge.pull-right.score span.badge.pull-right.score
p.lead = @exercise.description p.lead = @exercise.description
#alert.alert.alert-danger role='alert' #alert.alert.alert-danger role='alert'
h4 = t('.alert.title') h4 = t('.alert.title')
p = t('.alert.text', application_name: application_name) p = t('.alert.text', application_name: application_name)
#development-environment #development-environment
ul.nav.nav-justified.nav-tabs role='tablist' ul.nav.nav-justified.nav-tabs role='tablist'
li.active li.active
a data-placement='top' data-toggle='tab' data-tooltip=true href='#instructions' role='tab' title=t('shared.tooltips.shortcut', shortcut: 'ALT + 1') a data-placement='top' data-toggle='tab' data-tooltip=true href='#instructions' role='tab' title=t('shared.tooltips.shortcut', shortcut: 'ALT + 1')
i.fa.fa-question i.fa.fa-question
= t('activerecord.attributes.exercise.instructions') = t('activerecord.attributes.exercise.instructions')
li li
a data-placement='top' data-toggle='tab' data-tooltip=true href='#workspace' role='tab' title=t('shared.tooltips.shortcut', shortcut: 'ALT + 2') a data-placement='top' data-toggle='tab' data-tooltip=true href='#workspace' role='tab' title=t('shared.tooltips.shortcut', shortcut: 'ALT + 2')
i.fa.fa-code i.fa.fa-code
= t('.workspace') = t('.workspace')
li li
a data-placement='top' data-toggle='tab' data-tooltip=true href='#outputInformation' role='tab' title=t('shared.tooltips.shortcut', shortcut: 'ALT + 3') a data-placement='top' data-toggle='tab' data-tooltip=true href='#outputInformation' role='tab' title=t('shared.tooltips.shortcut', shortcut: 'ALT + 3')
i.fa.fa-terminal i.fa.fa-terminal
= t('.output') = t('.output')
li li
a data-placement='top' data-toggle='tab' data-tooltip=true href='#progress' role='tab' title=t('shared.tooltips.shortcut', shortcut: 'ALT + 4') a data-placement='top' data-toggle='tab' data-tooltip=true href='#progress' role='tab' title=t('shared.tooltips.shortcut', shortcut: 'ALT + 4')
i.fa.fa-line-chart i.fa.fa-line-chart
= t('.progress') = t('.progress')
hr hr
.tab-content .tab-content
#instructions.tab-pane.active #instructions.tab-pane.active
p = render_markdown(@exercise.instructions) p = render_markdown(@exercise.instructions)
br br
p.text-center p.text-center
a#start.btn.btn-lg.btn-success a#start.btn.btn-lg.btn-success
i.fa.fa-code i.fa.fa-code
= t('.start') = t('.start')
#workspace.tab-pane = render('editor', exercise: @exercise, files: @files, submission: @submission) #workspace.tab-pane = render('editor', exercise: @exercise, files: @files, submission: @submission)
#outputInformation.tab-pane data-message-no-output=t('.no_output') #outputInformation.tab-pane data-message-no-output=t('.no_output')
#hint #hint
.panel.panel-warning .panel.panel-warning
.panel-heading = t('.hint') .panel-heading = t('.hint')
.panel-body .panel-body
#output #output
pre = t('.no_output_yet') pre = t('.no_output_yet')
- if CodeOcean::Config.new(:code_ocean).read[:flowr][:enabled] - if CodeOcean::Config.new(:code_ocean).read[:flowr][:enabled]
#flowrHint.panel.panel-info data-url=CodeOcean::Config.new(:code_ocean).read[:flowr][:url] role='tab' #flowrHint.panel.panel-info data-url=CodeOcean::Config.new(:code_ocean).read[:flowr][:url] role='tab'
.panel-heading = 'Gain more insights here' .panel-heading = 'Gain more insights here'
.panel-body .panel-body
#progress.tab-pane #progress.tab-pane
#results #results
h2 = t('.results') h2 = t('.results')
p.test-count == t('.test_count', count: 0) p.test-count == t('.test_count', count: 0)
ul.list-unstyled ul.list-unstyled
ul#dummies.hidden.list-unstyled ul#dummies.hidden.list-unstyled
li.panel.panel-default li.panel.panel-default
.panel-heading .panel-heading
h3.panel-title == t('.file', filename: '', number: 0) h3.panel-title == t('.file', filename: '', number: 0)
.panel-body .panel-body
= row(label: '.passed_tests', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe) = row(label: '.passed_tests', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe)
= row(label: 'activerecord.attributes.submission.score', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe) = row(label: 'activerecord.attributes.submission.score', value: t('shared.out_of', maximum_value: 0, value: 0).html_safe)
= row(label: '.feedback') = row(label: '.feedback')
= row(label: '.output', value: link_to(t('shared.show'), '#')) = row(label: '.output', value: link_to(t('shared.show'), '#'))
#score data-maximum-score=@exercise.maximum_score data-score=@submission.try(:score) #score data-maximum-score=@exercise.maximum_score data-score=@submission.try(:score)
h4 h4
span == "#{t('activerecord.attributes.submission.score')}:&nbsp;" span == "#{t('activerecord.attributes.submission.score')}:&nbsp;"
span.score span.score
.progress .progress
.progress-bar role='progressbar' .progress-bar role='progressbar'
br br
p.text-center = render('editor_button', classes: 'btn-lg btn-success', data: {:'data-message-confirm' => t('exercises.editor.confirm_submit'), :'data-url' => submit_exercise_path(@exercise)}, icon: 'fa fa-send', id: 'submit', label: t('exercises.editor.submit')) p.text-center = render('editor_button', classes: 'btn-lg btn-success', data: {:'data-message-confirm' => t('exercises.editor.confirm_submit'), :'data-url' => submit_exercise_path(@exercise)}, icon: 'fa fa-send', id: 'submit', label: t('exercises.editor.submit'))
- if qa_url
#questions-column
#questions-holder data-url="#{qa_url}/qa/index/#{@exercise.id}/#{@user_id}"
= qa_js_tag

View File

@ -34,6 +34,12 @@ html lang='en'
.container data-controller=controller_name .container data-controller=controller_name
= render('breadcrumbs') = render('breadcrumbs')
= render('flash') = render('flash')
= yield - if (controller_name == "exercises" && action_name == "implement")
.container-fluid
= yield
- else
.container
= yield
- template_variables = {execution_environment: @exercise.execution_environment} if action_name == 'implement' - template_variables = {execution_environment: @exercise.execution_environment} if action_name == 'implement'
= render('shared/modal', classes: 'modal-lg', id: 'modal-help', template: 'application/help', template_variables: template_variables, title: t('shared.help.headline')) = render('shared/modal', classes: 'modal-lg', id: 'modal-help', template: 'application/help', template_variables: template_variables, title: t('shared.help.headline'))

View File

@ -1,11 +1,16 @@
default: &default default: &default
flowr: flowr:
enabled: false enabled: false
code_pilot:
enabled: false
development: development:
flowr: flowr:
enabled: true enabled: true
url: http://example.org:3000/api/exceptioninfo?id=&lang=auto url: http://example.org:3000/api/exceptioninfo?id=&lang=auto
code_pilot:
enabled: true
url: //localhost:3000
production: production:
<<: *default <<: *default

View File

@ -1,3 +1,5 @@
test: test:
flowr: flowr:
enabled: false enabled: false
code_pilot:
enabled: false