merged with current master

This commit is contained in:
Thomas Hille
2017-11-16 17:16:22 +01:00
144 changed files with 1406 additions and 349 deletions

View File

@@ -0,0 +1,18 @@
$(function() {
if ($.isController('error_templates')) {
$('#add-attribute').find('button').on('click', function () {
$.ajax(location + '/attribute.json', {
method: 'POST',
data: {
_method: 'PUT',
dataType: 'json',
error_template_attribute_id: $('#add-attribute').find('select').val()
}
}).success(function () {
location.reload();
}).error(function (error) {
$.flash.danger({text: error.statusText});
});
});
}
});

View File

@@ -0,0 +1,9 @@
#add-attribute {
display: flex;
max-width: 400px;
margin-top: 30px;
button {
margin-left: 10px;
}
}

View File

@@ -93,4 +93,16 @@ a.file-heading {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
}
.feedback {
.text {
margin-bottom: 10px;
}
.difficulty {
font-weight: bold;
}
.worktime {
font-weight: bold;
}
}

View File

@@ -8,7 +8,15 @@ module SubmissionScoring
output = execute_test_file(file, submission)
assessment = assessor.assess(output)
passed = ((assessment[:passed] == assessment[:count]) and (assessment[:score] > 0))
testrun_output = passed ? nil : output[:stderr]
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?
submission.exercise.execution_environment.error_templates.each do |template|
pattern = Regexp.new(template.signature).freeze
if pattern.match(testrun_output)
StructuredError.create_from_template(template, testrun_output)
end
end
end
Testrun.new(submission: submission, cause: 'assess', file: file, passed: passed, output: testrun_output).save
output.merge!(assessment)
output.merge!(filename: file.name_with_extension, message: feedback_message(file, output[:score]), weight: file.weight)

View File

