merged with current master
This commit is contained in:
18
app/assets/javascripts/error_templates.js
Normal file
18
app/assets/javascripts/error_templates.js
Normal 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});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
9
app/assets/stylesheets/error_templates.scss
Normal file
9
app/assets/stylesheets/error_templates.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
#add-attribute {
|
||||
display: flex;
|
||||
max-width: 400px;
|
||||
margin-top: 30px;
|
||||
|
||||
button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
|
86
app/controllers/error_template_attributes_controller.rb
Normal file
86
app/controllers/error_template_attributes_controller.rb
Normal 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
|
104
app/controllers/error_templates_controller.rb
Normal file
104
app/controllers/error_templates_controller.rb
Normal 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
|
51
app/controllers/exercise_collections_controller.rb
Normal file
51
app/controllers/exercise_collections_controller.rb
Normal 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
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
2
app/helpers/error_template_attributes_helper.rb
Normal file
2
app/helpers/error_template_attributes_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module ErrorTemplateAttributesHelper
|
||||
end
|
2
app/helpers/error_templates_helper.rb
Normal file
2
app/helpers/error_templates_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module ErrorTemplatesHelper
|
||||
end
|
8
app/models/error_template.rb
Normal file
8
app/models/error_template.rb
Normal 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
|
7
app/models/error_template_attribute.rb
Normal file
7
app/models/error_template_attribute.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
class ErrorTemplateAttribute < ActiveRecord::Base
|
||||
has_and_belongs_to_many :error_template
|
||||
|
||||
def to_s
|
||||
"#{id} [#{key}]"
|
||||
end
|
||||
end
|
@@ -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)') }
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
12
app/models/structured_error.rb
Normal file
12
app/models/structured_error.rb
Normal 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
|
17
app/models/structured_error_attribute.rb
Normal file
17
app/models/structured_error_attribute.rb
Normal 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
|
@@ -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
|
||||
|
@@ -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
|
||||
|
3
app/policies/error_template_attribute_policy.rb
Normal file
3
app/policies/error_template_attribute_policy.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class ErrorTemplateAttributePolicy < AdminOnlyPolicy
|
||||
|
||||
end
|
9
app/policies/error_template_policy.rb
Normal file
9
app/policies/error_template_policy.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
class ErrorTemplatePolicy < AdminOnlyPolicy
|
||||
def add_attribute?
|
||||
admin?
|
||||
end
|
||||
|
||||
def remove_attribute?
|
||||
admin?
|
||||
end
|
||||
end
|
3
app/policies/exercise_collection_policy.rb
Normal file
3
app/policies/exercise_collection_policy.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class ExerciseCollectionPolicy < AdminOnlyPolicy
|
||||
|
||||
end
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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"))
|
||||
|
16
app/views/error_template_attributes/_form.html.slim
Normal file
16
app/views/error_template_attributes/_form.html.slim
Normal 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)
|
3
app/views/error_template_attributes/edit.html.slim
Normal file
3
app/views/error_template_attributes/edit.html.slim
Normal file
@@ -0,0 +1,3 @@
|
||||
h1 = @error_template_attribute
|
||||
|
||||
= render('form')
|
28
app/views/error_template_attributes/index.html.slim
Normal file
28
app/views/error_template_attributes/index.html.slim
Normal 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)
|
3
app/views/error_template_attributes/new.html.slim
Normal file
3
app/views/error_template_attributes/new.html.slim
Normal file
@@ -0,0 +1,3 @@
|
||||
h1 = t('shared.new_model', model: ErrorTemplateAttribute.model_name.human)
|
||||
|
||||
= render('form')
|
8
app/views/error_template_attributes/show.html.slim
Normal file
8
app/views/error_template_attributes/show.html.slim
Normal 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
|
19
app/views/error_templates/_form.html.slim
Normal file
19
app/views/error_templates/_form.html.slim
Normal 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)
|
3
app/views/error_templates/edit.html.slim
Normal file
3
app/views/error_templates/edit.html.slim
Normal file
@@ -0,0 +1,3 @@
|
||||
h1 = @error_template
|
||||
|
||||
= render('form')
|
22
app/views/error_templates/index.html.slim
Normal file
22
app/views/error_templates/index.html.slim
Normal 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)
|
3
app/views/error_templates/new.html.slim
Normal file
3
app/views/error_templates/new.html.slim
Normal file
@@ -0,0 +1,3 @@
|
||||
h1 = t('shared.new_model', model: ErrorTemplate.model_name.human)
|
||||
|
||||
= render('form')
|
40
app/views/error_templates/show.html.slim
Normal file
40
app/views/error_templates/show.html.slim
Normal 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')
|
11
app/views/exercise_collections/_form.html.slim
Normal file
11
app/views/exercise_collections/_form.html.slim
Normal 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)
|
3
app/views/exercise_collections/edit.html.slim
Normal file
3
app/views/exercise_collections/edit.html.slim
Normal file
@@ -0,0 +1,3 @@
|
||||
h1 = @exercise_collection
|
||||
|
||||
= render('form')
|
24
app/views/exercise_collections/index.html.slim
Normal file
24
app/views/exercise_collections/index.html.slim
Normal 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)
|
3
app/views/exercise_collections/new.html.slim
Normal file
3
app/views/exercise_collections/new.html.slim
Normal file
@@ -0,0 +1,3 @@
|
||||
h1 = t('shared.new_model', model: ExerciseCollection.model_name.human)
|
||||
|
||||
= render('form')
|
11
app/views/exercise_collections/show.html.slim
Normal file
11
app/views/exercise_collections/show.html.slim
Normal 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)
|
@@ -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')
|
||||
|
@@ -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
|
||||
|
15
app/views/exercises/feedback.html.slim
Normal file
15
app/views/exercises/feedback.html.slim
Normal 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)
|
@@ -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?
|
||||
|
||||
|
@@ -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')
|
||||
|
@@ -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>
|
||||
|
||||
|
27
app/views/user_exercise_feedbacks/index.html.slim
Normal file
27
app/views/user_exercise_feedbacks/index.html.slim
Normal 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)
|
7
app/views/user_exercise_feedbacks/show.html.slim
Normal file
7
app/views/user_exercise_feedbacks/show.html.slim
Normal 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)
|
Reference in New Issue
Block a user