diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index 5505ef56..96762e2b 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -7,7 +7,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_and_authorize, only: MEMBER_ACTIONS + [:clone, :implement, :working_times, :intervention, :search, :run, :statistics, :submit, :reload, :feedback, :study_group_dashboard] + before_action :set_exercise_and_authorize, only: MEMBER_ACTIONS + [:clone, :implement, :working_times, :intervention, :search, :run, :statistics, :submit, :reload, :feedback, :requests_for_comments, :study_group_dashboard] before_action :set_external_user_and_authorize, only: [:statistics] before_action :set_file_types, only: [:create, :edit, :new, :update] before_action :set_course_token, only: [:implement] @@ -105,6 +105,21 @@ class ExercisesController < ApplicationController def feedback authorize! @feedbacks = @exercise.user_exercise_feedbacks.paginate(page: params[:page]) + @submissions = @feedbacks.map do |feedback| + feedback.exercise.final_submission(feedback.user) + end + end + + def requests_for_comments + authorize! + @search = RequestForComment + .with_last_activity + .where(exercise: @exercise) + .ransack(params[:q]) + @request_for_comments = @search.result + .order('last_comment DESC') + .paginate(page: params[:page]) + render 'request_for_comments/index' end def import_proforma_xml diff --git a/app/models/exercise.rb b/app/models/exercise.rb index 3f4414e8..def5a098 100644 --- a/app/models/exercise.rb +++ b/app/models/exercise.rb @@ -493,12 +493,17 @@ class Exercise < ApplicationRecord def maximum_score(user = nil) if user + # FIXME: where(user: user) will not work here! submissions.where(user: user).where("cause IN ('submit','assess')").where("score IS NOT NULL").order("score DESC").first.score || 0 rescue 0 else files.teacher_defined_tests.sum(:weight) end end + def final_submission(user) + submissions.final.where(user_id: user.id, user_type: user.class.name).order(created_at: :desc).first + end + def has_user_solved(user) maximum_score(user).to_i == maximum_score.to_i end diff --git a/app/policies/exercise_policy.rb b/app/policies/exercise_policy.rb index 662349fe..ba30802d 100644 --- a/app/policies/exercise_policy.rb +++ b/app/policies/exercise_policy.rb @@ -3,11 +3,11 @@ class ExercisePolicy < AdminOrAuthorPolicy admin? end - [:show?, :study_group_dashboard?].each do |action| + [:show?, :study_group_dashboard?, :feedback?, :requests_for_comments?, :statistics?].each do |action| define_method(action) { admin? || teacher? } end - [:clone?, :destroy?, :edit?, :statistics?, :update?, :feedback?].each do |action| + [:clone?, :destroy?, :edit?, :update?].each do |action| define_method(action) { admin? || author? } end diff --git a/app/views/exercises/feedback.html.slim b/app/views/exercises/feedback.html.slim index 3405a542..7c28eabc 100644 --- a/app/views/exercises/feedback.html.slim +++ b/app/views/exercises/feedback.html.slim @@ -4,12 +4,17 @@ h1 = link_to_if(policy(@exercise).show?, @exercise, exercise_path(@exercise)) .header = t('activerecord.attributes.exercise.description') .value = render_markdown(@exercise.description) - .header = t('activerecord.models.user_exercise_feedback.other') + span.header.col-sm-3.pl-0 = "#{t('activerecord.attributes.exercise.maximum_score')}" + span.col-sm-9 = @exercise.maximum_score + + .header.mt-3 = t('activerecord.models.user_exercise_feedback.other') - if @feedbacks.nil? or @feedbacks.size == 0 .no-feedback = t('user_exercise_feedback.no_feedback') ul.list-unstyled - - @feedbacks.each do |feedback| + - comment_presets = UserExerciseFeedbacksController.new.comment_presets + - time_presets = UserExerciseFeedbacksController.new.time_presets + - @feedbacks.each_with_index do |feedback, index| li.card.mt-2 .card-header role="tab" id="heading" div.clearfix.feedback-header @@ -20,8 +25,11 @@ h1 = link_to_if(policy(@exercise).show?, @exercise, exercise_path(@exercise)) .card-collapse role="tabpanel" .card-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 + .difficulty = "#{t('user_exercise_feedback.difficulty')} #{comment_presets[feedback.difficulty].join(' - ')}" if feedback.difficulty + .worktime = "#{t('user_exercise_feedback.working_time')} #{time_presets[feedback.user_estimated_worktime].join(' - ')}" if feedback.user_estimated_worktime + .card-footer + span.points = "#{t('exercises.statistics.score')}: #{@submissions[index].score}" + span.working_time.pull-right = "#{t('exercises.statistics.worktime')}: #{@exercise.average_working_time_for(feedback.user.id) or 0}" = render('shared/pagination', collection: @feedbacks) diff --git a/app/views/exercises/index.html.slim b/app/views/exercises/index.html.slim index 9c26ab6f..2594fb28 100644 --- a/app/views/exercises/index.html.slim +++ b/app/views/exercises/index.html.slim @@ -44,6 +44,7 @@ h1 = Exercise.model_name.human(count: 2) ul.dropdown-menu.float-right role="menu" li = link_to(t('shared.show'), exercise, 'data-turbolinks' => "false", class: 'dropdown-item') if policy(exercise).show? li = link_to(t('activerecord.models.user_exercise_feedback.other'), feedback_exercise_path(exercise), class: 'dropdown-item') if policy(exercise).feedback? + li = link_to(t('activerecord.models.request_for_comment.other'), requests_for_comments_exercise_path(exercise), class: 'dropdown-item') if policy(exercise).requests_for_comments? li = link_to(t('shared.destroy'), exercise, data: {confirm: t('shared.confirm_destroy')}, method: :delete, class: 'dropdown-item') if policy(exercise).destroy? li = link_to(t('.clone'), clone_exercise_path(exercise), data: {confirm: t('shared.confirm_destroy')}, method: :post, class: 'dropdown-item') if policy(exercise).clone? diff --git a/app/views/exercises/statistics.html.slim b/app/views/exercises/statistics.html.slim index dfa79b25..d4b7b1b2 100644 --- a/app/views/exercises/statistics.html.slim +++ b/app/views/exercises/statistics.html.slim @@ -38,18 +38,19 @@ h1 = @exercise hr div#chart_2 hr - .table-responsive - table.table.table-striped.sortable - thead - tr - - ['.user', '.score', '.runs', '.worktime'].each do |title| - th.header = t(title) - tbody - - @exercise.send(symbol).distinct().each do |user| - - if user_statistics[user.id] then us = user_statistics[user.id] else us = {"maximum_score" => nil, "runs" => nil} - - label = "#{user.displayname}" + - if current_user.admin? + .table-responsive + table.table.table-striped.sortable + thead tr - td = link_to_if symbol==:external_users && policy(user).statistics?, label, {controller: "exercises", action: "statistics", external_user_id: user.id, id: @exercise.id} - td = us['maximum_score'] or 0 - td = us['runs'] - td = @exercise.average_working_time_for(user.id) or 0 + - ['.user', '.score', '.runs', '.worktime'].each do |title| + th.header = t(title) + tbody + - @exercise.send(symbol).distinct().each do |user| + - if user_statistics[user.id] then us = user_statistics[user.id] else us = {"maximum_score" => nil, "runs" => nil} + - label = "#{user.displayname}" + tr + td = link_to_if symbol==:external_users && policy(user).statistics?, label, {controller: "exercises", action: "statistics", external_user_id: user.id, id: @exercise.id} + td = us['maximum_score'] or 0 + td = us['runs'] + td = @exercise.average_working_time_for(user.id) or 0 diff --git a/config/locales/de.yml b/config/locales/de.yml index f36e5cbb..dee7ba41 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -353,6 +353,7 @@ de: implement: Implementieren test_files: Test-Dateien feedback: Feedback + requests_for_comments: Kommentaranfragen study_group_dashboard: Live Dashboard statistics: average_score: Durchschnittliche Punktzahl diff --git a/config/locales/en.yml b/config/locales/en.yml index e9d30ec0..eb5c399f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -353,6 +353,7 @@ en: implement: Implement test_files: Test Files feedback: Feedback + requests_for_comments: Requests for Comments study_group_dashboard: Live Dashboard statistics: average_score: Average Score diff --git a/config/routes.rb b/config/routes.rb index 42cee5a3..43f54694 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -81,6 +81,7 @@ Rails.application.routes.draw do post :search get :statistics get :feedback + get :requests_for_comments get :reload post :submit get 'study_group_dashboard/:study_group_id', to: 'exercises#study_group_dashboard' diff --git a/spec/policies/exercise_policy_spec.rb b/spec/policies/exercise_policy_spec.rb index 49afe05a..4b4acd4e 100644 --- a/spec/policies/exercise_policy_spec.rb +++ b/spec/policies/exercise_policy_spec.rb @@ -14,7 +14,7 @@ let(:exercise) { FactoryBot.build(:dummy) } end end - [:create?, :index?, :new?].each do |action| + [:create?, :index?, :new?, :statistics?, :feedback?, :requests_for_comments?].each do |action| permissions(action) do it 'grants access to admins' do expect(subject).to permit(FactoryBot.build(:admin), exercise) @@ -30,7 +30,7 @@ let(:exercise) { FactoryBot.build(:dummy) } end end - [:clone?, :destroy?, :edit?, :statistics?, :update?].each do |action| + [:clone?, :destroy?, :edit?, :update?].each do |action| permissions(action) do it 'grants access to admins' do expect(subject).to permit(FactoryBot.build(:admin), exercise)