Merge remote-tracking branch 'origin/master' into statistics

This commit is contained in:
Maximilian Grundke
2016-03-02 12:01:16 +01:00
27 changed files with 533 additions and 145 deletions

View File

@@ -30,7 +30,7 @@ $(function() {
numMessages = 0,
turtlecanvas = $('#turtlecanvas'),
prompt = $('#prompt'),
commands = ['input', 'write', 'turtle', 'turtlebatch', 'exit', 'timeout', 'status'],
commands = ['input', 'write', 'turtle', 'turtlebatch', 'render', 'exit', 'timeout', 'status'],
streams = ['stdin', 'stdout', 'stderr'];
var ENTER_KEY_CODE = 13;
@@ -245,6 +245,18 @@ $(function() {
}
};
var findOrCreateRenderElement = function(index) {
if ($('#render-' + index).isPresent()) {
return $('#render-' + index);
} else {
var element = $('<div>').attr('id', 'render-' + index);
$('#render').append(element);
return element;
}
};
var getPanelClass = function(result) {
if (result.stderr && !result.score) {
return 'panel-danger';
@@ -1172,6 +1184,9 @@ $(function() {
showCanvas();
handleTurtlebatchCommand(msg);
break;
case 'render':
renderWebsocketOutput(msg);
break;
case 'exit':
killWebsocketAndContainer();
break;
@@ -1185,6 +1200,11 @@ $(function() {
}
};
var renderWebsocketOutput = function(msg){
var element = findOrCreateRenderElement(0);
element.append(msg.data);
};
var printWebsocketOutput = function(msg) {
if (!msg.data) {
return;

View File

@@ -0,0 +1,3 @@
// Place all the styles related to the CodeHarborLinks controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

View File

@@ -0,0 +1,69 @@
body {
background-color: #fff;
color: #333;
font-family: verdana, arial, helvetica, sans-serif;
font-size: 13px;
line-height: 18px;
}
p, ol, ul, td {
font-family: verdana, arial, helvetica, sans-serif;
font-size: 13px;
line-height: 18px;
}
pre {
background-color: #eee;
padding: 10px;
font-size: 11px;
}
a {
color: #000;
&:visited {
color: #666;
}
&:hover {
color: #fff;
background-color: #000;
}
}
div {
&.field, &.actions {
margin-bottom: 10px;
}
}
#notice {
color: green;
}
.field_with_errors {
padding: 2px;
background-color: red;
display: table;
}
#error_explanation {
width: 450px;
border: 2px solid red;
padding: 7px;
padding-bottom: 0;
margin-bottom: 20px;
background-color: #f0f0f0;
h2 {
text-align: left;
font-weight: bold;
padding: 5px 5px 5px 15px;
font-size: 12px;
margin: -7px;
margin-bottom: 0px;
background-color: #c00;
color: #fff;
}
ul li {
font-size: 12px;
list-style: square;
}
}

View File

@@ -0,0 +1,68 @@
class CodeHarborLinksController < ApplicationController
include CommonBehavior
before_action :set_code_harbor_link, only: [:show, :edit, :update, :destroy]
def authorize!
authorize(@code_harbor_link || @code_harbor_links)
end
private :authorize!
# GET /code_harbor_links
# GET /code_harbor_links.json
def index
@code_harbor_links = CodeHarborLink.where(user_id: current_user.id).paginate(page: params[:page])
authorize!
end
# GET /code_harbor_links/1
# GET /code_harbor_links/1.json
def show
authorize!
end
# GET /code_harbor_links/new
def new
@code_harbor_link = CodeHarborLink.new
authorize!
end
# GET /code_harbor_links/1/edit
def edit
authorize!
end
# POST /code_harbor_links
# POST /code_harbor_links.json
def create
@code_harbor_link = CodeHarborLink.new(code_harbor_link_params)
@code_harbor_link.user = current_user
authorize!
create_and_respond(object: @code_harbor_link)
end
# PATCH/PUT /code_harbor_links/1
# PATCH/PUT /code_harbor_links/1.json
def update
update_and_respond(object: @code_harbor_link, params: code_harbor_link_params)
authorize!
end
# DELETE /code_harbor_links/1
# DELETE /code_harbor_links/1.json
def destroy
destroy_and_respond(object: @code_harbor_link)
end
private
# Use callbacks to share common setup or constraints between actions.
def set_code_harbor_link
@code_harbor_link = CodeHarborLink.find(params[:id])
@code_harbor_link.user = current_user
authorize!
end
# Never trust parameters from the scary internet, only allow the white list through.
def code_harbor_link_params
params.require(:code_harbor_link).permit(:oauth2token)
end
end

View File

@@ -11,6 +11,10 @@ class ExercisesController < ApplicationController
before_action :set_file_types, only: [:create, :edit, :new, :update]
before_action :set_teams, only: [:create, :edit, :new, :update]
skip_before_filter :verify_authenticity_token, only: [:import_proforma_xml]
skip_after_action :verify_authorized, only: [:import_proforma_xml]
skip_after_action :verify_policy_scoped, only: [:import_proforma_xml]
def authorize!
authorize(@exercise || @exercises)
end
@@ -62,6 +66,58 @@ class ExercisesController < ApplicationController
def edit
end
def import_proforma_xml
begin
user = user_for_oauth2_request()
exercise = Exercise.new
request_body = request.body.read
exercise.from_proforma_xml(request_body)
exercise.user = user
saved = exercise.save
if saved
render :text => 'SUCCESS', :status => 200
else
logger.info(exercise.errors.full_messages)
render :text => 'Invalid exercise', :status => 400
end
rescue => error
if error.class == Hash
render :text => error.message, :status => error.status
else
raise error
render :text => '', :status => 500
end
end
end
def user_for_oauth2_request
authorizationHeader = request.headers['Authorization']
if authorizationHeader == nil
raise ({status: 401, message: 'No Authorization header'})
end
oauth2Token = authorizationHeader.split(' ')[1]
if oauth2Token == nil || oauth2Token.size == 0
raise ({status: 401, message: 'No token in Authorization header'})
end
user = user_by_code_harbor_token(oauth2Token)
if user == nil
raise ({status: 401, message: 'Unknown OAuth2 token'})
end
return user
end
private :user_for_oauth2_request
def user_by_code_harbor_token(oauth2Token)
link = CodeHarborLink.where(:oauth2token => oauth2Token)[0]
if link != nil
return link.user
end
end
private :user_by_code_harbor_token
def exercise_params
params[:exercise].permit(:description, :execution_environment_id, :file_id, :instructions, :public, :hide_file_tree, :team_id, :title, files_attributes: file_attributes).merge(user_id: current_user.id, user_type: current_user.class.name)
end

View File

@@ -111,6 +111,7 @@ class SubmissionsController < ApplicationController
if result[:status] == :container_running
socket = result[:socket]
command = result[:command]
socket.on :message do |event|
Rails.logger.info( Time.now.getutc.to_s + ": Docker sending: " + event.data)
@@ -139,6 +140,11 @@ class SubmissionsController < ApplicationController
Rails.logger.debug('Rescued parsing error, sent the received client data to docker:' + data)
end
end
# Send command after all listeners are attached.
# Newline required to flush
socket.send command + "\n"
Rails.logger.info('Sent command: ' + command.to_s)
else
kill_socket(tubesock)
end
@@ -176,7 +182,24 @@ class SubmissionsController < ApplicationController
for part in message.split("\n")
self.parse_message(part,output_stream,socket,false)
end
elsif(message.include? "<img")
#Rails.logger.info('img foung')
@buffering = true
@buffer = ""
@buffer += message
#Rails.logger.info('Starting to buffer')
elsif(@buffering && (message.include? "/>"))
@buffer += message
parsed = {'cmd'=>'write','stream'=>output_stream,'data'=>@buffer}
socket.send_data JSON.dump(parsed)
#socket.send_data @buffer
@buffering = false
#Rails.logger.info('Sent complete buffer')
elsif(@buffering)
@buffer += message
#Rails.logger.info('Appending to buffer')
else
#Rails.logger.info('else')
parsed = {'cmd'=>'write','stream'=>output_stream,'data'=>message}
socket.send_data JSON.dump(parsed)
Rails.logger.info('parse_message sent: ' + JSON.dump(parsed))

View File

@@ -0,0 +1,2 @@
module CodeHarborLinksHelper
end

View File

@@ -0,0 +1,13 @@
class CodeHarborLink < ActiveRecord::Base
validates :oauth2token, presence: true
validates :user_id, presence: true
belongs_to :internal_user, foreign_key: :user_id
alias_method :user, :internal_user
alias_method :user=, :internal_user=
def to_s
oauth2token
end
end

View File

@@ -1,3 +1,4 @@
require 'nokogiri'
require File.expand_path('../../../lib/active_model/validations/boolean_presence_validator', __FILE__)
class Exercise < ActiveRecord::Base
@@ -29,7 +30,11 @@ class Exercise < ActiveRecord::Base
def average_percentage
(average_score / maximum_score * 100).round if average_score and maximum_score != 0.0 else 0
if average_score and maximum_score != 0.0
(average_score / maximum_score * 100).round
else
0
end
end
def average_score
@@ -105,6 +110,54 @@ class Exercise < ActiveRecord::Base
exercise
end
def determine_file_role_from_proforma_file(task_node, file_node)
file_id = file_node.xpath('@id')
file_class = file_node.xpath('@class').first.value
comment = file_node.xpath('@comment').first.value
is_referenced_by_test = task_node.xpath("p:tests/p:test/p:filerefs/p:fileref[@id=#{file_id}]")
is_referenced_by_model_solution = task_node.xpath("p:model-solutions/p:model-solution/p:filerefs/p:fileref[@id=#{file_id}]")
if is_referenced_by_test && (file_class == 'internal')
return 'teacher_defined_test'
elsif is_referenced_by_model_solution && (file_class == 'internal')
return 'reference_implementation'
elsif (file_class == 'template') && (comment == 'main')
return 'main_file'
elsif (file_class == 'internal') && (comment == 'main')
end
return 'regular_file'
end
def from_proforma_xml(xml_string)
# how to extract the proforma functionality into a different module in rails?
xml = Nokogiri::XML(xml_string)
xml.collect_namespaces
task_node = xml.xpath('/root/p:task')
description = task_node.xpath('p:description/text()')[0].content
self.attributes = {
title: task_node.xpath('p:meta-data/p:title/text()')[0].content,
description: description,
instructions: description
}
task_node.xpath('p:files/p:file').all? { |file|
file_name_split = file.xpath('@filename').first.value.split('.')
file_class = file.xpath('@class').first.value
role = determine_file_role_from_proforma_file(task_node, file)
feedback_message_nodes = task_node.xpath("p:tests/p:test/p:test-configuration/c:feedback-message/text()")
files.build({
name: file_name_split.first,
content: file.xpath('text()').first.content,
read_only: false,
hidden: file_class == 'internal',
role: role,
feedback_message: (role == 'teacher_defined_test') ? feedback_message_nodes.first.content : nil,
file_type: FileType.where(
file_extension: ".#{file_name_split.second}"
).take
})
}
self.execution_environment_id = 1
end
def generate_token
self.token ||= SecureRandom.hex(4)
end
@@ -129,4 +182,5 @@ class Exercise < ActiveRecord::Base
end
end
private :valid_main_file?
end

View File

@@ -0,0 +1,3 @@
class CodeHarborLinkPolicy < AdminOnlyPolicy
end

View File

@@ -8,7 +8,7 @@
- if current_user.admin?
li = link_to(t('breadcrumbs.dashboard.show'), admin_dashboard_path)
li.divider
- models = [ExecutionEnvironment, Exercise, Consumer, ExternalUser, FileType, InternalUser, Submission, Team].sort_by { |model| model.model_name.human(count: 2) }
- models = [ExecutionEnvironment, Exercise, Consumer, CodeHarborLink, ExternalUser, FileType, InternalUser, Submission, Team].sort_by { |model| model.model_name.human(count: 2) }
- models.each do |model|
- if policy(model).index?
li = link_to(model.model_name.human(count: 2), send(:"#{model.model_name.collection}_path"))

View File

@@ -0,0 +1,6 @@
= form_for(@code_harbor_link) do |f|
= render('shared/form_errors', object: @code_harbor_link)
.form-group
= f.label(:oauth2token)
= f.text_field(:oauth2token, class: 'form-control', required: true)
.actions = render('shared/submit_button', f: f, object: @code_harbor_link)

View File

@@ -0,0 +1,3 @@
h1 = @code_harbor_link
= render('form')

View File

@@ -0,0 +1,18 @@
h1 = CodeHarborLink.model_name.human(count: 2)
.table-responsive
table.table
thead
tr
th = t('activerecord.attributes.code_harbor_link.oauth2token')
th colspan=3 = t('shared.actions')
tbody
- @code_harbor_links.each do |code_harbor_link|
tr
td = code_harbor_link.oauth2token
td = link_to(t('shared.show'), code_harbor_link)
td = link_to(t('shared.edit'), edit_code_harbor_link_path(code_harbor_link))
td = link_to(t('shared.destroy'), code_harbor_link, data: {confirm: t('shared.confirm_destroy')}, method: :delete)
= render('shared/pagination', collection: @code_harbor_links)
p = render('shared/new_button', model: CodeHarborLink)

View File

@@ -0,0 +1,3 @@
h1 = t('shared.new_model', model: CodeHarborLink.model_name.human)
= render('form')

View File

@@ -0,0 +1,7 @@
h1
= @code_harbor_link
= render('shared/edit_button', object: @code_harbor_link) if policy(@code_harbor_link).edit?
- %w[oauth2token].each do |attribute|
= row(label: "code_harbor_link.#{attribute}") do
= content_tag(:input, nil, class: 'form-control', readonly: true, value: @code_harbor_link.send(attribute))