Merge remote-tracking branch 'origin/master' into exercise-anomaly-detection

# Conflicts:
#	Capfile
#	Gemfile.lock
#	db/schema.rb
This commit is contained in:
Maximilian Grundke
2018-02-28 16:48:14 +01:00
44 changed files with 330 additions and 226 deletions

View File

@ -5,20 +5,18 @@ services:
addons:
code_climate:
repo_token: 53a2c2608c848714e96f6a1fc0365dcfdfec051f7827d50cea965ea625f49734
postgresql: "9.5"
repo_token:
secure: "cZoMNjQKB/D7W4B7JDk9PXooy2WCDypu7R4C/Vi0DziZCU9HRwLbdt9aoH5hgHFa7Fe2rHFgflPAAP7h698ozvP0waFtPqLAj+PbEt27LbBDvW8JcvNkKXA0rj5wyTkzuc/0kD+kPB4oDXMak6gZlB9HCJDsa3kdXScQGTVuPdU="
postgresql: "9.6"
before_install:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
# Config to run docker tests - doesn't work so far
# - sudo apt-get update
# - sudo apt-get upgrade lxc-docker
# - echo 'DOCKER_OPTS="-H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock --iptables=false"' | sudo tee /etc/default/docker > /dev/null
# - export DOCKER_HOST=tcp://192.168.23.75:2375
# - sudo service docker restart
# - sleep 5
# - docker pull openhpi/docker_ruby
- echo 'DOCKER_OPTS="-H tcp://127.0.0.1:2376 -H unix:///var/run/docker.sock --iptables=false"' | sudo tee /etc/default/docker > /dev/null
- sudo service docker restart
- sleep 5
- docker pull openhpi/co_execenv_python
- docker pull openhpi/co_execenv_java
before_script:
- cp .rspec.travis .rspec
@ -26,18 +24,13 @@ before_script:
- cp config/code_ocean.yml.travis config/code_ocean.yml
- cp config/database.yml.travis config/database.yml
- cp config/secrets.yml.travis config/secrets.yml
- cp config/docker.yml.erb.travis config/docker.yml.erb
- psql --command='CREATE DATABASE travis_ci_test;' --username=postgres
- bundle exec rake db:schema:load RAILS_ENV=test
cache: bundler
language: ruby
rvm:
- 2.3.1
# - 2.1.5
# - 2.2.1
- 2.3.6
script: bundle exec rspec --color --format documentation --require spec_helper --require rails_helper --tag ~docker
# one of the solutions I've found
# - sudo docker run --rm=true -v `pwd`:/ansible-apache:rw weldpua2008/docker-ansible:${OS_TYPE}${OS_VERSION}_v${ANSIBLE_VERSION} /bin/bash -c "/ansible-apache/tests/test-in-docker-image.sh ${OS_TYPE} ${OS_VERSION} ${ANSIBLE_VERSION}"
script: bundle exec rspec --color --format documentation --require spec_helper --require rails_helper && bundle exec codeclimate-test-reporter

View File

@ -1,8 +1,13 @@
require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/scm/git'
require 'capistrano/puma'
require 'capistrano/puma/nginx'
require 'capistrano/rails'
require 'capistrano/rvm'
require 'capistrano/upload-config'
require "whenever/capistrano"
require 'whenever/capistrano'
install_plugin Capistrano::SCM::Git
install_plugin Capistrano::Puma
install_plugin Capistrano::Puma::Nginx

41
Gemfile
View File

@ -1,42 +1,43 @@
source 'https://rubygems.org'
gem 'activerecord-jdbcpostgresql-adapter', platform: :jruby
gem 'bcrypt', '~> 3.1.7'
gem 'bcrypt'
gem 'bootstrap-will_paginate'
gem 'carrierwave'
gem 'coffee-rails', '~> 4.0.0'
gem 'concurrent-ruby', '~> 1.0.1'
gem 'concurrent-ruby-ext', '~> 1.0.1', platform: :ruby
gem 'docker-api','~> 1.25.0', require: 'docker'
gem 'factory_bot_rails', '~> 4.8.2'
gem 'coffee-rails'
gem 'concurrent-ruby'
gem 'concurrent-ruby-ext', platform: :ruby
gem 'docker-api', require: 'docker'
gem 'factory_bot_rails'
gem 'forgery'
gem 'highline'
gem 'jbuilder', '~> 2.0'
gem 'jbuilder'
gem 'jquery-rails'
gem 'jquery-turbolinks'
gem 'ims-lti'
gem 'ims-lti', '1.1.10' # version 1.1.13 will crash, because @provider.valid_request?(request) on lti.rb line 89 will return false.
gem 'kramdown'
gem 'newrelic_rpm'
gem 'pg', platform: :ruby
gem 'pg', '< 1.0', platform: :ruby
gem 'pry-byebug'
gem 'puma', '~> 2.15.3'
gem 'puma'
gem 'pundit'
gem 'rails', '4.2.5'
gem 'rails-i18n', '~> 4.0.0'
gem 'rails', '4.2.10'
gem 'rails-i18n'
gem 'ransack'
gem 'rubytree'
gem 'sass-rails', '~> 4.0.3'
gem 'sdoc', '~> 0.4.0', group: :doc
gem 'sass-rails'
gem 'sdoc', group: :doc
gem 'slim'
gem 'bootstrap_pagedown'
gem 'pagedown-rails', '~> 1.1.4'
gem 'pagedown-rails'
gem 'sorcery'
gem 'thread_safe'
gem 'turbolinks'
gem 'uglifier', '>= 1.3.0'
gem 'will_paginate', '~> 3.0'
gem 'turbolinks', '< 5.0.0' # newer versions prevent loading ACE if the page containing is not accessed directly / refreshed
gem 'uglifier'
gem 'will_paginate'
gem 'tubesock'
gem 'faye-websocket'
gem 'eventmachine', '1.0.9.1' # explicitly added, this is used by faye-websocket, version 1.2.5 still has an error in eventmachine.rb:202: [BUG] Segmentation fault, which is not yet fixed and causes the whole ruby process to crash
gem 'nokogiri'
gem 'd3-rails'
gem 'rest-client'
@ -46,7 +47,7 @@ gem 'whenever', require: false
group :development, :staging do
gem 'better_errors', platform: :ruby
gem 'binding_of_caller', platform: :ruby
gem 'capistrano', '~> 3.3.0'
gem 'capistrano'
gem 'capistrano3-puma'
gem 'capistrano-rails'
gem 'capistrano-rvm'
@ -54,7 +55,7 @@ group :development, :staging do
gem 'rack-mini-profiler'
gem 'rubocop', require: false
gem 'rubocop-rspec'
gem 'web-console', '~> 2.0', platform: :ruby
gem 'web-console', platform: :ruby
end
group :development, :test, :staging do

