From 96de763b8314c0fb6a194fc985cb6191c651dbe0 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Fri, 17 Jun 2016 16:42:07 +0200 Subject: [PATCH 1/5] Handle scoring presentation to client via websockets --- app/assets/javascripts/editor.js.erb | 19 ++++++++++--------- app/controllers/submissions_controller.rb | 6 +++++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index 1e64195e..f9555e92 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -184,8 +184,8 @@ $(function() { (streamed ? evaluateCodeWithStreamedResponse : evaluateCodeWithoutStreamedResponse)(url, callback); }; - var evaluateCodeWithStreamedResponse = function(url, callback) { - initWebsocketConnection(url); + var evaluateCodeWithStreamedResponse = function(url, onmessageFunction) { + initWebsocketConnection(url, onmessageFunction); // TODO only init turtle when required initTurtle(); @@ -306,9 +306,10 @@ $(function() { } }; - var handleScoringResponse = function(response) { - printScoringResults(response); - var score = _.reduce(response, function(sum, result) { + var handleScoringResponse = function(websocket_event) { + results = JSON.parse(websocket_event.data); + printScoringResults(results); + var score = _.reduce(results, function(sum, result) { return sum + result.score * result.weight; }, 0).toFixed(2); $('#score').data('score', score); @@ -743,7 +744,7 @@ $(function() { showSpinner($('#run')); toggleButtonStates(); 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); }); }); } }; @@ -778,7 +779,7 @@ $(function() { createSubmission(this, null, function(response) { showSpinner($('#assess')); var url = response.score_url; - evaluateCode(url, false, handleScoringResponse); + evaluateCode(url, true, handleScoringResponse); }); }; @@ -974,14 +975,14 @@ $(function() { $('#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) //causes: Puma::HttpParserError: Invalid HTTP format, parsing fails. //TODO: make sure that this gets cached. 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.onclose = function(evt) { /* expected at some point */ }; - websocket.onmessage = function(evt) { parseCanvasMessage(evt.data, true); }; + websocket.onmessage = onmessageFunction; websocket.onerror = function(evt) { showWebsocketError(); }; websocket.flush = function() { this.send('\n'); } }; diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 4f9d6a5b..c656ea87 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -214,7 +214,11 @@ class SubmissionsController < ApplicationController end 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 def set_docker_client From 7c1be5594a72d210997fb2757ef3448712cbbe55 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Fri, 17 Jun 2016 17:23:47 +0200 Subject: [PATCH 2/5] also handle testcommand via websocket --- app/assets/javascripts/editor.js.erb | 11 ++++++----- app/controllers/submissions_controller.rb | 10 ++++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index f9555e92..dc79ccff 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -350,13 +350,14 @@ $(function() { flowrOutputBuffer = ''; }; - var handleTestResponse = function(response) { + var handleTestResponse = function(websocket_event) { + result = JSON.parse(websocket_event.data); clearOutput(); - printOutput(response[0], false, 0); + printOutput(result, false, 0); if (qa_api) { - qa_api.executeCommand('syncOutput', [response]); + qa_api.executeCommand('syncOutput', [result]); } - showStatus(response[0]); + showStatus(result); showTab(1); }; @@ -956,7 +957,7 @@ $(function() { createSubmission(this, null, function(response) { showSpinner($('#test')); var url = response.test_url.replace(FILENAME_URL_PLACEHOLDER, active_file.filename); - evaluateCode(url, false, handleTestResponse); + evaluateCode(url, true, handleTestResponse); }); } }; diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index c656ea87..335e4b0c 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -270,8 +270,14 @@ class SubmissionsController < ApplicationController private :store_error def test - output = @docker_client.execute_test_command(@submission, params[:filename]) - render(json: [output]) + hijack do |tubesock| + 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 def with_server_sent_events From d29cf9cf6134691cdeba974499d4979b46521848 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Fri, 17 Jun 2016 19:22:25 +0200 Subject: [PATCH 3/5] Also support run output for QaApi --- app/assets/javascripts/editor.js.erb | 15 +++++++++++++++ lib/docker_client.rb | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index dc79ccff..f9ab5b94 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -37,6 +37,7 @@ $(function() { var ENTER_KEY_CODE = 13; var flowrOutputBuffer = ""; + var QaApiOutputBuffer = {'stdout': '', 'stderr': ''}; var flowrResultHtml = '
' var ajax = function(options) { @@ -317,6 +318,14 @@ $(function() { 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 var isFlowrEnabled = true;//parseInt($('#editor').data('user-id'))%2 == 0; var handleStderrOutputForFlowr = function() { @@ -586,10 +595,15 @@ $(function() { } else if (output.stderr) { element.addClass('text-warning').append(output.stderr); + flowrOutputBuffer += output.stderr; + QaApiOutputBuffer.stderr += output.stderr; } else if (output.stdout) { //if (output_mode_is_streaming){ element.addClass('text-success').append(output.stdout); flowrOutputBuffer += output.stdout; + QaApiOutputBuffer.stdout += output.stdout; + + //}else{ // element.addClass('text-success'); // element.data('content_buffer' , element.data('content_buffer') + output.stdout); @@ -1037,6 +1051,7 @@ $(function() { break; case 'exit': killWebsocketAndContainer(); + handleQaApiOutput(); handleStderrOutputForFlowr(); break; case 'timeout': diff --git a/lib/docker_client.rb b/lib/docker_client.rb index 80986377..f993c37c 100644 --- a/lib/docker_client.rb +++ b/lib/docker_client.rb @@ -200,7 +200,7 @@ class DockerClient execute_command(command, nil, block) end - #only used by server sent events (deprecated?) + #only used by score def execute_command(command, before_execution_block, output_consuming_block) #tries ||= 0 @container = DockerContainerPool.get_container(@execution_environment) From c21bf3f3d3aeb4a2d4130886e644e7e69d071249 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Fri, 17 Jun 2016 19:24:04 +0200 Subject: [PATCH 4/5] Adapt Modernizr requires.. We no longer need eventsource --- app/assets/javascripts/editor.js.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index f9ab5b94..c00d7350 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -538,8 +538,8 @@ $(function() { }; var isBrowserSupported = function() { - // eventsource tests for server send events (used for scoring), websockets is used for run - return Modernizr.eventsource && Modernizr.websockets; + // websockets is used for run, score and test + return Modernizr.websockets; }; var populatePanel = function(panel, result, index) { From 79309f6697725f67521ce2cf8f56d4ebbd53e7a1 Mon Sep 17 00:00:00 2001 From: Ralf Teusner Date: Thu, 28 Jul 2016 15:49:23 +0200 Subject: [PATCH 5/5] prevent printing out "undefined" if no container could be fetched and thus the response has no stdout or stderr. --- app/assets/javascripts/editor.js.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/editor.js.erb b/app/assets/javascripts/editor.js.erb index d173198f..2d73bbf0 100644 --- a/app/assets/javascripts/editor.js.erb +++ b/app/assets/javascripts/editor.js.erb @@ -604,11 +604,11 @@ $(function() { // output_mode_is_streaming = false; //} if (!colorize) { - if(output.stdout != ''){ + if(output.stdout != undefined && output.stdout != ''){ element.append(output.stdout) } - if(output.stderr != ''){ + if(output.stderr != undefined && output.stderr != ''){ element.append('There was an error: StdErr: ' + output.stderr); }