Merge branch 'master' into disable_rfcs

# Conflicts:
#	app/assets/stylesheets/editor.css.scss
This commit is contained in:
Ralf Teusner
2018-03-07 17:30:14 +01:00
26 changed files with 138 additions and 78 deletions

View File

@ -12,14 +12,11 @@ addons:
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
@ -27,6 +24,7 @@ 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
@ -35,8 +33,4 @@ language: ruby
rvm:
- 2.3.6
script: bundle exec rspec --color --format documentation --require spec_helper --require rails_helper --tag ~docker && bundle exec codeclimate-test-reporter
# 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

@ -7,6 +7,7 @@ gem 'carrierwave'
gem 'coffee-rails'
gem 'concurrent-ruby'
gem 'concurrent-ruby-ext', platform: :ruby
gem 'activerecord-deprecated_finders', require: 'active_record/deprecated_finders'
gem 'docker-api', require: 'docker'
gem 'factory_bot_rails'
gem 'forgery'

View File

@ -31,6 +31,7 @@ GEM
activemodel (= 4.2.10)
activesupport (= 4.2.10)
arel (~> 6.0)
activerecord-deprecated_finders (1.0.4)
activerecord-jdbc-adapter (50.0)
activerecord (>= 2.2)
activerecord-jdbcpostgresql-adapter (50.0)
@ -388,6 +389,7 @@ PLATFORMS
ruby
DEPENDENCIES
activerecord-deprecated_finders
activerecord-jdbcpostgresql-adapter
autotest-rails
bcrypt

View File

@ -42,17 +42,14 @@ button i.fa-spin {
background-color: #008CBA;
margin-top: 0;
width: 100%;
display: flex;
button {
font-size: 80%;
}
.button-two-only, .btn-group-two-only {
width: 50%;
}
button, .btn-group {
width: 50%;
flex-grow: 1;
}
.btn-group {
@ -195,4 +192,4 @@ button i.fa-spin {
.enforce-bottom-margin {
margin-bottom: 5px !important;
}
}

View File

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

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.full_score_reached, 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

@ -75,6 +75,11 @@ class SubmissionsController < ApplicationController
zio.write(file.content)
end
# zip exercise description
zio.put_next_entry(t('activerecord.models.exercise.one') + '.txt')
zio.write(@submission.exercise.title + "\r\n======================\r\n")
zio.write(@submission.exercise.description)
# zip .co file
zio.put_next_entry(".co")
zio.write(File.read id_file)
@ -167,7 +172,7 @@ class SubmissionsController < ApplicationController
# if the command is 'client_kill', send it to docker otherwise.
begin
parsed = JSON.parse(data)
if parsed['cmd'] == 'client_kill'
if parsed.class == Hash && parsed['cmd'] == 'client_kill'
Rails.logger.debug("Client exited container.")
@docker_client.kill_container(result[:container])
else

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

@ -11,7 +11,13 @@ class RequestForComment < ActiveRecord::Base
scope :not_stale, -> { where("user_id%10 <2 OR user_id%10 >= 4").where(exercise.exercise_collections.none{|ec| ec.id = 3} } ########### todo
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.
@ -47,6 +53,14 @@ 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

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

@ -10,7 +10,7 @@ h2 = t('shared.statistics')
= row(label: '.score') do
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: '.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)

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

@ -3,3 +3,31 @@ class AddSubmissionToRequestForComments < ActiveRecord::Migration
add_reference :request_for_comments, :submission
end
end
=begin
We issued the following on the database to add the submission_ids for existing entries
UPDATE request_for_comments
SET submission_id = sub.submission_id_external
FROM
(SELECT s.id AS submission_id_external,
rfc.id AS rfc_id,
s.created_at AS submission_created_at,
rfc.created_at AS rfc_created_at
FROM submissions s,
request_for_comments rfc
WHERE s.user_id = rfc.user_id
AND s.exercise_id = rfc.exercise_id
AND rfc.created_at + interval '2 hours' > s.created_at
AND s.created_at =
(SELECT MAX(created_at)
FROM submissions
WHERE exercise_id = s.exercise_id
AND user_id = s.user_id
AND rfc.created_at + interval '2 hours' > created_at
GROUP BY s.exercise_id,
s.user_id)) as sub
WHERE id = sub.rfc_id
AND submission_id IS NULL;
=end

View File

@ -1,8 +1,8 @@
class AddReachedFullScoreToRequestForComment < ActiveRecord::Migration
def up
add_column :request_for_comments, :full_score_reached, :boolean, default: false
RequestForComment.all.each { |rfc|
if (rfc.submission.present? && rfc.submission.exercise.has_user_solved(rfc.user))
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

View File

@ -1,6 +1,10 @@
class FixTimestampsOnFeedback < ActiveRecord::Migration
def change
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

@ -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;
}

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

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