View File

@ -2,43 +2,42 @@ GEM
remote: https://rubygems.org/
specs:
ZenTest (4.11.0)
actionmailer (4.2.5)
actionpack (= 4.2.5)
actionview (= 4.2.5)
activejob (= 4.2.5)
actionmailer (4.2.10)
actionpack (= 4.2.10)
actionview (= 4.2.10)
activejob (= 4.2.10)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.5)
actionview (= 4.2.5)
activesupport (= 4.2.5)
actionpack (4.2.10)
actionview (= 4.2.10)
activesupport (= 4.2.10)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.5)
activesupport (= 4.2.5)
actionview (4.2.10)
activesupport (= 4.2.10)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
activejob (4.2.5)
activesupport (= 4.2.5)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (4.2.10)
activesupport (= 4.2.10)
globalid (>= 0.3.0)
activemodel (4.2.5)
activesupport (= 4.2.5)
activemodel (4.2.10)
activesupport (= 4.2.10)
builder (~> 3.1)
activerecord (4.2.5)
activemodel (= 4.2.5)
activesupport (= 4.2.5)
activerecord (4.2.10)
activemodel (= 4.2.10)
activesupport (= 4.2.10)
arel (~> 6.0)
activerecord-jdbc-adapter (1.3.19)
activerecord (>= 2.2)
activerecord-jdbcpostgresql-adapter (1.3.19)
activerecord-jdbc-adapter (~> 1.3.19)
jdbc-postgres (>= 9.1)
activesupport (4.2.5)
activesupport (4.2.10)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
@ -59,7 +58,7 @@ GEM
will_paginate
bootstrap_pagedown (1.1.0)
rails (>= 3.2)
builder (3.2.2)
builder (3.2.3)
byebug (8.2.2)
capistrano (3.3.5)
capistrano-stats (~> 1.1.0)
@ -110,6 +109,7 @@ GEM
concurrent-ruby (1.0.2-java)
concurrent-ruby-ext (1.0.2)
concurrent-ruby (~> 1.0.2)
crass (1.0.3)
d3-rails (3.5.11)
railties (>= 3.1)
database_cleaner (1.5.1)
@ -139,13 +139,14 @@ GEM
ffi (1.9.10)
ffi (1.9.10-java)
forgery (0.6.0)
globalid (0.3.7)
activesupport (>= 4.1.0)
globalid (0.4.1)
activesupport (>= 4.2.0)
highline (1.7.8)
hike (1.2.3)
http-cookie (1.0.2)
domain_name (~> 0.5)
i18n (0.7.0)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
ims-lti (1.1.10)
builder
oauth (~> 0.4.5)
@ -159,19 +160,21 @@ GEM
jquery-turbolinks (2.1.0)
railties (>= 3.1.0)
turbolinks
json (1.8.3)
json (1.8.3-java)
json (1.8.6)
json (1.8.6-java)
jwt (1.5.1)
kramdown (1.9.0)
loofah (2.0.3)
loofah (2.2.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.6.4)
mime-types (>= 1.16, < 4)
mail (2.7.0)
mini_mime (>= 0.1.1)
method_source (0.8.2)
mime-types (2.99.3)
mini_portile2 (2.1.0)
minitest (5.10.1)
multi_json (1.12.1)
mini_mime (1.0.0)
mini_portile2 (2.3.0)
minitest (5.11.3)
multi_json (1.13.1)
multi_xml (0.5.5)
multipart-post (2.0.0)
net-scp (1.2.1)
@ -179,9 +182,9 @@ GEM
net-ssh (3.0.2)
netrc (0.10.3)
newrelic_rpm (3.14.3.313)
nokogiri (1.7.0.1)
mini_portile2 (~> 2.1.0)
nokogiri (1.7.0.1-java)
nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
nokogiri (1.8.2-java)
nyan-cat-formatter (0.11)
rspec (>= 2.99, >= 2.14.2, < 4)
oauth (0.4.7)
@ -203,6 +206,11 @@ GEM
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
pry (0.10.3-java)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
spoon (~> 0.0)
pry-byebug (3.3.0)
byebug (~> 8.0)
pry (~> 0.10)
@ -210,26 +218,26 @@ GEM
puma (2.15.3-java)
pundit (1.1.0)
activesupport (>= 3.0.0)
rack (1.6.5)
rack (1.6.9)
rack-mini-profiler (0.10.1)
rack (>= 1.2.0)
rack-test (0.6.3)
rack (>= 1.0)
rails (4.2.5)
actionmailer (= 4.2.5)
actionpack (= 4.2.5)
actionview (= 4.2.5)
activejob (= 4.2.5)
activemodel (= 4.2.5)
activerecord (= 4.2.5)
activesupport (= 4.2.5)
rails (4.2.10)
actionmailer (= 4.2.10)
actionpack (= 4.2.10)
actionview (= 4.2.10)
activejob (= 4.2.10)
activemodel (= 4.2.10)
activerecord (= 4.2.10)
activesupport (= 4.2.10)
bundler (>= 1.3.0, < 2.0)
railties (= 4.2.5)
railties (= 4.2.10)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
rails-dom-testing (1.0.8)
activesupport (>= 4.2.0.beta, < 5.0)
rails-dom-testing (1.0.9)
activesupport (>= 4.2.0, < 5.0)
nokogiri (~> 1.6)
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
@ -237,13 +245,13 @@ GEM
rails-i18n (4.0.8)
i18n (~> 0.7)
railties (~> 4.0)
railties (4.2.5)
actionpack (= 4.2.5)
activesupport (= 4.2.5)
railties (4.2.10)
actionpack (= 4.2.10)
activesupport (= 4.2.10)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.2.1)
rake (12.0.0)
rake (12.3.0)
ransack (1.7.0)
actionpack (>= 3.0)
activerecord (>= 3.0)
@ -319,6 +327,8 @@ GEM
bcrypt (~> 3.1)
oauth (~> 0.4, >= 0.4.4)
oauth2 (>= 0.8.0)
spoon (0.0.6)
ffi
spring (1.6.3)
sprockets (2.12.4)
hike (~> 1.2)
@ -334,16 +344,16 @@ GEM
net-ssh (>= 2.8.0)
structured_warnings (0.2.0)
temple (0.7.6)
thor (0.19.4)
thread_safe (0.3.5)
thread_safe (0.3.5-java)
thor (0.20.0)
thread_safe (0.3.6)
thread_safe (0.3.6-java)
tilt (1.4.1)
tubesock (0.2.5)
rack (>= 1.5.0)
websocket (>= 1.1.0)
turbolinks (2.5.3)
coffee-rails
tzinfo (1.2.2)
tzinfo (1.2.5)
thread_safe (~> 0.1)
uglifier (2.7.2)
execjs (>= 0.3.0)
@ -377,13 +387,13 @@ PLATFORMS
DEPENDENCIES
activerecord-jdbcpostgresql-adapter
autotest-rails
bcrypt (~> 3.1.7)
bcrypt
better_errors
binding_of_caller
bootstrap-will_paginate
bootstrap_pagedown
byebug
capistrano (~> 3.3.0)
capistrano
capistrano-rails
capistrano-rvm
capistrano-upload-config
@ -391,32 +401,33 @@ DEPENDENCIES
capybara
carrierwave
codeclimate-test-reporter
coffee-rails (~> 4.0.0)
concurrent-ruby (~> 1.0.1)
concurrent-ruby-ext (~> 1.0.1)
coffee-rails
concurrent-ruby
concurrent-ruby-ext
d3-rails
database_cleaner
docker-api (~> 1.25.0)
factory_bot_rails (~> 4.8.2)
docker-api
eventmachine (= 1.0.9.1)
factory_bot_rails
faye-websocket
forgery
highline
ims-lti
jbuilder (~> 2.0)
ims-lti (= 1.1.10)
jbuilder
jquery-rails
jquery-turbolinks
kramdown
newrelic_rpm
nokogiri
nyan-cat-formatter
pagedown-rails (~> 1.1.4)
pg
pagedown-rails
pg (< 1.0)
pry-byebug
puma (~> 2.15.3)
puma
pundit
rack-mini-profiler
rails (= 4.2.5)
rails-i18n (~> 4.0.0)
rails (= 4.2.10)
rails-i18n
rake
ransack
rest-client
@ -426,8 +437,8 @@ DEPENDENCIES
rubocop-rspec
rubytree
rubyzip
sass-rails (~> 4.0.3)
sdoc (~> 0.4.0)
sass-rails
sdoc
selenium-webdriver
simplecov
slim
@ -435,8 +446,11 @@ DEPENDENCIES
spring
thread_safe
tubesock
turbolinks
uglifier (>= 1.3.0)
web-console (~> 2.0)
turbolinks (< 5.0.0)
uglifier
web-console
whenever
will_paginate (~> 3.0)
will_paginate
BUNDLED WITH
1.16.1