@@ -0,0 +1,86 @@
class ErrorTemplateAttributesController < ApplicationController
before_action :set_error_template_attribute, only: [:show, :edit, :update, :destroy]
def authorize!
authorize(@error_template_attributes || @error_template_attribute)
end
private :authorize!
# GET /error_template_attributes
# GET /error_template_attributes.json
def index
@error_template_attributes = ErrorTemplateAttribute.all.order('important DESC', :key, :id).paginate(page: params[:page])
authorize!
end
# GET /error_template_attributes/1
# GET /error_template_attributes/1.json
def show
authorize!
end
# GET /error_template_attributes/new
def new
@error_template_attribute = ErrorTemplateAttribute.new
authorize!
end
# GET /error_template_attributes/1/edit
def edit
authorize!
end
# POST /error_template_attributes
# POST /error_template_attributes.json
def create
@error_template_attribute = ErrorTemplateAttribute.new(error_template_attribute_params)
authorize!
respond_to do |format|
if @error_template_attribute.save
format.html { redirect_to @error_template_attribute, notice: 'Error template attribute was successfully created.' }
format.json { render :show, status: :created, location: @error_template_attribute }
else
format.html { render :new }
format.json { render json: @error_template_attribute.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /error_template_attributes/1
# PATCH/PUT /error_template_attributes/1.json
def update
authorize!
respond_to do |format|
if @error_template_attribute.update(error_template_attribute_params)
format.html { redirect_to @error_template_attribute, notice: 'Error template attribute was successfully updated.' }
format.json { render :show, status: :ok, location: @error_template_attribute }
else
format.html { render :edit }
format.json { render json: @error_template_attribute.errors, status: :unprocessable_entity }
end
end
end
# DELETE /error_template_attributes/1
# DELETE /error_template_attributes/1.json
def destroy
authorize!
@error_template_attribute.destroy
respond_to do |format|
format.html { redirect_to error_template_attributes_url, notice: 'Error template attribute was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_error_template_attribute
@error_template_attribute = ErrorTemplateAttribute.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def error_template_attribute_params
params[:error_template_attribute].permit(:key, :description, :regex, :important)
end
end

View File

@@ -0,0 +1,104 @@
class ErrorTemplatesController < ApplicationController
before_action :set_error_template, only: [:show, :edit, :update, :destroy, :add_attribute, :remove_attribute]
def authorize!
authorize(@error_templates || @error_template)
end
private :authorize!
# GET /error_templates
# GET /error_templates.json
def index
@error_templates = ErrorTemplate.all.order(:execution_environment_id, :name).paginate(page: params[:page])
authorize!
end
# GET /error_templates/1
# GET /error_templates/1.json
def show
authorize!
end
# GET /error_templates/new
def new
@error_template = ErrorTemplate.new
authorize!
end
# GET /error_templates/1/edit
def edit
authorize!
end
# POST /error_templates
# POST /error_templates.json
def create
@error_template = ErrorTemplate.new(error_template_params)
authorize!
respond_to do |format|
if @error_template.save
format.html { redirect_to @error_template, notice: 'Error template was successfully created.' }
format.json { render :show, status: :created, location: @error_template }
else
format.html { render :new }
format.json { render json: @error_template.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /error_templates/1
# PATCH/PUT /error_templates/1.json
def update
authorize!
respond_to do |format|
if @error_template.update(error_template_params)
format.html { redirect_to @error_template, notice: 'Error template was successfully updated.' }
format.json { render :show, status: :ok, location: @error_template }
else
format.html { render :edit }
format.json { render json: @error_template.errors, status: :unprocessable_entity }
end
end
end
# DELETE /error_templates/1
# DELETE /error_templates/1.json
def destroy
authorize!
@error_template.destroy
respond_to do |format|
format.html { redirect_to error_templates_url, notice: 'Error template was successfully destroyed.' }
format.json { head :no_content }
end
end
def add_attribute
authorize!
@error_template.error_template_attributes << ErrorTemplateAttribute.find(params['error_template_attribute_id'])
respond_to do |format|
format.html { redirect_to @error_template }
format.json { head :no_content }
end
end
def remove_attribute
authorize!
@error_template.error_template_attributes.delete(ErrorTemplateAttribute.find(params['error_template_attribute_id']))
respond_to do |format|
format.html { redirect_to @error_template }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_error_template
@error_template = ErrorTemplate.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def error_template_params
params[:error_template].permit(:name, :execution_environment_id, :signature, :description, :hint)
end
end

View File

@@ -0,0 +1,51 @@
class ExerciseCollectionsController < ApplicationController
include CommonBehavior
before_action :set_exercise_collection, only: [:show, :edit, :update, :destroy]
def index
@exercise_collections = ExerciseCollection.all.paginate(:page => params[:page])
authorize!
end
def show
end
def new
@exercise_collection = ExerciseCollection.new
authorize!
end
def create
@exercise_collection = ExerciseCollection.new(exercise_collection_params)
authorize!
create_and_respond(object: @exercise_collection)
end
def destroy
authorize!
destroy_and_respond(object: @exercise_collection)
end
def edit
end
def update
update_and_respond(object: @exercise_collection, params: exercise_collection_params)
end
private
def set_exercise_collection
@exercise_collection = ExerciseCollection.find(params[:id])
authorize!
end
def authorize!
authorize(@exercise_collection || @exercise_collections)
end
def exercise_collection_params
params[:exercise_collection].permit(:name, :exercise_ids => [])
end
end

View File

@@ -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, :working_times, :intervention, :search, :run, :statistics, :submit, :reload]
before_action :set_exercise, only: MEMBER_ACTIONS + [:clone, :implement, :working_times, :intervention, :search, :run, :statistics, :submit, :reload, :feedback]
before_action :set_external_user, only: [:statistics]
before_action :set_file_types, only: [:create, :edit, :new, :update]
before_action :set_course_token, only: [:implement]
@@ -28,7 +28,6 @@ class ExercisesController < ApplicationController
1
end
def java_course_token
"702cbd2a-c84c-4b37-923a-692d7d1532d0"
end
@@ -94,6 +93,11 @@ class ExercisesController < ApplicationController
collect_set_and_unset_exercise_tags
end
def feedback
authorize!
@feedbacks = @exercise.user_exercise_feedbacks.paginate(page: params[:page])
end
def import_proforma_xml
begin
user = user_for_oauth2_request()
@@ -147,8 +151,7 @@ class ExercisesController < ApplicationController
private :user_by_code_harbor_token
def exercise_params
params[:exercise][:expected_worktime_seconds] = params[:exercise][:expected_worktime_minutes].to_i * 60
params[:exercise].permit(:description, :execution_environment_id, :file_id, :instructions, :public, :hide_file_tree, :allow_file_creation, :allow_auto_completion, :title, :expected_difficulty, :expected_worktime_seconds, files_attributes: file_attributes, :tag_ids => []).merge(user_id: current_user.id, user_type: current_user.class.name)
params[:exercise].permit(:description, :execution_environment_id, :file_id, :instructions, :public, :hide_file_tree, :allow_file_creation, :allow_auto_completion, :title, :expected_difficulty, files_attributes: file_attributes, :tag_ids => []).merge(user_id: current_user.id, user_type: current_user.class.name)
end
private :exercise_params
@@ -388,10 +391,13 @@ class ExercisesController < ApplicationController
# otherwise an internal user could be shown a false rfc here, since current_user.id is polymorphic, but only makes sense for external users when used with rfcs.)
# redirect 10 percent pseudorandomly to the feedback page
if current_user.respond_to? :external_id
if ((current_user.id + @submission.exercise.created_at.to_i) % 10 == 1)
if @submission.redirect_to_feedback?
redirect_to_user_feedback
return
elsif rfc = RequestForComment.unsolved.where(exercise_id: @submission.exercise, user_id: current_user.id).first
end
rfc = @submission.own_unsolved_rfc
if rfc
# set a message that informs the user that his own RFC should be closed.
flash[:notice] = I18n.t('exercises.submit.full_score_redirect_to_own_rfc')
flash.keep(:notice)
@@ -401,24 +407,30 @@ class ExercisesController < ApplicationController
format.json { render(json: {redirect: url_for(rfc)}) }
end
return
end
# else: show open rfc for same exercise if available
elsif rfc = RequestForComment.unsolved.where(exercise_id: @submission.exercise).where.not(question: nil).order("RANDOM()").find { | rfc_element |(rfc_element.comments_count < 5) }
rfc = @submission.unsolved_rfc
unless rfc.nil?
# set a message that informs the user that his score was perfect and help in RFC is greatly appreciated.
flash[:notice] = I18n.t('exercises.submit.full_score_redirect_to_rfc')
flash.keep(:notice)
respond_to do |format|
format.html { redirect_to(rfc) }
format.json { render(json: {redirect: url_for(rfc)}) }
format.html {redirect_to(rfc)}
format.json {render(json: {redirect: url_for(rfc)})}
end
return
end
end
else
# redirect to feedback page if score is less than 100 percent
redirect_to_user_feedback
return
if @exercise.needs_more_feedback?
redirect_to_user_feedback
else
redirect_to_lti_return_path
end
return
end
redirect_to_lti_return_path
end

View File

@@ -6,7 +6,7 @@ class SubmissionsController < ApplicationController
include SubmissionScoring
include Tubesock::Hijack
before_action :set_submission, only: [:download, :download_file, :render_file, :run, :score, :show, :statistics, :stop, :test]
before_action :set_submission, only: [:download, :download_file, :render_file, :run, :score, :extract_errors, :show, :statistics, :stop, :test]
before_action :set_docker_client, only: [:run, :test]
before_action :set_files, only: [:download, :download_file, :render_file, :show]
before_action :set_file, only: [:download_file, :render_file]
@@ -191,6 +191,9 @@ class SubmissionsController < ApplicationController
end
def kill_socket(tubesock)
# search for errors and save them as StructuredError (for scoring runs see submission_scoring.rb)
extract_errors
# save the output of this "run" as a "testrun" (scoring runs are saved in submission_scoring.rb)
save_run_output
@@ -199,6 +202,17 @@ 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 ||= ""
# Handle special commands first

View File

@@ -2,6 +2,7 @@ class UserExerciseFeedbacksController < ApplicationController
include CommonBehavior
before_action :set_user_exercise_feedback, only: [:edit, :update]
before_action :set_user_exercise_feedback_by_id, only: [:show, :destroy]
def comment_presets
[[0,t('user_exercise_feedback.difficulty_easy')],
@@ -19,10 +20,15 @@ class UserExerciseFeedbacksController < ApplicationController
[4,t('user_exercise_feedback.estimated_time_more_30')]]
end
def authorize!
authorize(@uef)
def index
@search = UserExerciseFeedback.all.search params[:q]
@uefs = @search.result.includes(:execution_environment).order(:id).paginate(page: params[:page])
authorize!
end
def show
authorize!
end
private :authorize!
def create
@exercise = Exercise.find(uef_params[:exercise_id])
@@ -49,7 +55,8 @@ class UserExerciseFeedbacksController < ApplicationController
end
def destroy
destroy_and_respond(object: @tag)
authorize!
destroy_and_respond(object: @uef)
end
def edit
@@ -58,11 +65,6 @@ class UserExerciseFeedbacksController < ApplicationController
authorize!
end
def uef_params
params[:user_exercise_feedback].permit(:feedback_text, :difficulty, :exercise_id, :user_estimated_worktime).merge(user_id: current_user.id, user_type: current_user.class.name)
end
private :uef_params
def new
@texts = comment_presets.to_a
@times = time_presets.to_a
@@ -89,6 +91,12 @@ class UserExerciseFeedbacksController < ApplicationController
end
end
private
def authorize!
authorize(@uef || @uefs)
end
def to_s
name
end
@@ -98,6 +106,14 @@ class UserExerciseFeedbacksController < ApplicationController
@uef = UserExerciseFeedback.find_by(exercise_id: params[:user_exercise_feedback][:exercise_id], user: current_user)
end
def set_user_exercise_feedback_by_id
@uef = UserExerciseFeedback.find(params[:id])
end
def uef_params
params[:user_exercise_feedback].permit(:feedback_text, :difficulty, :exercise_id, :user_estimated_worktime).merge(user_id: current_user.id, user_type: current_user.class.name)
end
def validate_inputs(uef_params)
begin
if uef_params[:difficulty].to_i < 0 || uef_params[:difficulty].to_i >= comment_presets.size
@@ -112,4 +128,4 @@ class UserExerciseFeedbacksController < ApplicationController
end
end
end
end

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
class ErrorTemplate < ActiveRecord::Base
belongs_to :execution_environment
has_and_belongs_to_many :error_template_attributes
def to_s
"#{id} [#{name}]"
end
end

View File

@@ -0,0 +1,7 @@
class ErrorTemplateAttribute < ActiveRecord::Base
has_and_belongs_to_many :error_template
def to_s
"#{id} [#{key}]"
end
end

View File

@@ -11,6 +11,7 @@ class ExecutionEnvironment < ActiveRecord::Base
has_many :exercises
belongs_to :file_type
has_many :hints
has_many :error_templates
scope :with_exercises, -> { where('id IN (SELECT execution_environment_id FROM exercises)') }

View File

@@ -20,6 +20,7 @@ class Exercise < ActiveRecord::Base
has_many :exercise_tags
has_many :tags, through: :exercise_tags
accepts_nested_attributes_for :exercise_tags
has_many :user_exercise_feedbacks
has_many :external_users, source: :user, source_type: ExternalUser, through: :submissions
has_many :internal_users, source: :user, source_type: InternalUser, through: :submissions
@@ -36,6 +37,8 @@ class Exercise < ActiveRecord::Base
@working_time_statistics = nil
MAX_EXERCISE_FEEDBACKS = 20
def average_percentage
if average_score and maximum_score != 0.0 and submissions.exists?(cause: 'submit')
@@ -361,4 +364,8 @@ class Exercise < ActiveRecord::Base
end
private :valid_main_file?
def needs_more_feedback
user_exercise_feedbacks.size <= MAX_EXERCISE_FEEDBACKS
end
end

View File

@@ -2,4 +2,8 @@ class ExerciseCollection < ActiveRecord::Base
has_and_belongs_to_many :exercises
end
def to_s
"#{I18n.t('activerecord.models.exercise_collection.one')}: #{name} (#{id})"
end
end

View File

@@ -36,8 +36,8 @@ class ProxyExercise < ActiveRecord::Base
Rails.logger.debug("retrieved assigned exercise for user #{user.id}: Exercise #{assigned_user_proxy_exercise.exercise}" )
assigned_user_proxy_exercise.exercise
else
Rails.logger.debug("find new matching exercise for user #{user.id}" )
matching_exercise =
Rails.logger.debug("find new matching exercise for user #{user.id}" )
begin
find_matching_exercise(user)
rescue => e #fallback
@@ -72,7 +72,7 @@ class ProxyExercise < ActiveRecord::Base
# find exercises
potential_recommended_exercises = []
exercises.where("expected_difficulty > 1").each do |ex|
exercises.where("expected_difficulty >= 1").each do |ex|
## find exercises which have only tags the user has already seen
if (ex.tags - tags_user_has_seen).empty?
potential_recommended_exercises << ex
@@ -85,8 +85,7 @@ class ProxyExercise < ActiveRecord::Base
@reason[:reason] = "easiest exercise in pool. empty potential exercises"
select_easiest_exercise(exercises)
else
recommended_exercise = select_best_matching_exercise(user, exercises_user_has_accessed, potential_recommended_exercises)
recommended_exercise
select_best_matching_exercise(user, exercises_user_has_accessed, potential_recommended_exercises)
end
end
@@ -238,4 +237,4 @@ class ProxyExercise < ActiveRecord::Base
exercises.order(:expected_difficulty).first
end
end
end

View File

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

View File

@@ -0,0 +1,17 @@
class StructuredErrorAttribute < ActiveRecord::Base
belongs_to :structured_error
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)
end
end

View File

@@ -18,6 +18,8 @@ class Submission < ActiveRecord::Base
validates :cause, inclusion: {in: CAUSES}
validates :exercise_id, presence: true
MAX_COMMENTS_ON_RECOMMENDED_RFC = 5
def build_files_hash(files, attribute)
files.map(&attribute.to_proc).zip(files).to_h
end
@@ -53,4 +55,16 @@ class Submission < ActiveRecord::Base
def to_s
Submission.model_name.human
end
def redirect_to_feedback?
((user_id + exercise.created_at.to_i) % 10 == 1) && exercise.needs_more_feedback
end
def own_unsolved_rfc
RequestForComment.unsolved.where(exercise_id: exercise, user_id: user_id).first
end
def unsolved_rfc
RequestForComment.unsolved.where(exercise_id: exercise).where.not(question: nil).order("RANDOM()").find { | rfc_element |(rfc_element.comments_count < MAX_COMMENTS_ON_RECOMMENDED_RFC) }
end
end

View File

@@ -2,10 +2,11 @@ class UserExerciseFeedback < ActiveRecord::Base
include Creation
belongs_to :exercise
has_one :execution_environment, through: :exercise
validates :user_id, uniqueness: { scope: [:exercise_id, :user_type] }
def to_s
"User Exercise Feedback"
end
end
end

View File

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

View File

@@ -0,0 +1,9 @@
class ErrorTemplatePolicy < AdminOnlyPolicy
def add_attribute?
admin?
end
def remove_attribute?
admin?
end
end

View File

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

View File

@@ -12,7 +12,7 @@ class ExercisePolicy < AdminOrAuthorPolicy
@user.internal_user?
end
[:clone?, :destroy?, :edit?, :statistics?, :update?].each do |action|
[:clone?, :destroy?, :edit?, :statistics?, :update?, :feedback?].each do |action|
define_method(action) { admin? || author?}
end

View File

@@ -1,8 +1,4 @@
class UserExerciseFeedbackPolicy < ApplicationPolicy
def author?
@user == @record.author
end
private :author?
class UserExerciseFeedbackPolicy < AdminOrAuthorPolicy
def create?
everyone
@@ -12,8 +8,4 @@ class UserExerciseFeedbackPolicy < ApplicationPolicy
everyone
end
[:show? ,:destroy?, :edit?, :update?].each do |action|
define_method(action) { admin? || author?}
end
end

View File

@@ -8,7 +8,8 @@
- if current_user.admin?
li = link_to(t('breadcrumbs.dashboard.show'), admin_dashboard_path)
li.divider
- models = [ExecutionEnvironment, Exercise, ProxyExercise, Tag, Consumer, CodeHarborLink, ExternalUser, FileType, FileTemplate, InternalUser].sort_by { |model| model.model_name.human(count: 2) }
- models = [ExecutionEnvironment, Exercise, ExerciseCollection, ProxyExercise, Tag, Consumer, CodeHarborLink, UserExerciseFeedback,
ErrorTemplate, ErrorTemplateAttribute, ExternalUser, FileType, FileTemplate, InternalUser].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,16 @@
= form_for(@error_template_attribute) do |f|
= render('shared/form_errors', object: @error_template_attribute)
.form-group
= f.label(:key)
= f.text_field(:key, class: 'form-control', required: true)
.form-group
= f.label(:description)
= f.text_field(:description, class: 'form-control')
.form-group
= f.label(:regex)
= f.text_field(:regex, class: 'form-control', required: true)
.help-block == t('error_templates.hints.signature')
.form-group
= f.check_box(:important)
= t('activerecord.attributes.error_template_attribute.important')
.actions = render('shared/submit_button', f: f, object: @error_template_attribute)

View File

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

View File

@@ -0,0 +1,28 @@
h1 = ErrorTemplateAttribute.model_name.human(count: 2)
.table-responsive
table.sortable.table
thead
tr
th
th = t('activerecord.attributes.error_template_attribute.key')
th = t('activerecord.attributes.error_template_attribute.description')
th = t('activerecord.attributes.error_template_attribute.regex')
th colspan=5 = t('shared.actions')
tbody
- @error_template_attributes.each do |error_template_attribute|
tr
td
- if error_template_attribute.important
span class="fa fa-star" aria-hidden="true"
- else
span class="fa fa-star-o" aria-hidden="true"
td = error_template_attribute.key
td = error_template_attribute.description
td = error_template_attribute.regex
td = link_to(t('shared.show'), error_template_attribute)
td = link_to(t('shared.edit'), edit_error_template_attribute_path(error_template_attribute))
td = link_to(t('shared.destroy'), error_template_attribute, data: {confirm: t('shared.confirm_destroy')}, method: :delete)
= render('shared/pagination', collection: @error_template_attributes)
p = render('shared/new_button', model: ErrorTemplateAttribute)

View File

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

View File

@@ -0,0 +1,8 @@
h1
= @error_template_attribute
= render('shared/edit_button', object: @error_template_attribute)
- [:key, :description, :regex, :important].each do |attribute|
= row(label: "error_template_attribute.#{attribute}", value: @error_template_attribute.send(attribute))
// todo: used by

View File

@@ -0,0 +1,19 @@
= form_for(@error_template) do |f|
= render('shared/form_errors', object: @error_template)
.form-group
= f.label(:name)
= f.text_field(:name, class: 'form-control', required: true)
.form-group
= f.label(:execution_environment_id)
= f.collection_select(:execution_environment_id, ExecutionEnvironment.all.order(:name), :id, :name, {include_blank: false}, class: 'form-control')
.form-group
= f.label(:signature)
= f.text_field(:signature, class: 'form-control')
.help-block == t('error_templates.hints.signature')
.form-group
= f.label(:description)
= f.text_field(:description, class: 'form-control')
.form-group
= f.label(:hint)
= f.text_field(:hint, class: 'form-control')
.actions = render('shared/submit_button', f: f, object: @error_template)

View File

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

View File

@@ -0,0 +1,22 @@
h1 = ErrorTemplate.model_name.human(count: 2)
.table-responsive
table.sortable.table
thead
tr
th = t('activerecord.attributes.error_template.name')
th = t('activerecord.attributes.error_template.description')
th = t('activerecord.attributes.exercise.execution_environment')
th colspan=3 = t('shared.actions')
tbody
- @error_templates.each do |error_template|
tr
td = error_template.name
td = error_template.description
td = link_to(error_template.execution_environment)
td = link_to(t('shared.show'), error_template)
td = link_to(t('shared.edit'), edit_error_template_path(error_template))
td = link_to(t('shared.destroy'), error_template, data: {confirm: t('shared.confirm_destroy')}, method: :delete)
= render('shared/pagination', collection: @error_templates)
p = render('shared/new_button', model: ErrorTemplate)

View File

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

View File

@@ -0,0 +1,40 @@
h1
= @error_template
= render('shared/edit_button', object: @error_template)
= row(label: 'error_template.name', value: @error_template.name)
= row(label: 'exercise.execution_environment', value: link_to(@error_template.execution_environment))
- [:signature, :description, :hint].each do |attribute|
= row(label: "error_template.#{attribute}", value: @error_template.send(attribute))
h3
= t 'error_templates.attributes'
.table-responsive
table.sortable.table
thead
tr
th
th = t('activerecord.attributes.error_template_attribute.key')
th = t('activerecord.attributes.error_template_attribute.description')
th = t('activerecord.attributes.error_template_attribute.regex')
th colspan=3 = t('shared.actions')
tbody
- @error_template.error_template_attributes.order('important DESC', :key).each do |attribute|
tr
td
- if attribute.important
span class="fa fa-star" aria-hidden="true"
- else
span class="fa fa-star-o" aria-hidden="true"
td = attribute.key
td = attribute.description
td = attribute.regex
td = link_to(t('shared.show'), attribute)
td = link_to(t('shared.destroy'), attribute_error_template_url(:error_template_attribute_id => attribute.id), :method => :delete)
#add-attribute
= collection_select({}, :error_template_attribute_id,
ErrorTemplateAttribute.where.not(id: @error_template.error_template_attributes.select(:id).to_a).order('important DESC', :key),
:id, :key, {include_blank: false}, class: '')
button.btn.btn-default = t('error_templates.add_attribute')

View File

@@ -0,0 +1,11 @@
- exercises = Exercise.order(:title)
= form_for(@exercise_collection, data: {exercises: exercises}, multipart: true) do |f|
= render('shared/form_errors', object: @exercise_collection)
.form-group
= f.label(:name)
= f.text_field(:name, class: 'form-control', required: true)
.form-group
= f.label(:exercises)
= f.collection_select(:exercise_ids, exercises, :id, :title, {}, {class: 'form-control', multiple: true})
.actions = render('shared/submit_button', f: f, object: @exercise_collection)

View File

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

View File

@@ -0,0 +1,24 @@
h1 = ExerciseCollection.model_name.human(count: 2)
.table-responsive
table.table
thead
tr
th = t('activerecord.attributes.exercise_collections.id')
th = t('activerecord.attributes.exercise_collections.name')
th = t('activerecord.attributes.exercise_collections.updated_at')
th = t('activerecord.attributes.exercise_collections.exercises')
th colspan=3 = t('shared.actions')
tbody
- @exercise_collections.each do |collection|
tr
td = collection.id
td = link_to(collection.name, collection)
td = collection.updated_at
td = collection.exercises.size
td = link_to(t('shared.show'), collection)
td = link_to(t('shared.edit'), edit_exercise_collection_path(collection))
td = link_to(t('shared.destroy'), collection, data: {confirm: t('shared.confirm_destroy')}, method: :delete)
= render('shared/pagination', collection: @exercise_collections)
p = render('shared/new_button', model: ExerciseCollection)

View File

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

View File

@@ -0,0 +1,11 @@
h1
= @exercise_collection
= render('shared/edit_button', object: @exercise_collection)
= row(label: 'exercise_collections.name', value: @exercise_collection.name)
= row(label: 'exercise_collections.updated_at', value: @exercise_collection.updated_at)
h4 = t('activerecord.attributes.exercise_collections.exercises')
ul.list-unstyled
- @exercise_collection.exercises.sort_by{|c| c.title}.each do |exercise|
li = link_to(exercise, exercise)

View File

@@ -3,7 +3,7 @@
- consumer_id = @current_user.respond_to?(:external_id) ? @current_user.consumer_id : '' #'tests' #(@current_user.uuid.present? ? @current_user.uuid : '')
- show_break_interventions = @show_break_interventions || "false"
- show_rfc_interventions = @show_rfc_interventions || "false"
#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 data-user-external-id=external_user_external_id data-working-times-url=working_times_exercise_path data-intervention-save-url=intervention_exercise_path data-rfc-interventions=show_rfc_interventions data-break-interventions=show_break_interventions data-course_token=@course_token data-search-save-url=search_exercise_path
#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 data-user-external-id=external_user_external_id data-working-times-url=working_times_exercise_path(@exercise) data-intervention-save-url=intervention_exercise_path(@exercise) data-rfc-interventions=show_rfc_interventions data-break-interventions=show_break_interventions data-course_token=@course_token data-search-save-url=search_exercise_path(@exercise)
div id="sidebar" class=(@exercise.hide_file_tree ? 'sidebar-col-collapsed' : 'sidebar-col') = render('editor_file_tree', exercise: @exercise, files: @files)
div id='output_sidebar' class='output-col-collapsed' = render('exercises/editor_output', external_user_id: external_user_id, consumer_id: consumer_id )
div id='frames' class='editor-col'
@@ -24,4 +24,4 @@
= render('shared/modal', id: 'comment-modal', title: t('exercises.implement.comment.request'), template: 'exercises/_request_comment_dialogcontent')
= render('shared/modal', id: 'break-intervention-modal', title: t('exercises.implement.break_intervention.title'), template: 'interventions/_break_intervention_modal')
= render('shared/modal', id: 'break-intervention-modal', title: t('exercises.implement.break_intervention.title'), template: 'interventions/_break_intervention_modal')

View File

@@ -35,9 +35,6 @@
.form-group
= f.label(t('activerecord.attributes.exercise.difficulty'))
= f.number_field :expected_difficulty, in: 1..10, step: 1
.form-group
= f.label(t('activerecord.attributes.exercise.worktime'))
= f.number_field "expected_worktime_minutes", value: @exercise.expected_worktime_seconds / 60, in: 1..1000, step: 1
h2 = t('exercises.form.tags')
ul.list-unstyled.panel-group

View File

@@ -0,0 +1,15 @@
h1 = @exercise
ul.list-unstyled.panel-group#files
- @feedbacks.each do |feedback|
li.panel.panel-default
.panel-heading role="tab" id="heading"
div.clearfix
span = feedback.user.name
.panel-collapse role="tabpanel"
.panel-body.feedback
.text = feedback.feedback_text
.difficulty = "#{t('user_exercise_feedback.difficulty')} #{feedback.difficulty}" if feedback.difficulty
.worktime = "#{t('user_exercise_feedback.working_time')} #{feedback.user_estimated_worktime}" if feedback.user_estimated_worktime
= render('shared/pagination', collection: @feedbacks)

View File

@@ -18,7 +18,6 @@ h1 = Exercise.model_name.human(count: 2)
th = t('activerecord.attributes.exercise.maximum_score')
th = t('activerecord.attributes.exercise.tags')
th = t('activerecord.attributes.exercise.difficulty')
th = t('activerecord.attributes.exercise.worktime')
th
= t('activerecord.attributes.exercise.public')
- if policy(Exercise).batch_update?
@@ -34,7 +33,6 @@ h1 = Exercise.model_name.human(count: 2)
td = exercise.maximum_score
td = exercise.exercise_tags.count
td = exercise.expected_difficulty
td = (exercise.expected_worktime_seconds / 60).ceil
td.public data-value=exercise.public? = symbol_for(exercise.public?)
td = link_to(t('shared.edit'), edit_exercise_path(exercise)) if policy(exercise).edit?
td = link_to(t('.implement'), implement_exercise_path(exercise)) if policy(exercise).implement?
@@ -47,6 +45,7 @@ h1 = Exercise.model_name.human(count: 2)
span.sr-only Toggle Dropdown
ul.dropdown-menu.pull-right role="menu"
li = link_to(t('shared.show'), exercise) if policy(exercise).show?
li = link_to(t('activerecord.models.user_exercise_feedback.other'), feedback_exercise_path(exercise)) if policy(exercise).feedback?
li = link_to(t('shared.destroy'), exercise, data: {confirm: t('shared.confirm_destroy')}, method: :delete) if policy(exercise).destroy?
li = link_to(t('.clone'), clone_exercise_path(exercise), data: {confirm: t('shared.confirm_destroy')}, method: :post) if policy(exercise).clone?

View File

@@ -20,7 +20,6 @@ h1
= row(label: 'exercise.embedding_parameters') do
= content_tag(:input, nil, class: 'form-control', readonly: true, value: embedding_parameters(@exercise))
= row(label: 'exercise.difficulty', value: @exercise.expected_difficulty)
= row(label: 'exercise.worktime', value: "#{@exercise.expected_worktime_seconds/60} min")
= row(label: 'exercise.tags', value: @exercise.exercise_tags.map{|et| "#{et.tag.name} (#{et.factor})"}.sort.join(", "))
h2 = t('activerecord.attributes.exercise.files')

View File

@@ -29,7 +29,8 @@
<%= t('activerecord.attributes.request_for_comments.question')%>
</h5>
<div class="text">
<%= @request_for_comment.question or t('request_for_comments.no_question')%>
<% question = @request_for_comment.question %>
<%= question.nil? or question.empty? ? t('request_for_comments.no_question') : question %>
</div>
</div>

View File

@@ -0,0 +1,27 @@
h1 = UserExerciseFeedback.model_name.human(count: 2)
= render(layout: 'shared/form_filters') do |f|
.form-group
= f.label(:execution_environment_id_eq, t('activerecord.attributes.exercise.execution_environment'), class: 'sr-only')
= f.collection_select(:execution_environment_id_eq, ExecutionEnvironment.with_exercises, :id, :name, class: 'form-control', prompt: t('activerecord.attributes.exercise.execution_environment'))
.form-group
= f.label(:exercise_title_cont, t('activerecord.attributes.request_for_comments.exercise'), class: 'sr-only')
= f.search_field(:exercise_title_cont, class: 'form-control', placeholder: t('activerecord.attributes.request_for_comments.exercise'))
.table-responsive
table.table
thead
tr
th colspan=2 = t('activerecord.attributes.user_exercise_feedback.user')
th = t('activerecord.attributes.user_exercise_feedback.exercise')
th colspan=2 = t('shared.actions')
tbody
- @uefs.each do |uef|
tr
td = uef.user.id
td = uef.user.name
td = link_to(uef.exercise.title, uef.exercise)
td = link_to(t('shared.show'), uef)
td = link_to(t('shared.destroy'), uef, data: {confirm: t('shared.confirm_destroy')}, method: :delete)
= render('shared/pagination', collection: @uefs)

View File

@@ -0,0 +1,7 @@
h2 = @uef
= row(label: 'activerecord.attributes.user_exercise_feedback.exercise', value: link_to(@uef.exercise.title, @uef.exercise))
= row(label: 'user_exercise_feedback.user', value: @uef.user)
= row(label: 'activerecord.attributes.user_exercise_feedback.feedback_text', value: @uef.feedback_text)
= row(label: 'user_exercise_feedback.difficulty', value: @uef.difficulty)
= row(label: 'user_exercise_feedback.working_time', value: @uef.user_estimated_worktime)