Moved sockets. Fixed sockets.
This commit is contained in:
@ -30,11 +30,8 @@ var CodeOceanEditor = {
|
|||||||
output_mode_is_streaming: true,
|
output_mode_is_streaming: true,
|
||||||
runmode: this.NONE,
|
runmode: this.NONE,
|
||||||
|
|
||||||
websocket: null,
|
|
||||||
numMessages: 0,
|
numMessages: 0,
|
||||||
prompt: $('#prompt'),
|
prompt: $('#prompt'),
|
||||||
commands: ['input', 'write', 'turtle', 'turtlebatch', 'render', 'exit', 'timeout', 'status'],
|
|
||||||
streams: ['stdin', 'stdout', 'stderr'],
|
|
||||||
lastCopyText: null,
|
lastCopyText: null,
|
||||||
|
|
||||||
autosaveTimer: null,
|
autosaveTimer: null,
|
||||||
@ -42,7 +39,6 @@ var CodeOceanEditor = {
|
|||||||
|
|
||||||
ENTER_KEY_CODE: 13,
|
ENTER_KEY_CODE: 13,
|
||||||
|
|
||||||
flowrOutputBuffer: "",
|
|
||||||
QaApiOutputBuffer: {'stdout': '', 'stderr': ''},
|
QaApiOutputBuffer: {'stdout': '', 'stderr': ''},
|
||||||
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>',
|
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>',
|
||||||
|
|
||||||
@ -255,9 +251,9 @@ var CodeOceanEditor = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
initializeEventHandlers: function () {
|
initializeEventHandlers: function () {
|
||||||
$(document).on('click', '#results a', this.showOutput);
|
$(document).on('click', '#results a', this.showOutput.bind(this));
|
||||||
$(document).on('keypress', this.handleKeyPress);
|
$(document).on('keypress', this.handleKeyPress.bind(this));
|
||||||
$('a[data-toggle="tab"]').on('show.bs.tab', this.storeTab);
|
$('a[data-toggle="tab"]').on('show.bs.tab', this.storeTab.bind(this));
|
||||||
this.initializeFileTreeButtons();
|
this.initializeFileTreeButtons();
|
||||||
this.initializeWorkflowButtons();
|
this.initializeWorkflowButtons();
|
||||||
this.initializeWorkspaceButtons();
|
this.initializeWorkspaceButtons();
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
CodeOceanEditorEvaluation = {
|
CodeOceanEditorEvaluation = {
|
||||||
chunkBuffer: [{streamedResponse: true}],
|
chunkBuffer: [{streamedResponse: true}],
|
||||||
|
|
||||||
evaluateCode: function (url, onmessageFunction) {
|
handleScoringResponse: function (results) {
|
||||||
this.initWebsocketConnection(url, onmessageFunction);;
|
|
||||||
},
|
|
||||||
|
|
||||||
handleScoringResponse: function (websocket_event) {
|
|
||||||
var results = JSON.parse(websocket_event.data);
|
|
||||||
this.printScoringResults(results);
|
this.printScoringResults(results);
|
||||||
var score = _.reduce(results, function (sum, result) {
|
var score = _.reduce(results, function (sum, result) {
|
||||||
return sum + result.score * result.weight;
|
return sum + result.score * result.weight;
|
||||||
@ -16,8 +11,7 @@ CodeOceanEditorEvaluation = {
|
|||||||
this.showTab(2);
|
this.showTab(2);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleTestResponse: function (websocket_event) {
|
handleTestResponse: function (result) {
|
||||||
var result = JSON.parse(websocket_event.data);
|
|
||||||
this.clearOutput();
|
this.clearOutput();
|
||||||
this.printOutput(result, false, 0);
|
this.printOutput(result, false, 0);
|
||||||
if (this.qa_api) {
|
if (this.qa_api) {
|
||||||
@ -111,7 +105,7 @@ CodeOceanEditorEvaluation = {
|
|||||||
this.createSubmission('#assess', null, function (response) {
|
this.createSubmission('#assess', null, function (response) {
|
||||||
this.showSpinner($('#assess'));
|
this.showSpinner($('#assess'));
|
||||||
var url = response.score_url;
|
var url = response.score_url;
|
||||||
this.evaluateCode(url, this.handleScoringResponse.bind(this));
|
this.initializeSocketForScoring(url);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
46
app/assets/javascripts/editor/execution.js.erb
Normal file
46
app/assets/javascripts/editor/execution.js.erb
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
CodeOceanEditorWebsocket = {
|
||||||
|
websocket: null,
|
||||||
|
|
||||||
|
createSocketUrl: function(url) {
|
||||||
|
return '<%= DockerClient.config['ws_client_protocol'] %>' + window.location.hostname + ':' + window.location.port + url;
|
||||||
|
},
|
||||||
|
|
||||||
|
initializeSocket: function(url) {
|
||||||
|
this.websocket = new CommandSocket(this.createSocketUrl(url),
|
||||||
|
function (evt) {
|
||||||
|
this.resetOutputTab();
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
this.websocket.onError(this.showWebsocketError.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
initializeSocketForTesting: function(url) {
|
||||||
|
this.initializeSocket(url);
|
||||||
|
this.websocket.on('default',this.handleTestResponse.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
initializeSocketForScoring: function(url) {
|
||||||
|
this.initializeSocket(url);
|
||||||
|
this.websocket.on('default',this.handleScoringResponse.bind(this))
|
||||||
|
},
|
||||||
|
|
||||||
|
initializeSocketForRunning: function(url) {
|
||||||
|
this.initializeSocket(url);
|
||||||
|
this.websocket.on('input',this.showPrompt.bind(this));
|
||||||
|
this.websocket.on('write', this.printWebsocketOutput.bind(this));
|
||||||
|
this.websocket.on('turtle', this.handleTurtleCommand.bind(this));
|
||||||
|
this.websocket.on('turtlebatch', this.handleTurtlebatchCommand.bind(this));
|
||||||
|
this.websocket.on('render', this.renderWebsocketOutput.bind(this));
|
||||||
|
this.websocket.on('exit', this.handleExitCommand.bind(this));
|
||||||
|
this.websocket.on('timeout', this.showTimeoutMessage.bind(this));
|
||||||
|
this.websocket.on('status', this.showStatus.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
handleExitCommand: function() {
|
||||||
|
this.killWebsocketAndContainer();
|
||||||
|
this.handleQaApiOutput();
|
||||||
|
this.handleStderrOutputForFlowr();
|
||||||
|
this.augmentStacktraceInOutput();
|
||||||
|
this.cleanUpTurtle();
|
||||||
|
}
|
||||||
|
};
|
@ -134,7 +134,7 @@ CodeOceanEditorSubmissions = {
|
|||||||
this.showSpinner($('#run'));
|
this.showSpinner($('#run'));
|
||||||
this.toggleButtonStates();
|
this.toggleButtonStates();
|
||||||
var url = response.run_url.replace(this.FILENAME_URL_PLACEHOLDER, this.active_file.filename);
|
var url = response.run_url.replace(this.FILENAME_URL_PLACEHOLDER, this.active_file.filename);
|
||||||
this.evaluateCode(url, function(evt) { this.parseCanvasMessage(evt.data, true); }.bind(this));
|
this.initializeSocketForRunning(url);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -154,7 +154,7 @@ CodeOceanEditorSubmissions = {
|
|||||||
this.createSubmission('#test', null, function(response) {
|
this.createSubmission('#test', null, function(response) {
|
||||||
this.showSpinner($('#test'));
|
this.showSpinner($('#test'));
|
||||||
var url = response.test_url.replace(this.FILENAME_URL_PLACEHOLDER, this.active_file.filename);
|
var url = response.test_url.replace(this.FILENAME_URL_PLACEHOLDER, this.active_file.filename);
|
||||||
this.evaluateCode(url, this.handleTestResponse.bind(this));
|
this.initializeSocketForTesting();
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -16,6 +16,8 @@ CodeOceanEditorTurtle = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleTurtleCommand: function (msg) {
|
handleTurtleCommand: function (msg) {
|
||||||
|
this.initTurtle();
|
||||||
|
this.showCanvas();
|
||||||
if (msg.action in this.turtlescreen) {
|
if (msg.action in this.turtlescreen) {
|
||||||
var result = this.turtlescreen[msg.action].apply(this.turtlescreen, msg.args);
|
var result = this.turtlescreen[msg.action].apply(this.turtlescreen, msg.args);
|
||||||
this.websocket.send(JSON.stringify({cmd: 'result', 'result': result}));
|
this.websocket.send(JSON.stringify({cmd: 'result', 'result': result}));
|
||||||
@ -26,6 +28,8 @@ CodeOceanEditorTurtle = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleTurtlebatchCommand: function (msg) {
|
handleTurtlebatchCommand: function (msg) {
|
||||||
|
this.initTurtle();
|
||||||
|
this.showCanvas();
|
||||||
for (var i = 0; i < msg.batch.length; i++) {
|
for (var i = 0; i < msg.batch.length; i++) {
|
||||||
var cmd = msg.batch[i];
|
var cmd = msg.batch[i];
|
||||||
this.turtlescreen[cmd[0]].apply(this.turtlescreen, cmd[1]);
|
this.turtlescreen[cmd[0]].apply(this.turtlescreen, cmd[1]);
|
||||||
|
@ -1,89 +1,90 @@
|
|||||||
CodeOceanEditorWebsocket = {
|
CommandSocket = function(url, onOpen) {
|
||||||
initWebsocketConnection: function (url, onmessageFunction) {
|
this.handlers = {};
|
||||||
//TODO: get the protocol from config file dependent on environment. (dev: ws, prod: wss)
|
this.websocket = new WebSocket(url);
|
||||||
//causes: Puma::HttpParserError: Invalid HTTP format, parsing fails.
|
this.websocket.onopen = onOpen;
|
||||||
//TODO: make sure that this gets cached.
|
this.websocket.onmessage = this.onMessage.bind(this);
|
||||||
this.websocket = new WebSocket('<%= DockerClient.config['ws_client_protocol'] %>' + window.location.hostname + ':' + window.location.port + url);
|
this.websocket.flush = function () {
|
||||||
this.websocket.onopen = function (evt) {
|
this.send('\n');
|
||||||
this.resetOutputTab();
|
|
||||||
}.bind(this); // todo show some kind of indicator for established connection
|
|
||||||
this.websocket.onclose = function (evt) { /* expected at some point */
|
|
||||||
}.bind(this);
|
|
||||||
this.websocket.onmessage = onmessageFunction;
|
|
||||||
this.websocket.onerror = function (evt) {
|
|
||||||
this.showWebsocketError();
|
|
||||||
}.bind(this);
|
|
||||||
this.websocket.flush = function () {
|
|
||||||
this.send('\n');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
//ToDo: Move websocket and commands variable in here
|
|
||||||
executeWebsocketCommand: function (msg) {
|
|
||||||
if ($.inArray(msg.cmd, this.commands) == -1) {
|
|
||||||
console.log("Unknown command: " + msg.cmd);
|
|
||||||
// skipping unregistered commands is required
|
|
||||||
// as we may receive mirrored response due to internal behaviour
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (msg.cmd) {
|
|
||||||
case 'input':
|
|
||||||
this.showPrompt(msg);
|
|
||||||
break;
|
|
||||||
case 'write':
|
|
||||||
this.printWebsocketOutput(msg);
|
|
||||||
break;
|
|
||||||
case 'turtle':
|
|
||||||
this.initTurtle();
|
|
||||||
this.showCanvas();
|
|
||||||
this.handleTurtleCommand(msg);
|
|
||||||
break;
|
|
||||||
case 'turtlebatch':
|
|
||||||
this.initTurtle();
|
|
||||||
this.showCanvas();
|
|
||||||
this.handleTurtlebatchCommand(msg);
|
|
||||||
break;
|
|
||||||
case 'render':
|
|
||||||
this.renderWebsocketOutput(msg);
|
|
||||||
break;
|
|
||||||
case 'exit':
|
|
||||||
this.killWebsocketAndContainer();
|
|
||||||
this.handleQaApiOutput();
|
|
||||||
this.handleStderrOutputForFlowr();
|
|
||||||
this.augmentStacktraceInOutput();
|
|
||||||
this.cleanUpTurtle();
|
|
||||||
break;
|
|
||||||
case 'timeout':
|
|
||||||
// just show the timeout message here. Another exit command is sent by the rails backend when the socket to the docker container closes.
|
|
||||||
this.showTimeoutMessage();
|
|
||||||
break;
|
|
||||||
case 'status':
|
|
||||||
this.showStatus(msg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
parseCanvasMessage: function (message, recursive) {
|
|
||||||
var msg;
|
|
||||||
message = message.replace(/^\s+|\s+$/g, "");
|
|
||||||
try {
|
|
||||||
// todo validate json instead of catching
|
|
||||||
msg = JSON.parse(message);
|
|
||||||
} catch (e) {
|
|
||||||
if (!recursive) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// why does docker sometimes send multiple commands at once?
|
|
||||||
message = message.replace(/^\s+|\s+$/g, "");
|
|
||||||
var messages = message.split("\n");
|
|
||||||
for (var i = 0; i < messages.length; i++) {
|
|
||||||
if (!messages[i]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
this.parseCanvasMessage(messages[i], false);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.executeWebsocketCommand(msg);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CommandSocket.prototype.onError = function(callback){
|
||||||
|
this.websocket.onerror = callback
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows it to register an event-handler on the given cmd.
|
||||||
|
* The handler needs to accept one argument, the message.
|
||||||
|
* There is only handler per command at the moment.
|
||||||
|
* @param command
|
||||||
|
* @param handler
|
||||||
|
*/
|
||||||
|
CommandSocket.prototype.on = function(command, handler) {
|
||||||
|
this.handlers[command] = handler;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to initialize the recursive message parser.
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
CommandSocket.prototype.onMessage = function(event) {
|
||||||
|
//Parses the message (serches for linebreaks) and executes every contained cmd.
|
||||||
|
this.parseMessage(event.data, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a message, checks wether it contains multiple commands (seperated by linebreaks)
|
||||||
|
* This needs to be done because of the behavior of the docker-socket connection.
|
||||||
|
* Because of this, sometimes multiple commands might be executed in one message.
|
||||||
|
* @param message
|
||||||
|
* @param recursive
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
CommandSocket.prototype.parseMessage = function(message, recursive) {
|
||||||
|
var msg;
|
||||||
|
var message_string = message.replace(/^\s+|\s+$/g, "");
|
||||||
|
try {
|
||||||
|
// todo validate json instead of catching
|
||||||
|
msg = JSON.parse(message_string);
|
||||||
|
} catch (e) {
|
||||||
|
if (!recursive) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// why does docker sometimes send multiple commands at once?
|
||||||
|
message_string = message_string.replace(/^\s+|\s+$/g, "");
|
||||||
|
var messages = message_string.split("\n");
|
||||||
|
for (var i = 0; i < messages.length; i++) {
|
||||||
|
if (!messages[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this.parseMessage(messages[i], false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.executeCommand(msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the handler that is registered for a certain command.
|
||||||
|
* Does nothing if the command was not specified yet.
|
||||||
|
* If there is a null-handler (defined with on('default',func)) this gets
|
||||||
|
* executed if the command was not registered or the message has no cmd prop.
|
||||||
|
* @param cmd
|
||||||
|
*/
|
||||||
|
CommandSocket.prototype.executeCommand = function(cmd) {
|
||||||
|
if ('cmd' in cmd && cmd.cmd in this.handlers) {
|
||||||
|
this.handlers[cmd.cmd](cmd);
|
||||||
|
} else if ('default' in this.handlers) {
|
||||||
|
this.handlers['default'](cmd);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to send a message through the socket.
|
||||||
|
* If data is not a string we'll try use jsonify to make it a string.
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
CommandSocket.prototype.send = function(data) {
|
||||||
|
this.websocket.send(data);
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user