canvas-lms/app/presenters/grade_summary_assignment_pr...

355 lines
8.7 KiB
Ruby

# frozen_string_literal: true
#
# Copyright (C) 2013 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
class GradeSummaryAssignmentPresenter
include TextHelper
include GradeDisplay
attr_reader :assignment, :submission, :originality_reports
def initialize(summary, current_user, assignment, submission)
@summary = summary
@current_user = current_user
@assignment = assignment
@submission = submission
@originality_reports = @submission.originality_reports_for_display if @submission
end
def upload_status
return unless submission
# The sort here ensures that statuses received are in the failed,
# pending and success order. With that security we can just pluck
# first one.
submission.attachments
.map { |a| AttachmentUploadStatus.upload_status(a) }
.min
end
def originality_report?
@originality_reports.present?
end
def show_distribution_graph?
@assignment.score_statistic = @summary.assignment_stats[assignment.id] # Avoid another query
@assignment.can_view_score_statistics?(@current_user)
end
def is_unread?
(submission.present? ? @summary.unread_submission_ids.include?(submission.id) : false)
end
def item_unread?(item)
submission.present? && !!@summary.unread_submission_items[submission.id]&.include?(item)
end
def hide_grade_from_student?
submission.blank? || submission.hide_grade_from_student?
end
def graded?
return false if submission.blank?
(submission.grade || submission.excused?) && !hide_grade_from_student?
end
def is_letter_graded?
assignment.grading_type == "letter_grade"
end
def is_gpa_scaled?
assignment.grading_type == "gpa_scale"
end
def points_graded?
assignment.grading_type == "points"
end
def is_letter_graded_or_gpa_scaled?
is_letter_graded? || is_gpa_scaled?
end
def is_assignment?
assignment.instance_of?(Assignment)
end
def has_no_group_weight?
!(assignment.group_weight rescue false)
end
def has_no_score_display?
hide_grade_from_student? || submission.nil?
end
def quiz_pending_review?
submission&.submission_type == "online_quiz" && submission&.workflow_state == "pending_review"
end
def custom_grade_status?
return false unless Account.site_admin.feature_enabled?(:custom_gradebook_statuses)
submission&.custom_grade_status_id?
end
def custom_grade_status_id
submission&.custom_grade_status_id
end
def original_points
has_no_score_display? ? "" : submission.published_score
end
def unchangeable?
!@summary.editable? || assignment.special_class
end
def has_comments?
submission&.visible_submission_comments && !submission.visible_submission_comments.empty?
end
def has_scoring_details?
return false unless submission&.score.present? && assignment&.points_possible.present?
assignment.points_possible > 0 && !hide_grade_from_student?
end
def has_grade_distribution?
return false if assignment&.points_possible.blank?
assignment.points_possible > 0 && !hide_grade_from_student?
end
def has_rubric_assessments?
!rubric_assessments.empty?
end
def is_text_entry?
submission.submission_type == "online_text_entry"
end
def is_online_upload?
submission.submission_type == "online_upload"
end
def should_display_details?
!assignment.special_class && (has_comments? || has_scoring_details?)
end
def special_class
assignment.special_class ? ("hard_coded " + assignment.special_class) : "editable"
end
def show_submission_details?
is_assignment? && !!submission&.can_view_details?(@current_user)
end
def classes
classes = ["student_assignment"]
classes << "assignment_graded" if graded?
classes << special_class
classes << "excused" if excused?
classes << "extended" if extended?
classes.join(" ")
end
def missing?
submission.try(:missing?)
end
def late?
submission.try(:late?)
end
def excused?
submission.try(:excused?)
end
def extended?
submission.try(:extended?)
end
def deduction_present?
!!(submission&.points_deducted&.> 0)
end
def entered_grade
if is_letter_graded_or_gpa_scaled? && submission.entered_grade.present?
"(#{submission.entered_grade})"
else
""
end
end
def display_entered_score
"#{I18n.n round_if_whole(submission.entered_score)} #{entered_grade}"
end
def display_points_deducted
I18n.n round_if_whole(-submission.points_deducted)
end
def published_grade
if is_letter_graded_or_gpa_scaled? && !submission.published_grade.nil?
"(#{replace_dash_with_minus(submission.published_grade)})"
else
""
end
end
def display_score
if has_no_score_display?
""
else
"#{I18n.n round_if_whole(submission.published_score)} #{published_grade}"
end
end
def turnitin
plagiarism("turnitin")
end
def vericite
plagiarism("vericite")
end
def plagiarism(type)
plag_data = if type == "vericite"
submission.vericite_data(true)
else
submission.originality_data
end
t = if is_text_entry?
plag_data[OriginalityReport.submission_asset_key(submission)] ||
plag_data[submission.asset_string]
elsif is_online_upload? && file
plag_data[file.asset_string]
end
t.try(:[], :state) ? t : nil
end
def grade_distribution
@grade_distribution ||= if (stats = @summary.assignment_stats[assignment.id])
[stats.maximum, stats.minimum, stats.mean, stats.median, stats.lower_q, stats.upper_q].map { |stat| stat&.to_f&.round(2) }
end
end
def graph
@graph ||= begin
high, low, mean, median, lower_q, upper_q = grade_distribution
score = submission&.score
# Just render the old-style fake box-and whiskers plot (box edges are high and low with middle at mean)
# if flag off or we don't have the new statistics
GradeSummaryGraph.new(
high:,
low:,
lower_q:,
upper_q:,
median:,
mean:,
points_possible: assignment.points_possible,
score:,
legacy: !show_advanced_statistics?(median)
)
end
end
def show_advanced_statistics?(median)
Account.site_admin.feature_enabled?(:enhanced_grade_statistics) && !median.nil?
end
def file
@file ||= submission.attachments.detect { |a| plagiarism_attachment?(a) }
end
def plagiarism_attachment?(a)
@originality_reports.any? { |o| o.attachment == a } ||
(submission.turnitin_data && submission.turnitin_data[a.asset_string]).present? ||
(submission.vericite_data(true) && submission.vericite_data(true)[a.asset_string]).present?
end
def comments
submission.visible_submission_comments
end
def rubric_assessments
return [] unless submission
submission.visible_rubric_assessments_for(@current_user)
end
def group
@group ||= assignment&.assignment_group
end
def viewing_fake_student?
@summary.student_enrollment.fake_student?
end
FULLWIDTH = 150.0
GradeSummaryGraph = Struct.new(:high, :low, :lower_q, :upper_q, :median, :mean, :points_possible, :score, :legacy, keyword_init: true) do
def low_pos
pixels_for(legacy ? 0 : low)
end
def lq_pos
pixels_for(legacy ? low : lower_q)
end
def uq_pos
pixels_for(legacy ? high : upper_q)
end
def median_pos
pixels_for(legacy ? mean : median)
end
def high_pos
pixels_for(legacy ? points_possible : high)
end
def max_pos
FULLWIDTH
end
def score_pos
pixels_for(score)
end
def title
if legacy
I18n.t("#grade_summary.graph_title",
"Mean %{mean}, High %{high}, Low %{low}",
{
mean: I18n.n(mean), high: I18n.n(high), low: I18n.n(low)
})
else
I18n.t("Median %{median}, High %{high}, Low %{low}",
{
median: I18n.n(median), high: I18n.n(high), low: I18n.n(low)
})
end
end
private
def pixels_for(value)
(value.to_f / points_possible * FULLWIDTH)
end
end
end