Merge branch 'master' of github.com:openHPI/codeocean
This commit is contained in:
@@ -17,6 +17,7 @@ $(function() {
|
||||
var active_frame = undefined;
|
||||
var running = false;
|
||||
var qa_api = undefined;
|
||||
var output_mode_is_streaming = true;
|
||||
|
||||
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>'
|
||||
|
||||
@@ -615,6 +616,10 @@ $(function() {
|
||||
|
||||
var printOutput = function(output, colorize, index) {
|
||||
var element = findOrCreateOutputElement(index);
|
||||
// disable streaming if desired
|
||||
//if (output.stdout && output.stdout.length >= 20 && output.stdout.substr(0,20) == "##DISABLESTREAMING##"){
|
||||
// output_mode_is_streaming = false;
|
||||
//}
|
||||
if (!colorize) {
|
||||
var stream = _.sortBy([output.stderr || '', output.stdout || ''], function(stream) {
|
||||
return stream.length;
|
||||
@@ -623,7 +628,14 @@ $(function() {
|
||||
} else if (output.stderr) {
|
||||
element.addClass('text-warning').append(output.stderr);
|
||||
} else if (output.stdout) {
|
||||
//if (output_mode_is_streaming){
|
||||
element.addClass('text-success').append(output.stdout);
|
||||
//}else{
|
||||
// element.addClass('text-success');
|
||||
// element.data('content_buffer' , element.data('content_buffer') + output.stdout);
|
||||
//}
|
||||
//} else if (output.code && output.code == '200'){
|
||||
// element.append( element.data('content_buffer'));
|
||||
} else {
|
||||
element.addClass('text-muted').text($('#output').data('message-no-output'));
|
||||
}
|
||||
@@ -651,6 +663,11 @@ $(function() {
|
||||
})) {
|
||||
showTimeoutMessage();
|
||||
}
|
||||
if (_.some(response, function(result) {
|
||||
return result.status === 'container_depleted';
|
||||
})) {
|
||||
showContainerDepletedMessage();
|
||||
}
|
||||
if (qa_api) {
|
||||
// send test response to QA
|
||||
qa_api.executeCommand('syncOutput', [response]);
|
||||
@@ -815,6 +832,8 @@ $(function() {
|
||||
var showStatus = function(output) {
|
||||
if (output.status === 'timeout') {
|
||||
showTimeoutMessage();
|
||||
} else if (output.status === 'container_depleted') {
|
||||
showContainerDepletedMessage();
|
||||
} else if (output.stderr) {
|
||||
$.flash.danger({
|
||||
icon: ['fa', 'fa-bug'],
|
||||
@@ -828,6 +847,13 @@ $(function() {
|
||||
}
|
||||
};
|
||||
|
||||
var showContainerDepletedMessage = function() {
|
||||
$.flash.danger({
|
||||
icon: ['fa', 'fa-clock-o'],
|
||||
text: $('#editor').data('message-depleted')
|
||||
});
|
||||
};
|
||||
|
||||
var showTab = function(index) {
|
||||
$('a[data-toggle="tab"]').eq(index || 0).tab('show');
|
||||
};
|
||||
@@ -944,7 +970,7 @@ $(function() {
|
||||
}
|
||||
|
||||
var initializeCodePilot = function() {
|
||||
if ($('#questions-column').isPresent() && QaApi.isBrowserSupported()) {
|
||||
if ($('#questions-column').isPresent() && (typeof QaApi != 'undefined') && QaApi.isBrowserSupported()) {
|
||||
$('#editor-column').addClass('col-md-8').removeClass('col-md-10');
|
||||
$('#questions-column').addClass('col-md-3');
|
||||
|
||||
|
@@ -1,5 +1,10 @@
|
||||
h1 {
|
||||
margin-bottom: 1em;
|
||||
font-size: 25px;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.lead {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
i.fa {
|
||||
|
@@ -10,6 +10,7 @@ class ApplicationController < ActionController::Base
|
||||
rescue_from Pundit::NotAuthorizedError, with: :render_not_authorized
|
||||
|
||||
def current_user
|
||||
::NewRelic::Agent.add_custom_parameters({ external_user_id: session[:external_user_id], session_user_id: session[:user_id] })
|
||||
@current_user ||= ExternalUser.find_by(id: session[:external_user_id]) || login_from_session || login_from_other_sources
|
||||
end
|
||||
|
||||
|
@@ -16,27 +16,31 @@ class CommentsController < ApplicationController
|
||||
#if admin, show all comments.
|
||||
#check whether user is the author of the passed file_id, if so, show all comments. otherwise, only show comments of auther and own comments
|
||||
file = CodeOcean::File.find(params[:file_id])
|
||||
submission = Submission.find(file.context_id)
|
||||
#there might be no submission yet, so dont use find
|
||||
submission = Submission.find_by(id: file.context_id)
|
||||
if submission
|
||||
is_admin = false
|
||||
if current_user.respond_to? :external_id
|
||||
user_id = current_user.external_id
|
||||
else
|
||||
user_id = current_user.id
|
||||
is_admin = current_user.role == 'admin'
|
||||
end
|
||||
|
||||
is_admin = false
|
||||
if current_user.respond_to? :external_id
|
||||
user_id = current_user.external_id
|
||||
if(is_admin || user_id == submission.user_id)
|
||||
# fetch all comments for this file
|
||||
@comments = Comment.where(file_id: params[:file_id])
|
||||
else
|
||||
@comments = Comment.where(file_id: params[:file_id], user_id: user_id)
|
||||
end
|
||||
|
||||
#@comments = Comment.where(file_id: params[:file_id])
|
||||
|
||||
#add names to comments
|
||||
@comments.map{|comment| comment.username = Xikolo::UserClient.get(comment.user_id.to_s)[:display_name]}
|
||||
else
|
||||
user_id = current_user.id
|
||||
is_admin = current_user.role == 'admin'
|
||||
@comments = Comment.all.limit(0) #we need an empty relation here
|
||||
end
|
||||
|
||||
if(is_admin || user_id == submission.user_id)
|
||||
# fetch all comments for this file
|
||||
@comments = Comment.where(file_id: params[:file_id])
|
||||
else
|
||||
@comments = Comment.where(file_id: params[:file_id], user_id: user_id)
|
||||
end
|
||||
|
||||
#@comments = Comment.where(file_id: params[:file_id])
|
||||
|
||||
#add names to comments
|
||||
@comments.map{|comment| comment.username = Xikolo::UserClient.get(comment.user_id.to_s)[:display_name]}
|
||||
authorize!
|
||||
end
|
||||
|
||||
@@ -60,7 +64,7 @@ class CommentsController < ApplicationController
|
||||
# POST /comments
|
||||
# POST /comments.json
|
||||
def create
|
||||
@comment = Comment.new(comment_params.merge(user_type: 'InternalUser'))
|
||||
@comment = Comment.new(comment_params.merge(user_type: current_user.class.name))
|
||||
|
||||
respond_to do |format|
|
||||
if @comment.save
|
||||
|
@@ -95,6 +95,7 @@ module Lti
|
||||
private :return_to_consumer
|
||||
|
||||
def send_score(score)
|
||||
::NewRelic::Agent.add_custom_parameters({ score: score, session: session })
|
||||
fail(Error, "Score #{score} must be between 0 and #{MAXIMUM_SCORE}!") unless (0..MAXIMUM_SCORE).include?(score)
|
||||
provider = build_tool_provider(consumer: Consumer.find_by(id: session[:consumer_id]), parameters: session[:lti_parameters])
|
||||
if provider.nil?
|
||||
|
@@ -12,7 +12,11 @@ module SubmissionParameters
|
||||
private :reject_illegal_file_attributes!
|
||||
|
||||
def submission_params
|
||||
submission_params = params[:submission].permit(:cause, :exercise_id, files_attributes: file_attributes).merge(user_id: current_user.id, user_type: current_user.class.name)
|
||||
if current_user
|
||||
current_user_id = current_user.id
|
||||
current_user_class_name = current_user.class.name
|
||||
end
|
||||
submission_params = params[:submission].permit(:cause, :exercise_id, files_attributes: file_attributes).merge(user_id: current_user_id, user_type: current_user_class_name)
|
||||
reject_illegal_file_attributes!(submission_params)
|
||||
submission_params
|
||||
end
|
||||
|
@@ -20,12 +20,14 @@ module SubmissionScoring
|
||||
private :execute_test_file
|
||||
|
||||
def feedback_message(file, score)
|
||||
set_locale
|
||||
score == Assessor::MAXIMUM_SCORE ? I18n.t('exercises.implement.default_feedback') : file.feedback_message
|
||||
end
|
||||
|
||||
def score_submission(submission)
|
||||
outputs = collect_test_results(submission)
|
||||
score = outputs.map { |output| output[:score] * output[:weight] }.reduce(:+)
|
||||
score = outputs.map { |output|
|
||||
output[:score] * output[:weight] }.reduce(:+)
|
||||
submission.update(score: score)
|
||||
outputs
|
||||
end
|
||||
|
@@ -6,7 +6,7 @@ class ExercisesController < ApplicationController
|
||||
|
||||
before_action :handle_file_uploads, only: [:create, :update]
|
||||
before_action :set_execution_environments, only: [:create, :edit, :new, :update]
|
||||
before_action :set_exercise, only: MEMBER_ACTIONS + [:clone, :implement, :run, :statistics, :submit]
|
||||
before_action :set_exercise, only: MEMBER_ACTIONS + [:clone, :implement, :run, :statistics, :submit, :reload]
|
||||
before_action :set_file_types, only: [:create, :edit, :new, :update]
|
||||
before_action :set_teams, only: [:create, :edit, :new, :update]
|
||||
|
||||
@@ -138,6 +138,10 @@ class ExercisesController < ApplicationController
|
||||
def show
|
||||
end
|
||||
|
||||
#we might want to think about auth here
|
||||
def reload
|
||||
end
|
||||
|
||||
def statistics
|
||||
end
|
||||
|
||||
@@ -152,6 +156,7 @@ class ExercisesController < ApplicationController
|
||||
end
|
||||
|
||||
def transmit_lti_score
|
||||
::NewRelic::Agent.add_custom_parameters({ submission: @submission.id, normalized_score: @submission.normalized_score })
|
||||
response = send_score(@submission.normalized_score)
|
||||
if response[:status] == 'success'
|
||||
redirect_to_lti_return_path
|
||||
|
@@ -20,7 +20,7 @@ class SubmissionsController < ApplicationController
|
||||
def create
|
||||
@submission = Submission.new(submission_params)
|
||||
authorize!
|
||||
copy_comments
|
||||
#copy_comments
|
||||
create_and_respond(object: @submission)
|
||||
end
|
||||
|
||||
@@ -28,7 +28,7 @@ class SubmissionsController < ApplicationController
|
||||
# copy each annotation and set the target_file.id
|
||||
unless(params[:annotations_arr].nil?)
|
||||
params[:annotations_arr].each do | annotation |
|
||||
comment = Comment.new(:user_id => annotation[1][:user_id], :file_id => annotation[1][:file_id], :user_type => 'InternalUser', :row => annotation[1][:row], :column => annotation[1][:column], :text => annotation[1][:text])
|
||||
comment = Comment.new(:user_id => annotation[1][:user_id], :file_id => annotation[1][:file_id], :user_type => current_user.class.name, :row => annotation[1][:row], :column => annotation[1][:column], :text => annotation[1][:text])
|
||||
source_file = CodeOcean::File.find(annotation[1][:file_id])
|
||||
|
||||
#comment = Comment.new(annotation[1].permit(:user_id, :file_id, :user_type, :row, :column, :text, :created_at, :updated_at))
|
||||
@@ -55,7 +55,7 @@ class SubmissionsController < ApplicationController
|
||||
end
|
||||
|
||||
def index
|
||||
@search = Submission.search(params[:q])
|
||||
@search = Submission.last(100).search(params[:q])
|
||||
@submissions = @search.result.includes(:exercise, :user).paginate(page: params[:page])
|
||||
authorize!
|
||||
end
|
||||
@@ -70,22 +70,18 @@ class SubmissionsController < ApplicationController
|
||||
|
||||
def run
|
||||
with_server_sent_events do |server_sent_event|
|
||||
container_info_sent = false
|
||||
stderr = ''
|
||||
output = @docker_client.execute_run_command(@submission, params[:filename]) do |stream, chunk|
|
||||
unless container_info_sent
|
||||
server_sent_event.write({id: @docker_client.container.try(:id), port_bindings: @docker_client.container.try(:port_bindings)}, event: 'info')
|
||||
container_info_sent = true
|
||||
end
|
||||
server_sent_event.write({stream => chunk}, event: 'output')
|
||||
stderr += chunk if stream == :stderr
|
||||
end
|
||||
server_sent_event.write(output, event: 'status')
|
||||
if stderr.present?
|
||||
if hint = Whistleblower.new(execution_environment: @submission.execution_environment).generate_hint(stderr)
|
||||
output = @docker_client.execute_run_command(@submission, params[:filename])
|
||||
|
||||
server_sent_event.write({stdout: output[:stdout]}, event: 'output') if output[:stdout]
|
||||
server_sent_event.write({stderr: output[:stderr]}, event: 'output') if output[:stderr]
|
||||
|
||||
server_sent_event.write({status: output[:status]}, event: 'status')
|
||||
|
||||
unless output[:stderr].nil?
|
||||
if hint = Whistleblower.new(execution_environment: @submission.execution_environment).generate_hint(output[:stderr])
|
||||
server_sent_event.write(hint, event: 'hint')
|
||||
else
|
||||
store_error(stderr)
|
||||
store_error(output[:stderr])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -138,7 +134,7 @@ class SubmissionsController < ApplicationController
|
||||
end
|
||||
|
||||
def store_error(stderr)
|
||||
::Error.create(execution_environment_id: @submission.execution_environment.id, message: stderr)
|
||||
::Error.create(submission_id: @submission.id, execution_environment_id: @submission.execution_environment.id, message: stderr)
|
||||
end
|
||||
private :store_error
|
||||
|
||||
|
@@ -13,6 +13,8 @@ module ExerciseHelper
|
||||
|
||||
if enabled
|
||||
config.read[:code_pilot][:url]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@@ -23,7 +23,7 @@ class Exercise < ActiveRecord::Base
|
||||
validates :token, presence: true, uniqueness: true
|
||||
|
||||
def average_percentage
|
||||
(average_score / maximum_score * 100).round if average_score
|
||||
(average_score/ maximum_score * 100).round if average_score
|
||||
end
|
||||
|
||||
def average_score
|
||||
|
@@ -38,11 +38,16 @@ class Submission < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def normalized_score
|
||||
score / exercise.maximum_score if score
|
||||
::NewRelic::Agent.add_custom_parameters({ unnormalized_score: score })
|
||||
if !score.nil? && !exercise.maximum_score.nil? && (exercise.maximum_score > 0)
|
||||
score / exercise.maximum_score
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def percentage
|
||||
(normalized_score * 100).round if score
|
||||
(normalized_score * 100).round
|
||||
end
|
||||
|
||||
[:score, :stop].each do |action|
|
||||
|
@@ -12,12 +12,12 @@ class ExercisePolicy < AdminOrAuthorPolicy
|
||||
define_method(action) { admin? || author? || team_member? }
|
||||
end
|
||||
|
||||
[:implement?, :submit?].each do |action|
|
||||
[:implement?, :submit?, :reload?].each do |action|
|
||||
define_method(action) { everyone }
|
||||
end
|
||||
|
||||
def team_member?
|
||||
@record.team.try(:members, []).include?(@user)
|
||||
@record.team.try(:members, []).include?(@user) if @record.team
|
||||
end
|
||||
private :team_member?
|
||||
|
||||
|
@@ -1,10 +1,10 @@
|
||||
#editor.row data-exercise-id=exercise.id data-message-timeout=t('exercises.editor.timeout', permitted_execution_time: @exercise.execution_environment.permitted_execution_time) data-errors-url=execution_environment_errors_path(exercise.execution_environment) data-submissions-url=submissions_path data-user-id=@current_user.id
|
||||
#editor.row data-exercise-id=exercise.id data-message-depleted=t('exercises.editor.depleted') data-message-timeout=t('exercises.editor.timeout', permitted_execution_time: @exercise.execution_environment.permitted_execution_time) data-errors-url=execution_environment_errors_path(exercise.execution_environment) data-submissions-url=submissions_path data-user-id=@current_user.id
|
||||
.col-sm-3 = render('editor_file_tree', files: @files)
|
||||
#frames.col-sm-9
|
||||
- @files.each do |file|
|
||||
= render('editor_frame', exercise: exercise, file: file)
|
||||
#editor-buttons.btn-group
|
||||
= render('editor_button', data: {:'data-message-confirm' => t('exercises.editor.confirm_start_over'), :'data-url' => exercise_path(exercise)}, icon: 'fa fa-history', id: 'start-over', label: t('exercises.editor.start_over'))
|
||||
= render('editor_button', data: {:'data-message-confirm' => t('exercises.editor.confirm_start_over'), :'data-url' => reload_exercise_path(exercise)}, icon: 'fa fa-history', id: 'start-over', label: t('exercises.editor.start_over'))
|
||||
= render('editor_button', data: {:'data-message-success' => t('submissions.create.success'), :'data-placement' => 'top', :'data-tooltip' => true}, icon: 'fa fa-save', id: 'save', label: t('exercises.editor.save'), title: t('.tooltips.save'))
|
||||
.btn-group
|
||||
= render('editor_button', disabled: true, icon: 'fa fa-ban', id: 'dummy', label: t('exercises.editor.dummy'))
|
||||
|
@@ -71,8 +71,11 @@
|
||||
span.score
|
||||
.progress
|
||||
.progress-bar role='progressbar'
|
||||
|
||||
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'))
|
||||
- if session[:lti_parameters].try(:has_key?, 'lis_outcome_service_url')
|
||||
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}"
|
||||
|
@@ -9,7 +9,7 @@ html lang='en'
|
||||
= stylesheet_link_tag('//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css')
|
||||
= stylesheet_link_tag('application', media: 'all', 'data-turbolinks-track' => true)
|
||||
= javascript_include_tag('application', 'data-turbolinks-track' => true)
|
||||
= javascript_include_tag('//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.2/underscore-min.js')
|
||||
= javascript_include_tag('//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js')
|
||||
= javascript_include_tag('//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js')
|
||||
= yield(:head)
|
||||
= csrf_meta_tags
|
||||
|
@@ -10,6 +10,6 @@ h2 = t('shared.statistics')
|
||||
p == t('shared.out_of', maximum_value: @submission.exercise.maximum_score, value: @submission.score)
|
||||
p = progress_bar(@submission.percentage)
|
||||
= row(label: '.final_submissions', value: @submission.exercise.submissions.final.distinct.count(:user_id, :user_type) - 1)
|
||||
= row(label: '.average_score') do
|
||||
p == t('shared.out_of', maximum_value: @submission.exercise.maximum_score, value: @submission.exercise.average_score.round(2))
|
||||
p = progress_bar(@submission.exercise.average_percentage)
|
||||
/= row(label: '.average_score') do
|
||||
/ p == t('shared.out_of', maximum_value: @submission.exercise.maximum_score, value: @submission.exercise.average_score.round(2))
|
||||
/ p = progress_bar(@submission.exercise.average_percentage)
|
||||
|
Reference in New Issue
Block a user