View File

@ -26,4 +26,4 @@
//= require markdown.converter
//= require markdown.sanitizer
//= require markdown.editor
//= require ../../../vendor/assets/javascripts/ace/ext-language_tools
//= require ace/ext-language_tools

View File

@ -3,7 +3,7 @@
}
.chosen-container {
width: 100% !important;
width: 250px !important;
}
.code-field {

View File

@ -9,7 +9,7 @@ module SubmissionScoring
assessment = assessor.assess(output)
passed = ((assessment[:passed] == assessment[:count]) and (assessment[:score] > 0))
testrun_output = passed ? nil : 'message: ' + output[:message].to_s + "\n stdout: " + output[:stdout].to_s + "\n stderr: " + output[:stderr].to_s
if !testrun_output.blank?
unless testrun_output.blank?
submission.exercise.execution_environment.error_templates.each do |template|
pattern = Regexp.new(template.signature).freeze
if pattern.match(testrun_output)
@ -47,6 +47,14 @@ module SubmissionScoring
end
end
submission.update(score: score)
if submission.normalized_score == 1.0
Thread.new do
RequestForComment.where(exercise_id: submission.exercise_id, user_id: submission.user_id, user_type: submission.user_type).each{ |rfc|
rfc.full_score_reached = true
rfc.save
}
end
end
outputs
end
end

View File

@ -416,6 +416,9 @@ class ExercisesController < ApplicationController
flash[:notice] = I18n.t('exercises.submit.full_score_redirect_to_rfc')
flash.keep(:notice)
# increase counter 'times_featured' in rfc
rfc.increment!(:times_featured)
respond_to do |format|
format.html {redirect_to(rfc)}
format.json {render(json: {redirect: url_for(rfc)})}

View File

@ -14,46 +14,40 @@ class RequestForCommentsController < ApplicationController
def index
@search = RequestForComment
.last_per_user(2)
.joins('join "submissions" s on s.id = request_for_comments.submission_id
left outer join "files" f on f.context_id = s.id
left outer join "comments" on comments.file_id = f.id')
.group('request_for_comments.id, request_for_comments.user_id, request_for_comments.exercise_id,
request_for_comments.file_id, request_for_comments.question, request_for_comments.created_at,
request_for_comments.updated_at, request_for_comments.user_type, request_for_comments.solved,
request_for_comments.submission_id, request_for_comments.row_number') # ugly, but rails wants it this way
.select('request_for_comments.*, max(comments.updated_at) as last_comment')
.with_last_activity
.search(params[:q])
@request_for_comments = @search.result.order('created_at DESC').paginate(page: params[:page], total_entries: @search.result.length)
@request_for_comments = @search.result
.order('created_at DESC')
.paginate(page: params[:page], total_entries: @search.result.length)
authorize!
end
# GET /my_request_for_comments
def get_my_comment_requests
@search = RequestForComment
.with_last_activity
.where(user_id: current_user.id)
.joins('join "submissions" s on s.id = request_for_comments.submission_id
left outer join "files" f on f.context_id = s.id
left outer join "comments" on comments.file_id = f.id')
.group('request_for_comments.id')
.select('request_for_comments.*, max(comments.updated_at) as last_comment')
.search(params[:q])
@request_for_comments = @search.result.order('created_at DESC').paginate(page: params[:page])
@request_for_comments = @search.result
.order('created_at DESC')
.paginate(page: params[:page])
render 'index'
end
# GET /my_rfc_activity
def get_rfcs_with_my_comments
@search = RequestForComment
.with_last_activity
.joins(:comments) # we don't need to outer join here, because we know the user has commented on these
.where(comments: {user_id: current_user.id})
.joins('join "submissions" s on s.id = request_for_comments.submission_id
left outer join "files" f on f.context_id = s.id
left outer join "comments" as c on c.file_id = f.id')
.group('request_for_comments.id')
.select('request_for_comments.*, max(c.updated_at) as last_comment')
.search(params[:q])
@request_for_comments = @search.result.order('last_comment DESC').paginate(page: params[:page])
@request_for_comments = @search.result
.order('last_comment DESC')
.paginate(page: params[:page])
render 'index'
end
# GET /request_for_comments/1/mark_as_solved
def mark_as_solved
authorize!
@request_for_comment.solved = true
@ -66,6 +60,7 @@ class RequestForCommentsController < ApplicationController
end
end
# POST /request_for_comments/1/set_thank_you_note
def set_thank_you_note
authorize!
@request_for_comment.thank_you_note = params[:note]
@ -82,10 +77,6 @@ class RequestForCommentsController < ApplicationController
end
end
def submit
end
# GET /request_for_comments/1
# GET /request_for_comments/1.json
def show
@ -146,10 +137,6 @@ class RequestForCommentsController < ApplicationController
authorize!
end
def comment_params
params.permit(:exercise_id, :feedback_text).merge(user_id: current_user.id, user_type: current_user.class.name)
end
private
# Use callbacks to share common setup or constraints between actions.
def set_request_for_comment
@ -162,4 +149,8 @@ class RequestForCommentsController < ApplicationController
params.require(:request_for_comment).permit(:exercise_id, :file_id, :question, :requested_at, :solved, :submission_id).merge(user_id: current_user.id, user_type: current_user.class.name)
end
def comment_params
params.permit(:exercise_id, :feedback_text).merge(user_id: current_user.id, user_type: current_user.class.name)
end
end

View File

@ -202,21 +202,11 @@ class SubmissionsController < ApplicationController
tubesock.close
end
def extract_errors
if !@message_buffer.blank?
@submission.exercise.execution_environment.error_templates.each do |template|
pattern = Regexp.new(template.signature).freeze
if pattern.match(@message_buffer)
StructuredError.create_from_template(template, @message_buffer)
end
end
end
end
def handle_message(message, tubesock, container)
@run_output ||= ""
@raw_output ||= ''
@run_output ||= ''
# Handle special commands first
if (/^#exit/.match(message))
if /^#exit/.match(message)
# Just call exit_container on the docker_client.
# Do not call kill_socket for the websocket to the client here.
# @docker_client.exit_container closes the socket to the container,
@ -228,17 +218,17 @@ class SubmissionsController < ApplicationController
# Filter out information about run_command, test_command, user or working directory
run_command = @submission.execution_environment.run_command % command_substitutions(params[:filename])
test_command = @submission.execution_environment.test_command % command_substitutions(params[:filename])
if !(/root|workspace|#{run_command}|#{test_command}/.match(message))
unless /root|workspace|#{run_command}|#{test_command}/.match(message)
parse_message(message, 'stdout', tubesock)
end
end
end
def parse_message(message, output_stream, socket, recursive = true)
parsed = '';
parsed = ''
begin
parsed = JSON.parse(message)
if(parsed.class == Hash && parsed.key?('cmd'))
if parsed.class == Hash and parsed.key?('cmd')
socket.send_data message
Rails.logger.info('parse_message sent: ' + message)
else
@ -248,24 +238,24 @@ class SubmissionsController < ApplicationController
end
rescue JSON::ParserError => e
# Check wether the message contains multiple lines, if true try to parse each line
if ((recursive == true) && (message.include? "\n"))
if recursive and message.include? "\n"
for part in message.split("\n")
self.parse_message(part,output_stream,socket,false)
end
elsif(message.include? "<img")
elsif message.include? '<img'
#Rails.logger.info('img foung')
@buffering = true
@buffer = ""
@buffer = ''
@buffer += message
#Rails.logger.info('Starting to buffer')
elsif(@buffering && (message.include? "/>"))
elsif @buffering and 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)
elsif @buffering
@buffer += message
#Rails.logger.info('Appending to buffer')
else
@ -275,18 +265,30 @@ class SubmissionsController < ApplicationController
Rails.logger.info('parse_message sent: ' + JSON.dump(parsed))
end
ensure
@raw_output += parsed['data'] if parsed.class == Hash and parsed.key? 'data'
# save the data that was send to the run_output if there is enough space left. this will be persisted as a testrun with cause "run"
@run_output += JSON.dump(parsed) if @run_output.size <= max_run_output_buffer_size
end
end
def save_run_output
if !@run_output.blank?
unless @run_output.blank?
@run_output = @run_output[(0..max_run_output_buffer_size-1)] # trim the string to max_message_buffer_size chars
Testrun.create(file: @file, cause: 'run', submission: @submission, output: @run_output)
end
end
def extract_errors
unless @raw_output.blank?
@submission.exercise.execution_environment.error_templates.each do |template|
pattern = Regexp.new(template.signature).freeze
if pattern.match(@raw_output)
StructuredError.create_from_template(template, @raw_output, @submission)
end
end
end
end
def score
hijack do |tubesock|
Thread.new { EventMachine.run } unless EventMachine.reactor_running? && EventMachine.reactor_thread.alive?

View File

@ -47,7 +47,7 @@ class ExecutionEnvironment < ActiveRecord::Base
private :validate_docker_image?
def working_docker_image?
DockerClient.pull(docker_image) unless DockerClient.image_tags.include?(docker_image)
DockerClient.pull(docker_image) unless DockerClient.find_image_by_tag(docker_image).blank?
output = DockerClient.new(execution_environment: self).execute_arbitrary_command(VALIDATION_COMMAND)
errors.add(:docker_image, "error: #{output[:stderr]}") if output[:stderr].present?
rescue DockerClient::Error => error

View File

@ -10,7 +10,13 @@ class RequestForComment < ActiveRecord::Base
scope :unsolved, -> { where(solved: [false, nil]) }
def self.last_per_user(n = 5)
from("(#{row_number_user_sql}) as request_for_comments").where("row_number <= ?", n)
from("(#{row_number_user_sql}) as request_for_comments")
.where("row_number <= ?", n)
.group('request_for_comments.id, request_for_comments.user_id, request_for_comments.exercise_id,
request_for_comments.file_id, request_for_comments.question, request_for_comments.created_at,
request_for_comments.updated_at, request_for_comments.user_type, request_for_comments.solved,
request_for_comments.full_score_reached, request_for_comments.submission_id, request_for_comments.row_number')
# ugly, but necessary
end
# not used right now, finds the last submission for the respective user and exercise.
@ -46,12 +52,20 @@ class RequestForComment < ActiveRecord::Base
commenters.uniq {|user| user.id}
end
def self.with_last_activity
self.joins('join "submissions" s on s.id = request_for_comments.submission_id
left outer join "files" f on f.context_id = s.id
left outer join "comments" c on c.file_id = f.id')
.group('request_for_comments.id')
.select('request_for_comments.*, max(c.updated_at) as last_comment')
end
def to_s
"RFC-" + self.id.to_s
end
private
def self.row_number_user_sql
select("id, user_id, exercise_id, file_id, question, created_at, updated_at, user_type, solved, submission_id, row_number() OVER (PARTITION BY user_id ORDER BY created_at DESC) as row_number").to_sql
select("id, user_id, exercise_id, file_id, question, created_at, updated_at, user_type, solved, full_score_reached, submission_id, row_number() OVER (PARTITION BY user_id ORDER BY created_at DESC) as row_number").to_sql
end
end

View File

@ -1,9 +1,10 @@
class StructuredError < ActiveRecord::Base
belongs_to :error_template
belongs_to :submission
belongs_to :file, class_name: 'CodeOcean::File'
def self.create_from_template(template, message_buffer)
instance = self.create(error_template: template)
def self.create_from_template(template, message_buffer, submission)
instance = self.create(error_template: template, submission: submission)
template.error_template_attributes.each do |attribute|
StructuredErrorAttribute.create_from_template(attribute, instance, message_buffer)
end

View File

@ -3,15 +3,13 @@ class StructuredErrorAttribute < ActiveRecord::Base
belongs_to :error_template_attribute
def self.create_from_template(attribute, structured_error, message_buffer)
match = false
value = nil
result = message_buffer.match(attribute.regex)
if result != nil
match = true
if result.captures.size > 0
value = result.captures[0]
end
end
self.create(structured_error: structured_error, error_template_attribute: attribute, value: value, match: match)
self.create(structured_error: structured_error, error_template_attribute: attribute, value: value, match: result != nil)
end
end

View File

@ -8,6 +8,7 @@ class Submission < ActiveRecord::Base
belongs_to :exercise
has_many :testruns
has_many :structured_errors
has_many :comments, through: :files
delegate :execution_environment, to: :exercise

View File

@ -1,5 +1,6 @@
#flash.fixed_error_messages data-message-failure=t('shared.message_failure')
- %w[alert danger info notice success warning].each do |severity|
div.alert.flash class="alert-#{{'alert' => 'warning', 'notice' => 'success'}.fetch(severity, severity)}"
p id="flash-#{severity}" = flash[severity]
span.fa.fa-times
#flash-container
#flash.container.fixed_error_messages data-message-failure=t('shared.message_failure')
- %w[alert danger info notice success warning].each do |severity|
div.alert.flash class="alert-#{{'alert' => 'warning', 'notice' => 'success'}.fetch(severity, severity)}"
p id="flash-#{severity}" = flash[severity]
span.fa.fa-times

View File

@ -32,8 +32,8 @@ html lang='en'
li = link_to(t('shared.help.link'), '#modal-help', data: {toggle: 'modal'})
= render('session')
.container data-controller=controller_name
= render('breadcrumbs')
= render('flash')
= render('breadcrumbs')
- if (controller_name == "exercises" && action_name == "implement")
.container-fluid
= yield

View File

@ -27,6 +27,9 @@ h1 = RequestForComment.model_name.human(count: 2)
- if request_for_comment.solved?
td
span class="fa fa-check" aria-hidden="true"
- elsif request_for_comment.full_score_reached
td
span class="fa fa-check" style="color:darkgrey" aria-hidden="true"
- else
td = ''
td = link_to(request_for_comment.exercise.title, request_for_comment)

View File

@ -17,7 +17,7 @@ namespace :deploy do
after :compile_assets, :copy_vendor_assets do
on roles(fetch(:assets_roles)) do
within release_path do
execute :cp, 'vendor/assets/images/*', 'public/assets/'
execute :cp, '-r', 'vendor/assets/images/', 'public/assets/'
execute :cp, '-r', 'vendor/assets/javascripts/ace', 'public/assets/'
end
end

View File

@ -34,7 +34,21 @@ production:
ws_host: ws://localhost:4243 #url to connect rails server to docker host
ws_client_protocol: wss:// #set the websocket protocol to be used by the client to connect to the rails server (ws on development, wss on production)
staging:
<<: *default
host: unix:///var/run/docker.sock
pool:
active: true
refill:
async: false
batch_size: 8
interval: 15
timeout: 60
workspace_root: <%= Rails.root.join('tmp', 'files', Rails.env) %>
ws_host: ws://localhost:4243 #url to connect rails server to docker host
ws_client_protocol: 'wss:' #set the websocket protocol to be used by the client to connect to the rails server (ws on development, wss on production)
test:
<<: *default
host: tcp://192.168.59.104:2376
host: tcp://127.0.0.1:2376
workspace_root: <%= File.join('/', 'shared', Rails.env) %>

View File

@ -32,7 +32,7 @@ production:
timeout: 60
workspace_root: <%= Rails.root.join('tmp', 'files', Rails.env) %>
ws_host: ws://localhost:4243 #url to connect rails server to docker host
ws_client_protocol: 'wss:' #set the websocket protocol to be used by the client to connect to the rails server (ws on development, wss on production)
ws_client_protocol: wss:// #set the websocket protocol to be used by the client to connect to the rails server (ws on development, wss on production)
staging:
<<: *default
@ -50,5 +50,5 @@ staging:
test:
<<: *default
host: tcp://192.168.59.104:2376
host: tcp://127.0.0.1:2376
workspace_root: <%= File.join('/', 'shared', Rails.env) %>

View File

@ -35,6 +35,9 @@ Rails.application.configure do
# yet still be able to expire them through the digest params.
config.assets.digest = true
# Suppress logger output for asset requests.
config.assets.quiet = true
# Adds additional error checking when serving assets at runtime.
# Checks for improperly declared sprockets dependencies.
# Raises helpful error messages.

View File

@ -0,0 +1,5 @@
class AddSubmissionToStructuredErrors < ActiveRecord::Migration
def change
add_reference :structured_errors, :submission, index: true
end
end

View File

@ -0,0 +1,15 @@
class AddReachedFullScoreToRequestForComment < ActiveRecord::Migration
def up
add_column :request_for_comments, :full_score_reached, :boolean, default: false
RequestForComment.find_each { |rfc|
if rfc.submission.present? and rfc.submission.exercise.has_user_solved(rfc.user)
rfc.full_score_reached = true
rfc.save
end
}
end
def down
remove_column :request_for_comments, :full_score_reached
end
end

View File

@ -0,0 +1,5 @@
class AddTimesFeaturedToRequestForComments < ActiveRecord::Migration
def change
add_column :request_for_comments, :times_featured, :integer, default: 0
end
end

View File

@ -0,0 +1,10 @@
class FixTimestampsOnFeedback < ActiveRecord::Migration
def up
change_column_default(:user_exercise_feedbacks, :created_at, nil)
change_column_default(:user_exercise_feedbacks, :updated_at, nil)
end
def down
end
end

View File

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180226131340) do
ActiveRecord::Schema.define(version: 20180222145909) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -291,16 +291,18 @@ ActiveRecord::Schema.define(version: 20180226131340) do
end
create_table "request_for_comments", force: :cascade do |t|
t.integer "user_id", null: false
t.integer "exercise_id", null: false
t.integer "file_id", null: false
t.integer "user_id", null: false
t.integer "exercise_id", null: false
t.integer "file_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "user_type", limit: 255
t.string "user_type", limit: 255
t.text "question"
t.boolean "solved", default: false
t.boolean "solved", default: false
t.integer "submission_id"
t.text "thank_you_note"
t.boolean "full_score_reached", default: false
t.integer "times_featured", default: 0
end
create_table "searches", force: :cascade do |t|
@ -326,8 +328,11 @@ ActiveRecord::Schema.define(version: 20180226131340) do
t.integer "file_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "submission_id"
end
add_index "structured_errors", ["submission_id"], name: "index_structured_errors_on_submission_id", using: :btree
create_table "submissions", force: :cascade do |t|
t.integer "exercise_id"
t.float "score"
@ -368,15 +373,15 @@ ActiveRecord::Schema.define(version: 20180226131340) do
end
create_table "user_exercise_feedbacks", force: :cascade do |t|
t.integer "exercise_id", null: false
t.integer "user_id", null: false
t.string "user_type", null: false
t.integer "exercise_id", null: false
t.integer "user_id", null: false
t.string "user_type", null: false
t.integer "difficulty"
t.integer "working_time_seconds"
t.string "feedback_text"
t.integer "user_estimated_worktime"
t.datetime "created_at", default: '2017-11-20 18:20:25', null: false
t.datetime "updated_at", default: '2017-11-20 18:20:25', null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "user_exercise_interventions", force: :cascade do |t|

View File

@ -1,3 +1,8 @@
#flash-container {
position: relative;
top: -21px;
}
.flash {
display: none;
@ -10,14 +15,10 @@
}
.fixed_error_messages {
position: fixed;
position: absolute;
z-index: 1000;
top: 20px;
left: 0;
padding: inherit;
width: 100%;
padding-left: 10%;
padding-right: 10%;
padding-top: 0;
}

View File

@ -13,7 +13,7 @@ add-apt-repository ppa:chris-lea/node.js
apt-get update
# code_ocean
apt-get install -y postgresql-client postgresql-9.5 postgresql-server-dev-9.5 vagrant
apt-get install -y postgresql-client postgresql-10 postgresql-server-dev-10 vagrant
# Docker
if [ ! -f /etc/default/docker ]
@ -49,18 +49,18 @@ apt-get install -y libgdbm-dev libncurses5-dev automake libtool bison libffi-dev
gpg --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3
curl -L https://get.rvm.io | bash -s stable
source /etc/profile.d/rvm.sh
rvm install 2.3.1
rvm use 2.3.1 --default
rvm install 2.3.6
rvm use 2.3.6 --default
ruby -v
# rails
apt-get -y install nodejs
gem install rails -v 4.2.1
gem install rails -v 4.2.10
# drop postgres access control
if ! grep -q code_ocean /etc/postgresql/9.5/main/pg_hba.conf
if ! grep -q code_ocean /etc/postgresql/10/main/pg_hba.conf
then
cat >/etc/postgresql/9.3/main/pg_hba.conf <<EOF
cat >/etc/postgresql/10/main/pg_hba.conf <<EOF
# code_ocean: drop access control
local all all trust
host all all 127.0.0.1/32 trust
@ -110,7 +110,7 @@ passenger_root /usr/lib/ruby/vendor_ruby/phusion_passenger/locations.ini;
server {
server_name codeocean.local;
root /vagrant/public;
passenger_ruby /usr/local/rvm/gems/ruby-2.1.5/wrappers/ruby;
passenger_ruby /usr/local/rvm/gems/ruby-2.3.6/wrappers/ruby;
passenger_sticky_sessions on;
passenger_enabled on;
passenger_app_env development;

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

View File

@ -98,7 +98,7 @@ describe Lti do
let(:consumer) { FactoryBot.create(:consumer) }
let(:score) { 0.5 }
let(:submission) { FactoryBot.create(:submission) }
let!(:lti_parameter) { FactoryBot.create(:lti_parameter)}
let!(:lti_parameter) { FactoryBot.create(:lti_parameter, consumers_id: consumer.id, external_users_id: submission.user_id, exercises_id: submission.exercise_id)}
context 'with an invalid score' do
it 'raises an exception' do
@ -114,7 +114,6 @@ describe Lti do
context 'when grading is not supported' do
it 'returns a corresponding status' do
skip('ralf: this does not work, since send_score pulls data from the database, which then returns an empty array. On this is called .first, which returns nil and lets the test fail. Before Toms changes, this was taken from the session, which could be mocked')
expect_any_instance_of(IMS::LTI::ToolProvider).to receive(:outcome_service?).and_return(false)
expect(controller.send(:send_score, submission.exercise_id, score, submission.user_id)[:status]).to eq('unsupported')
end
@ -133,12 +132,10 @@ describe Lti do
end
it 'sends the score' do
skip('ralf: this does not work, since send_score pulls data from the database, which then returns an empty array. On this is called .first, which returns nil and lets the test fail. Before Toms changes, this was taken from the session, which could be mocked')
controller.send(:send_score, submission.exercise_id, score, submission.user_id)
end
it 'returns code, message, and status' do
skip('ralf: this does not work, since send_score pulls data from the database, which then returns an empty array. On this is called .first, which returns nil and lets the test fail. Before Toms changes, this was taken from the session, which could be mocked')
result = controller.send(:send_score, submission.exercise_id, score, submission.user_id)
expect(result[:code]).to eq(response.response_code)
expect(result[:message]).to eq(response.body)

View File

@ -2,6 +2,7 @@ FactoryBot.define do
factory :user_exercise_feedback, class: UserExerciseFeedback do
created_by_external_user
feedback_text 'Most suitable exercise ever'
association :exercise, factory: :math
end
end

View File

@ -2,12 +2,22 @@ require 'rails_helper'
require 'seeds_helper'
describe DockerClient, docker: true do
WORKSPACE_PATH = '/tmp/code_ocean_test'
let(:command) { 'whoami' }
let(:docker_client) { described_class.new(execution_environment: FactoryBot.build(:java), user: FactoryBot.build(:admin)) }
let(:execution_environment) { FactoryBot.build(:java) }
let(:image) { double }
let(:submission) { FactoryBot.create(:submission) }
let(:workspace_path) { '/tmp' }
let(:workspace_path) { WORKSPACE_PATH }
before(:all) do
FileUtils.mkdir_p(WORKSPACE_PATH)
end
after(:all) do
FileUtils.rm_rf(WORKSPACE_PATH)
end
describe '.check_availability!' do
context 'when a socket error occurs' do
@ -129,7 +139,7 @@ describe DockerClient, docker: true do
after(:each) { docker_client.send(:create_workspace_files, container, submission) }
it 'creates submission-specific directories' do
expect(Dir).to receive(:mkdir).at_least(:once)
expect(Dir).to receive(:mkdir).at_least(:once).and_call_original
end
it 'copies binary files' do

View File

@ -16,13 +16,8 @@
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
unless RUBY_PLATFORM == 'java'
if ENV['CODECLIMATE_REPO_TOKEN']
require 'codeclimate-test-reporter'
CodeClimate::TestReporter.start
else
require 'simplecov'
SimpleCov.start('rails')
end
end
RSpec.configure do |config|

View File

@ -1,6 +1,5 @@
FactoryBot.define do
factory :error_template_attribute do
error_template nil
key "MyString"
regex "MyString"
end

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long