Merge pull request #64 from openHPI/score-websocket

Score websocket
This commit is contained in:
rteusner
2016-07-28 15:52:21 +02:00
committed by GitHub
3 changed files with 48 additions and 23 deletions

View File

@ -41,6 +41,7 @@ $(function() {
var ENTER_KEY_CODE = 13; var ENTER_KEY_CODE = 13;
var flowrOutputBuffer = ""; var flowrOutputBuffer = "";
var QaApiOutputBuffer = {'stdout': '', 'stderr': ''};
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>'
var ajax = function(options) { var ajax = function(options) {
@ -191,8 +192,8 @@ $(function() {
(streamed ? evaluateCodeWithStreamedResponse : evaluateCodeWithoutStreamedResponse)(url, callback); (streamed ? evaluateCodeWithStreamedResponse : evaluateCodeWithoutStreamedResponse)(url, callback);
}; };
var evaluateCodeWithStreamedResponse = function(url, callback) { var evaluateCodeWithStreamedResponse = function(url, onmessageFunction) {
initWebsocketConnection(url); initWebsocketConnection(url, onmessageFunction);
// TODO only init turtle when required // TODO only init turtle when required
initTurtle(); initTurtle();
@ -313,9 +314,10 @@ $(function() {
} }
}; };
var handleScoringResponse = function(response) { var handleScoringResponse = function(websocket_event) {
printScoringResults(response); results = JSON.parse(websocket_event.data);
var score = _.reduce(response, function(sum, result) { printScoringResults(results);
var score = _.reduce(results, function(sum, result) {
return sum + result.score * result.weight; return sum + result.score * result.weight;
}, 0).toFixed(2); }, 0).toFixed(2);
$('#score').data('score', score); $('#score').data('score', score);
@ -323,6 +325,14 @@ $(function() {
showTab(2); showTab(2);
}; };
var handleQaApiOutput = function() {
if (qa_api) {
qa_api.executeCommand('syncOutput', [[QaApiOutputBuffer]]);
// reset the object
}
QaApiOutputBuffer = {'stdout': '', 'stderr': ''};
}
// activate flowr only for half of the audience // activate flowr only for half of the audience
var isFlowrEnabled = true;//parseInt($('#editor').data('user-id'))%2 == 0; var isFlowrEnabled = true;//parseInt($('#editor').data('user-id'))%2 == 0;
var handleStderrOutputForFlowr = function() { var handleStderrOutputForFlowr = function() {
@ -356,13 +366,14 @@ $(function() {
flowrOutputBuffer = ''; flowrOutputBuffer = '';
}; };
var handleTestResponse = function(response) { var handleTestResponse = function(websocket_event) {
result = JSON.parse(websocket_event.data);
clearOutput(); clearOutput();
printOutput(response[0], false, 0); printOutput(result, false, 0);
if (qa_api) { if (qa_api) {
qa_api.executeCommand('syncOutput', [response]); qa_api.executeCommand('syncOutput', [result]);
} }
showStatus(response[0]); showStatus(result);
showTab(1); showTab(1);
}; };
@ -546,8 +557,8 @@ $(function() {
}; };
var isBrowserSupported = function() { var isBrowserSupported = function() {
// eventsource tests for server send events (used for scoring), websockets is used for run // websockets is used for run, score and test
return Modernizr.eventsource && Modernizr.websockets; return Modernizr.websockets;
}; };
var populatePanel = function(panel, result, index) { var populatePanel = function(panel, result, index) {
@ -593,20 +604,23 @@ $(function() {
// output_mode_is_streaming = false; // output_mode_is_streaming = false;
//} //}
if (!colorize) { if (!colorize) {
if(output.stdout != ''){ if(output.stdout != undefined && output.stdout != ''){
element.append(output.stdout) element.append(output.stdout)
} }
if(output.stderr != ''){ if(output.stderr != undefined && output.stderr != ''){
element.append('There was an error: StdErr: ' + output.stderr); element.append('There was an error: StdErr: ' + output.stderr);
} }
} else if (output.stderr) { } else if (output.stderr) {
element.addClass('text-warning').append(output.stderr); element.addClass('text-warning').append(output.stderr);
flowrOutputBuffer += output.stderr;
QaApiOutputBuffer.stderr += output.stderr;
} else if (output.stdout) { } else if (output.stdout) {
//if (output_mode_is_streaming){ //if (output_mode_is_streaming){
element.addClass('text-success').append(output.stdout); element.addClass('text-success').append(output.stdout);
// flowrOutputBuffer += output.stdout; flowrOutputBuffer += output.stdout;
QaApiOutputBuffer.stdout += output.stdout;
//}else{ //}else{
// element.addClass('text-success'); // element.addClass('text-success');
// element.data('content_buffer' , element.data('content_buffer') + output.stdout); // element.data('content_buffer' , element.data('content_buffer') + output.stdout);
@ -762,7 +776,7 @@ $(function() {
showSpinner($('#run')); showSpinner($('#run'));
toggleButtonStates(); toggleButtonStates();
var url = response.run_url.replace(FILENAME_URL_PLACEHOLDER, active_file.filename); var url = response.run_url.replace(FILENAME_URL_PLACEHOLDER, active_file.filename);
evaluateCode(url, true, printChunk); evaluateCode(url, true, function(evt) { parseCanvasMessage(evt.data, true); });
}); });
} }
}; };
@ -797,7 +811,7 @@ $(function() {
createSubmission(this, null, function(response) { createSubmission(this, null, function(response) {
showSpinner($('#assess')); showSpinner($('#assess'));
var url = response.score_url; var url = response.score_url;
evaluateCode(url, false, handleScoringResponse); evaluateCode(url, true, handleScoringResponse);
}); });
}; };
@ -976,7 +990,7 @@ $(function() {
createSubmission(this, null, function(response) { createSubmission(this, null, function(response) {
showSpinner($('#test')); showSpinner($('#test'));
var url = response.test_url.replace(FILENAME_URL_PLACEHOLDER, active_file.filename); var url = response.test_url.replace(FILENAME_URL_PLACEHOLDER, active_file.filename);
evaluateCode(url, false, handleTestResponse); evaluateCode(url, true, handleTestResponse);
}); });
} }
}; };
@ -995,14 +1009,14 @@ $(function() {
$('#test').toggle(isActiveFileTestable()); $('#test').toggle(isActiveFileTestable());
}; };
var initWebsocketConnection = function(url) { var initWebsocketConnection = function(url, onmessageFunction) {
//TODO: get the protocol from config file dependent on environment. (dev: ws, prod: wss) //TODO: get the protocol from config file dependent on environment. (dev: ws, prod: wss)
//causes: Puma::HttpParserError: Invalid HTTP format, parsing fails. //causes: Puma::HttpParserError: Invalid HTTP format, parsing fails.
//TODO: make sure that this gets cached. //TODO: make sure that this gets cached.
websocket = new WebSocket('<%= DockerClient.config['ws_client_protocol'] %>' + window.location.hostname + ':' + window.location.port + url); websocket = new WebSocket('<%= DockerClient.config['ws_client_protocol'] %>' + window.location.hostname + ':' + window.location.port + url);
websocket.onopen = function(evt) { resetOutputTab(); }; // todo show some kind of indicator for established connection websocket.onopen = function(evt) { resetOutputTab(); }; // todo show some kind of indicator for established connection
websocket.onclose = function(evt) { /* expected at some point */ }; websocket.onclose = function(evt) { /* expected at some point */ };
websocket.onmessage = function(evt) { parseCanvasMessage(evt.data, true); }; websocket.onmessage = onmessageFunction;
websocket.onerror = function(evt) { showWebsocketError(); }; websocket.onerror = function(evt) { showWebsocketError(); };
websocket.flush = function() { this.send('\n'); } websocket.flush = function() { this.send('\n'); }
}; };
@ -1056,6 +1070,7 @@ $(function() {
break; break;
case 'exit': case 'exit':
killWebsocketAndContainer(); killWebsocketAndContainer();
handleQaApiOutput();
handleStderrOutputForFlowr(); handleStderrOutputForFlowr();
augmentStacktraceInOutput(); augmentStacktraceInOutput();
break; break;

View File

@ -228,7 +228,11 @@ class SubmissionsController < ApplicationController
end end
def score def score
render(json: score_submission(@submission)) hijack do |tubesock|
Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive?
# tubesock is the socket to the client
tubesock.send_data JSON.dump(score_submission(@submission))
end
end end
def set_docker_client def set_docker_client
@ -280,8 +284,14 @@ class SubmissionsController < ApplicationController
private :store_error private :store_error
def test def test
output = @docker_client.execute_test_command(@submission, params[:filename]) hijack do |tubesock|
render(json: [output]) Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive?
output = @docker_client.execute_test_command(@submission, params[:filename])
# tubesock is the socket to the client
tubesock.send_data JSON.dump(output)
end
end end
def with_server_sent_events def with_server_sent_events

View File

@ -200,7 +200,7 @@ class DockerClient
execute_command(command, nil, block) execute_command(command, nil, block)
end end
#only used by server sent events (deprecated?) #only used by score
def execute_command(command, before_execution_block, output_consuming_block) def execute_command(command, before_execution_block, output_consuming_block)
#tries ||= 0 #tries ||= 0
@container = DockerContainerPool.get_container(@execution_environment) @container = DockerContainerPool.get_container(@execution_environment)