add soft-deletion to rubric associations
closes EVAL-812 flag=none Test Plan 1: Deleting and Restoring a Rubric Association 1. Create a rubric in a course and attach it to an assignment. 2. In SpeedGrader, use the rubric to assess a couple students. 3. On the assignment show page, click the trash can icon on the rubric to delete the rubric association with the assignment (it says "Delete Rubric" but it's really just deleting the Rubric Association). 4. Go back to SpeedGrader and verify the rubric does not show up for the students that you assessed (the final grade should still be awarded, if you awarded one separate from the rubric). 5. Verify the rubric itself still exists at /courses/:id/rubrics 6. Go to /courses/:id/undelete and verify the RubricAssociation that was deleted is listed there. Click 'restore'. 7. Go back to the assignment show page and note that the rubric is now showing again. 8. Go back to SpeedGrader and note that the rubric assessments have returned. Hallelujah! Test Plan 2: Deleting and Restoring a Rubric 1. Create a rubric in a course and attach it to an assignment. 2. In SpeedGrader, use the rubric to assess a couple students. 3. Go to /courses/:id/rubrics, click on the rubric, and then click 'Delete Rubric'. 4. Go to the assignment show page and verify the rubric does not show as attached to the assignment any more. 5. Go back to SpeedGrader and verify the rubric does not show up for the students that you assessed (the final grade should still be awarded, if you awarded one separate from the rubric). 6. Go to /courses/:id/undelete and verify the Rubric that was deleted is listed there. Click 'restore'. You can also verify that the RubricAssociation for the assignment is listed on the page as well, though you don't need to click 'Restore' for that item. 7. Go back to the assignment show page and note that the rubric is now showing again. 8. Go back to SpeedGrader and note that the rubric assessments have returned. Hallelujah! * Edge case testing: test account rubrics, and using rubrics that were created in other courses. Change-Id: I02745619457f99f760bd9459ab9d85d23bba57bc Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/253739 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> QA-Review: Kai Bjorkman <kbjorkman@instructure.com> Product-Review: Syed Hussain <shussain@instructure.com> Reviewed-by: Adrian Packel <apackel@instructure.com> Reviewed-by: Gary Mei <gmei@instructure.com>
This commit is contained in:
parent
d720d0d122
commit
c2f17da038
|
@ -340,11 +340,15 @@ class ContextController < ApplicationController
|
|||
|
||||
WORKFLOW_TYPES = [:all_discussion_topics, :assignment_groups, :assignments,
|
||||
:collaborations, :context_modules, :enrollments, :groups,
|
||||
:quizzes, :rubrics, :wiki_pages].freeze
|
||||
:quizzes, :rubrics, :wiki_pages, :rubric_associations_with_deleted].freeze
|
||||
ITEM_TYPES = WORKFLOW_TYPES + [:attachments, :all_group_categories].freeze
|
||||
def undelete_index
|
||||
if authorized_action(@context, @current_user, :manage_content)
|
||||
@item_types = WORKFLOW_TYPES.select { |type| @context.class.reflections.key?(type.to_s) }.map { |type| @context.association(type).reader }
|
||||
@item_types = WORKFLOW_TYPES.each_with_object([]) do |workflow_type, item_types|
|
||||
if @context.class.reflections.key?(workflow_type.to_s)
|
||||
item_types << @context.association(workflow_type).reader
|
||||
end
|
||||
end
|
||||
|
||||
@deleted_items = []
|
||||
@item_types.each do |scope|
|
||||
|
@ -369,6 +373,7 @@ class ContextController < ApplicationController
|
|||
return render_unauthorized_action unless @context.grants_right?(@current_user, :manage_groups)
|
||||
end
|
||||
type = type.pluralize
|
||||
type = 'rubric_associations_with_deleted' if type == 'rubric_associations'
|
||||
raise "invalid type" unless ITEM_TYPES.include?(type.to_sym) && scope.class.reflections.key?(type)
|
||||
|
||||
@item = scope.association(type).reader.find(id)
|
||||
|
|
|
@ -957,7 +957,7 @@ class GradebooksController < ApplicationController
|
|||
env[:student_group_reason_for_change] = updated_group_info.reason_for_change if updated_group_info.reason_for_change.present?
|
||||
end
|
||||
|
||||
if @assignment.rubric_association
|
||||
if @assignment.active_rubric_association?
|
||||
env[:update_rubric_assessment_url] = context_url(
|
||||
@context,
|
||||
:context_rubric_association_rubric_assessments_url,
|
||||
|
|
|
@ -131,8 +131,8 @@ class RubricAssociationsController < ApplicationController
|
|||
# If the rubric wasn't created as a general course rubric,
|
||||
# and this was the last place it was being used in the course,
|
||||
# go ahead and delete the rubric from the course.
|
||||
association_count = RubricAssociation.where(:context_id => @context, :context_type => @context.class.to_s, :rubric_id => @rubric).for_grading.count
|
||||
if !RubricAssociation.for_purpose('bookmark').where(rubric_id: @rubric).first && association_count == 0
|
||||
association_count = RubricAssociation.active.where(:context_id => @context, :context_type => @context.class.to_s, :rubric_id => @rubric).for_grading.count
|
||||
if !RubricAssociation.active.for_purpose('bookmark').where(rubric_id: @rubric).first && association_count == 0
|
||||
@rubric.destroy_for(@context, current_user: @current_user)
|
||||
end
|
||||
render :json => @association
|
||||
|
|
|
@ -191,7 +191,7 @@ class RubricsController < ApplicationController
|
|||
#
|
||||
# @returns Rubric
|
||||
def destroy
|
||||
@rubric = RubricAssociation.where(rubric_id: params[:id], context_id: @context, context_type: @context.class.to_s).first.rubric
|
||||
@rubric = RubricAssociation.active.where(rubric_id: params[:id], context_id: @context, context_type: @context.class.to_s).first.rubric
|
||||
if authorized_action(@rubric, @current_user, :delete_associations) && authorized_action(@context, @current_user, :manage_rubrics)
|
||||
@rubric.destroy_for(@context, current_user: @current_user)
|
||||
render :json => @rubric
|
||||
|
|
|
@ -838,7 +838,7 @@ class SubmissionsApiController < ApplicationController
|
|||
end
|
||||
|
||||
assessment = params[:rubric_assessment]
|
||||
if assessment.is_a?(ActionController::Parameters) && @assignment.rubric_association
|
||||
if assessment.is_a?(ActionController::Parameters) && @assignment.active_rubric_association?
|
||||
if (assessment.keys & @assignment.rubric_association.rubric.criteria_object.map{|c| c.id.to_s}).empty?
|
||||
return render :json => {:message => "invalid rubric_assessment"}, :status => :bad_request
|
||||
end
|
||||
|
|
|
@ -58,14 +58,14 @@ class AssessmentRequest < ActiveRecord::Base
|
|||
p.dispatch :rubric_assessment_submission_reminder
|
||||
p.to { self.assessor }
|
||||
p.whenever { |record|
|
||||
record.assigned? && @send_reminder && rubric_association
|
||||
record.assigned? && @send_reminder && active_rubric_association?
|
||||
}
|
||||
p.data { course_broadcast_data }
|
||||
|
||||
p.dispatch :peer_review_invitation
|
||||
p.to { self.assessor }
|
||||
p.whenever { |record|
|
||||
send_notification = record.assigned? && @send_reminder && !rubric_association
|
||||
send_notification = record.assigned? && @send_reminder && !active_rubric_association?
|
||||
# Do not send notifications if the context is an unpublished course
|
||||
# or if the asset is a submission and the assignment is unpublished
|
||||
send_notification = false if self.context.is_a?(Course) && !self.context.workflow_state.in?(['available', 'completed'])
|
||||
|
@ -147,7 +147,7 @@ class AssessmentRequest < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def comment_added(comment)
|
||||
self.workflow_state = "completed" unless self.rubric_association && self.rubric_association.rubric
|
||||
self.workflow_state = "completed" unless active_rubric_association? && self.rubric_association.rubric
|
||||
end
|
||||
|
||||
def asset_user_name
|
||||
|
@ -162,4 +162,8 @@ class AssessmentRequest < ActiveRecord::Base
|
|||
override.update(marked_complete: true) if override.present?
|
||||
end
|
||||
end
|
||||
|
||||
def active_rubric_association?
|
||||
!!self.rubric_association&.active?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -82,7 +82,7 @@ class Assignment < ActiveRecord::Base
|
|||
has_one :wiki_page
|
||||
has_many :learning_outcome_alignments, -> { where("content_tags.tag_type='learning_outcome' AND content_tags.workflow_state<>'deleted'").preload(:learning_outcome) }, as: :content, inverse_of: :content, class_name: 'ContentTag'
|
||||
has_one :rubric_association, -> { where(purpose: 'grading').order(:created_at).preload(:rubric) }, as: :association, inverse_of: :association_object
|
||||
has_one :rubric, :through => :rubric_association
|
||||
has_one :rubric, -> { merge(RubricAssociation.active) }, :through => :rubric_association
|
||||
has_one :teacher_enrollment, -> { preload(:user).where(enrollments: { workflow_state: 'active', type: 'TeacherEnrollment' }) }, class_name: 'TeacherEnrollment', foreign_key: 'course_id', primary_key: 'context_id'
|
||||
has_many :ignores, :as => :asset
|
||||
has_many :moderated_grading_selections, class_name: 'ModeratedGrading::Selection'
|
||||
|
@ -297,7 +297,7 @@ class Assignment < ActiveRecord::Base
|
|||
|
||||
# Learning outcome alignments seem to get copied magically, possibly
|
||||
# through the rubric
|
||||
if self.rubric_association
|
||||
if active_rubric_association?
|
||||
result.rubric_association = self.rubric_association.clone
|
||||
result.rubric_association.skip_updating_points_possible = true
|
||||
end
|
||||
|
@ -1261,7 +1261,7 @@ class Assignment < ActiveRecord::Base
|
|||
def destroy
|
||||
self.workflow_state = 'deleted'
|
||||
ContentTag.delete_for(self)
|
||||
self.rubric_association.destroy if self.rubric_association.present?
|
||||
self.rubric_association.destroy if active_rubric_association?
|
||||
self.save!
|
||||
|
||||
each_submission_type { |submission| submission.destroy if submission && !submission.deleted? }
|
||||
|
@ -2073,7 +2073,7 @@ class Assignment < ActiveRecord::Base
|
|||
|
||||
if opts[:comment] && opts[:assessment_request]
|
||||
# if there is no rubric the peer review is complete with just a comment
|
||||
opts[:assessment_request].complete unless opts[:assessment_request].rubric_association
|
||||
opts[:assessment_request].complete unless opts[:assessment_request].active_rubric_association?
|
||||
end
|
||||
|
||||
# commenting on a student submission results in a teacher occupying a
|
||||
|
@ -2209,7 +2209,9 @@ class Assignment < ActiveRecord::Base
|
|||
|
||||
def as_json(options={})
|
||||
json = super(options)
|
||||
if json && json['assignment']
|
||||
return json unless json
|
||||
|
||||
if json['assignment']
|
||||
# remove anything coming automatically from deprecated db column
|
||||
json['assignment'].delete('group_category')
|
||||
if self.group_category
|
||||
|
@ -2219,7 +2221,16 @@ class Assignment < ActiveRecord::Base
|
|||
# or failing that, version from query
|
||||
json['assignment']['group_category'] = self.read_attribute('group_category')
|
||||
end
|
||||
|
||||
if json.dig('assignment', 'rubric_association') && !active_rubric_association?
|
||||
json['assignment'].delete('rubric_association')
|
||||
end
|
||||
end
|
||||
|
||||
if json['rubric_association'] && !active_rubric_association?
|
||||
json.delete('rubric_association')
|
||||
end
|
||||
|
||||
json
|
||||
end
|
||||
|
||||
|
@ -2375,7 +2386,7 @@ class Assignment < ActiveRecord::Base
|
|||
private :visible_students_for_speed_grader
|
||||
|
||||
def visible_rubric_assessments_for(user, opts={})
|
||||
return [] unless user && self.rubric_association
|
||||
return [] unless user && active_rubric_association?
|
||||
|
||||
scope = self.rubric_association.rubric_assessments.preload(:assessor)
|
||||
|
||||
|
@ -3585,6 +3596,10 @@ class Assignment < ActiveRecord::Base
|
|||
self.find_by(lti_context_id: lti_context_id)
|
||||
end
|
||||
|
||||
def active_rubric_association?
|
||||
!!self.rubric_association&.active?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_muted
|
||||
|
|
|
@ -495,7 +495,7 @@ class ContentTag < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def has_rubric_association?
|
||||
content.respond_to?(:rubric_association) && content.rubric_association
|
||||
content.respond_to?(:rubric_association) && !!content.rubric_association&.active?
|
||||
end
|
||||
|
||||
scope :for_tagged_url, lambda { |url, tag| where(:url => url, :tag => tag) }
|
||||
|
|
|
@ -85,7 +85,7 @@ module Context
|
|||
end
|
||||
|
||||
def self.sorted_rubrics(user, context)
|
||||
associations = RubricAssociation.bookmarked.for_context_codes(context.asset_string).preload(:rubric => :context)
|
||||
associations = RubricAssociation.active.bookmarked.for_context_codes(context.asset_string).preload(:rubric => :context)
|
||||
Canvas::ICU.collate_by(associations.to_a.uniq(&:rubric_id).select{|r| r.rubric }) { |r| r.rubric.title || CanvasSort::Last }
|
||||
end
|
||||
|
||||
|
@ -102,7 +102,7 @@ module Context
|
|||
context_codes << context.asset_string if context
|
||||
end
|
||||
end
|
||||
associations += RubricAssociation.bookmarked.for_context_codes(context_codes).include_rubric.preload(:context).to_a
|
||||
associations += RubricAssociation.active.bookmarked.for_context_codes(context_codes).include_rubric.preload(:context).to_a
|
||||
end
|
||||
|
||||
associations = associations.select(&:rubric).uniq{|a| [a.rubric_id, a.context.asset_string] }
|
||||
|
|
|
@ -139,7 +139,7 @@ class LearningOutcomeResult < ActiveRecord::Base
|
|||
# rubocop:disable Metrics/LineLength
|
||||
scope :exclude_muted_associations, -> {
|
||||
joins("LEFT JOIN #{RubricAssociation.quoted_table_name} rassoc ON rassoc.id = learning_outcome_results.association_id AND learning_outcome_results.association_type = 'RubricAssociation'").
|
||||
joins("LEFT JOIN #{Assignment.quoted_table_name} ra ON ra.id = rassoc.association_id AND rassoc.association_type = 'Assignment' AND rassoc.purpose = 'grading'").
|
||||
joins("LEFT JOIN #{Assignment.quoted_table_name} ra ON ra.id = rassoc.association_id AND rassoc.association_type = 'Assignment' AND rassoc.purpose = 'grading' AND rassoc.workflow_state = 'active'").
|
||||
joins("LEFT JOIN #{Quizzes::Quiz.quoted_table_name} ON quizzes.id = learning_outcome_results.association_id AND learning_outcome_results.association_type = 'Quizzes::Quiz'").
|
||||
joins("LEFT JOIN #{Assignment.quoted_table_name} qa ON qa.id = quizzes.assignment_id").
|
||||
joins("LEFT JOIN #{Assignment.quoted_table_name} sa ON sa.id = learning_outcome_results.association_id AND learning_outcome_results.association_type = 'Assignment'").
|
||||
|
|
|
@ -185,7 +185,7 @@ class ModeratedGrading::ProvisionalGrade < ActiveRecord::Base
|
|||
|
||||
def publish_rubric_assessments!
|
||||
self.rubric_assessments.each do |provisional_assessment|
|
||||
rubric_association = provisional_assessment.rubric_association
|
||||
rubric_association = provisional_assessment.active_rubric_association? ? provisional_assessment.rubric_association : nil
|
||||
# This case arises when a rubric is deleted.
|
||||
next if rubric_association.nil?
|
||||
|
||||
|
|
|
@ -28,7 +28,8 @@ class Rubric < ActiveRecord::Base
|
|||
belongs_to :user
|
||||
belongs_to :rubric # based on another rubric
|
||||
belongs_to :context, polymorphic: [:course, :account]
|
||||
has_many :rubric_associations, :class_name => 'RubricAssociation', :dependent => :destroy
|
||||
has_many :rubric_associations, -> { where(workflow_state: "active") }, class_name: 'RubricAssociation', inverse_of: :rubric, dependent: :destroy
|
||||
has_many :rubric_associations_with_deleted, class_name: 'RubricAssociation', inverse_of: :rubric
|
||||
has_many :rubric_assessments, :through => :rubric_associations, :dependent => :destroy
|
||||
has_many :learning_outcome_alignments, -> { where("content_tags.tag_type='learning_outcome' AND content_tags.workflow_state<>'deleted'").preload(:learning_outcome) }, as: :content, inverse_of: :content, class_name: 'ContentTag'
|
||||
|
||||
|
@ -99,6 +100,7 @@ class Rubric < ActiveRecord::Base
|
|||
LEFT JOIN #{RubricAssociation.quoted_table_name} associations_for_count
|
||||
ON rubrics.id = associations_for_count.rubric_id
|
||||
AND associations_for_count.purpose = 'grading'
|
||||
AND associations_for_count.workflow_state = 'active'
|
||||
JOINS
|
||||
group('rubrics.id').
|
||||
having('COUNT(rubrics.id) < 2')
|
||||
|
@ -109,6 +111,7 @@ class Rubric < ActiveRecord::Base
|
|||
LEFT JOIN #{RubricAssociation.quoted_table_name} associations_for_unassessed
|
||||
ON rubrics.id = associations_for_unassessed.rubric_id
|
||||
AND associations_for_unassessed.purpose = 'grading'
|
||||
AND associations_for_unassessed.workflow_state = 'active'
|
||||
JOINS
|
||||
joins(<<~JOINS).
|
||||
LEFT JOIN #{RubricAssessment.quoted_table_name} assessments_for_unassessed
|
||||
|
@ -137,14 +140,19 @@ class Rubric < ActiveRecord::Base
|
|||
|
||||
alias_method :destroy_permanently!, :destroy
|
||||
def destroy
|
||||
rubric_associations.update_all(:bookmarked => false, :updated_at => Time.now.utc)
|
||||
self.workflow_state = 'deleted'
|
||||
self.save
|
||||
if self.save
|
||||
rubric_associations.in_batches.destroy_all
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def restore
|
||||
self.workflow_state = 'active'
|
||||
self.save
|
||||
if self.save
|
||||
rubric_associations_with_deleted.where(workflow_state: "deleted").find_each(&:restore)
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# If any rubric_associations for a given context are marked as
|
||||
|
@ -162,9 +170,10 @@ class Rubric < ActiveRecord::Base
|
|||
association.destroy
|
||||
end
|
||||
else
|
||||
ras.update_all(:bookmarked => false, :updated_at => Time.now.utc)
|
||||
ras.destroy_all
|
||||
end
|
||||
unless rubric_associations.bookmarked.exists?
|
||||
|
||||
if rubric_associations.bookmarked.none?
|
||||
self.destroy
|
||||
end
|
||||
end
|
||||
|
|
|
@ -55,7 +55,8 @@ class RubricAssessment < ActiveRecord::Base
|
|||
|
||||
def update_outcomes_for_assessment(outcome_ids=[])
|
||||
return if outcome_ids.empty?
|
||||
alignments = if self.rubric_association.present?
|
||||
|
||||
alignments = if active_rubric_association?
|
||||
self.rubric_association.association_object.learning_outcome_alignments.where({
|
||||
learning_outcome_id: outcome_ids
|
||||
})
|
||||
|
@ -143,7 +144,7 @@ class RubricAssessment < ActiveRecord::Base
|
|||
|
||||
def update_assessment_requests
|
||||
requests = self.assessment_requests
|
||||
if self.rubric_association.present?
|
||||
if active_rubric_association?
|
||||
requests += self.rubric_association.assessment_requests.where({
|
||||
assessor_id: self.assessor_id,
|
||||
asset_id: self.artifact_id,
|
||||
|
@ -262,7 +263,8 @@ class RubricAssessment < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def considered_anonymous?
|
||||
return false unless self.rubric_association.present?
|
||||
return false unless active_rubric_association?
|
||||
|
||||
self.rubric_association.association_type == 'Assignment' &&
|
||||
self.rubric_association.association_object.anonymous_peer_reviews?
|
||||
end
|
||||
|
@ -272,7 +274,7 @@ class RubricAssessment < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def related_group_submissions_and_assessments
|
||||
if self.rubric_association && self.rubric_association.association_object.is_a?(Assignment) && !self.artifact.is_a?(ModeratedGrading::ProvisionalGrade) && !self.rubric_association.association_object.grade_group_students_individually
|
||||
if active_rubric_association? && self.rubric_association.association_object.is_a?(Assignment) && !self.artifact.is_a?(ModeratedGrading::ProvisionalGrade) && !self.rubric_association.association_object.grade_group_students_individually
|
||||
students = self.rubric_association.association_object.group_students(self.user).last
|
||||
submissions = students.map do |student|
|
||||
submission = self.rubric_association.association_object.find_asset_for_assessment(self.rubric_association, student).first
|
||||
|
@ -286,4 +288,8 @@ class RubricAssessment < ActiveRecord::Base
|
|||
def set_root_account_id
|
||||
self.root_account_id ||= self.rubric&.root_account_id
|
||||
end
|
||||
|
||||
def active_rubric_association?
|
||||
!!self.rubric_association&.active?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
# with this idea, such as assignment submissions.
|
||||
# The other purpose of this class is just to make rubrics reusable.
|
||||
class RubricAssociation < ActiveRecord::Base
|
||||
include Workflow
|
||||
include Canvas::SoftDeletable
|
||||
|
||||
attr_accessor :skip_updating_points_possible
|
||||
attr_writer :updating_user
|
||||
|
@ -34,13 +34,13 @@ class RubricAssociation < ActiveRecord::Base
|
|||
polymorphic_prefix: :association
|
||||
|
||||
belongs_to :context, polymorphic: [:course, :account]
|
||||
has_many :rubric_assessments, :dependent => :nullify
|
||||
has_many :assessment_requests, :dependent => :nullify
|
||||
has_many :rubric_assessments
|
||||
has_many :assessment_requests
|
||||
|
||||
has_a_broadcast_policy
|
||||
|
||||
validates_presence_of :purpose, :rubric_id, :association_id, :association_type, :context_id, :context_type
|
||||
validates :workflow_state, inclusion: {in: ["active"]}, allow_nil: true
|
||||
validates :workflow_state, inclusion: {in: ["active", "deleted"]}
|
||||
|
||||
before_create :set_root_account_id
|
||||
before_save :update_assignment_points
|
||||
|
@ -63,10 +63,6 @@ class RubricAssociation < ActiveRecord::Base
|
|||
before_destroy :record_deletion_audit_event
|
||||
end
|
||||
|
||||
workflow do
|
||||
state :active
|
||||
end
|
||||
|
||||
ValidAssociationModels = {
|
||||
'Course' => ::Course,
|
||||
'Assignment' => ::Assignment,
|
||||
|
@ -87,6 +83,11 @@ class RubricAssociation < ActiveRecord::Base
|
|||
klass.where(id: a_id).first if a_id.present? # authorization is checked in the calling method
|
||||
end
|
||||
|
||||
def restore
|
||||
self.workflow_state = "active"
|
||||
save
|
||||
end
|
||||
|
||||
def course_broadcast_data
|
||||
context.broadcast_data if context.is_a?(Course)
|
||||
end
|
||||
|
@ -111,7 +112,11 @@ class RubricAssociation < ActiveRecord::Base
|
|||
def assert_uniqueness
|
||||
if purpose == 'grading'
|
||||
RubricAssociation.where(association_id: association_id, association_type: association_type, purpose: 'grading').each do |ra|
|
||||
ra.destroy unless ra == self
|
||||
next if ra == self
|
||||
|
||||
ra.rubric_assessments.update_all(rubric_association_id: nil)
|
||||
ra.assessment_requests.update_all(rubric_association_id: nil)
|
||||
ra.destroy_permanently!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -126,10 +131,8 @@ class RubricAssociation < ActiveRecord::Base
|
|||
|
||||
def update_alignments
|
||||
return unless assignment
|
||||
outcome_ids = []
|
||||
unless self.destroyed?
|
||||
outcome_ids = rubric.learning_outcome_alignments.map(&:learning_outcome_id)
|
||||
end
|
||||
|
||||
outcome_ids = self.deleted? ? [] : rubric.learning_outcome_alignments.map(&:learning_outcome_id)
|
||||
LearningOutcome.update_alignments(assignment, context, outcome_ids)
|
||||
true
|
||||
end
|
||||
|
@ -390,7 +393,7 @@ class RubricAssociation < ActiveRecord::Base
|
|||
private
|
||||
|
||||
def record_save_audit_event
|
||||
existing_association = assignment.rubric_association
|
||||
existing_association = assignment.active_rubric_association? ? assignment.rubric_association : nil
|
||||
event_type = existing_association.present? ? 'rubric_updated' : 'rubric_created'
|
||||
payload = if event_type == 'rubric_created'
|
||||
{id: rubric_id}
|
||||
|
|
|
@ -58,6 +58,7 @@ module SpeedGrader
|
|||
],
|
||||
:include_root => false
|
||||
)
|
||||
|
||||
res['context']['concluded'] = assignment.context.concluded?
|
||||
res['anonymize_students'] = assignment.anonymize_students?
|
||||
res['anonymize_graders'] = !assignment.can_view_other_grader_identities?(current_user)
|
||||
|
|
|
@ -104,7 +104,11 @@ class Submission < ActiveRecord::Base
|
|||
has_many :attachment_associations, :as => :context, :inverse_of => :context
|
||||
has_many :provisional_grades, class_name: 'ModeratedGrading::ProvisionalGrade'
|
||||
has_many :originality_reports
|
||||
has_one :rubric_assessment, -> { where(assessment_type: 'grading').where.not(rubric_association: nil) }, as: :artifact, inverse_of: :artifact
|
||||
has_one :rubric_assessment, -> do
|
||||
joins(:rubric_association).
|
||||
where(assessment_type: 'grading').
|
||||
where(rubric_associations: { workflow_state: 'active' })
|
||||
end, as: :artifact, inverse_of: :artifact
|
||||
has_one :lti_result, inverse_of: :submission, class_name: 'Lti::Result', dependent: :destroy
|
||||
has_many :submission_drafts, inverse_of: :submission, dependent: :destroy
|
||||
|
||||
|
@ -2225,7 +2229,7 @@ class Submission < ActiveRecord::Base
|
|||
@assessment_request_count ||= 0
|
||||
@assessment_request_count += 1
|
||||
user = obj.user rescue nil
|
||||
association = self.assignment.rubric_association
|
||||
association = self.assignment.active_rubric_association? ? self.assignment.rubric_association : nil
|
||||
res = self.assessment_requests.where(assessor_asset_id: obj.id, assessor_asset_type: obj.class.to_s, assessor_id: user.id, rubric_association_id: association.try(:id)).
|
||||
first_or_initialize
|
||||
res.user_id = self.user_id
|
||||
|
@ -2553,7 +2557,7 @@ class Submission < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def visible_rubric_assessments_for(viewing_user, attempt: nil)
|
||||
return [] if assignment.rubric_association.blank?
|
||||
return [] unless assignment.active_rubric_association?
|
||||
|
||||
unless posted? || grants_right?(viewing_user, :read_grade)
|
||||
# If this submission is unposted and the viewer can't view the grade,
|
||||
|
@ -2636,7 +2640,7 @@ class Submission < ActiveRecord::Base
|
|||
submission.user = user
|
||||
|
||||
assessment = user_data[:rubric_assessment]
|
||||
if assessment.is_a?(Hash) && assignment.rubric_association
|
||||
if assessment.is_a?(Hash) && assignment.active_rubric_association?
|
||||
# prepend each key with "criterion_", which is required by
|
||||
# the current RubricAssociation#assess code.
|
||||
assessment.keys.each do |crit_name|
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<a
|
||||
href="<%= context_url(@context, :context_rubrics_url) %>"
|
||||
class="add_rubric_link Button icon-plus"
|
||||
style="<%= hidden if @assignment.rubric_association %>"
|
||||
style="<%= hidden if @assignment.active_rubric_association? %>"
|
||||
>
|
||||
<span aria-hidden="true"><%= t 'links.add_rubric', "Rubric" %></span>
|
||||
<span class="screenreader-only"><%= t('Add Rubric') %></span>
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
<% js_bundle 'legacy/assignments_peer_reviews' %>
|
||||
|
||||
<h1><%= t :title, "%{assignment} Peer Reviews", :assignment => @assignment.title %></h1>
|
||||
<% if @assignment.rubric_association %>
|
||||
<% if @assignment.active_rubric_association? %>
|
||||
<%= t :overview_with_rubric, "Student peer reviews will be considered complete when students have commented
|
||||
at least once on the page and filled out the rubric form for the assignment." %>
|
||||
<% else %>
|
||||
|
|
|
@ -193,7 +193,7 @@
|
|||
<a
|
||||
href="<%= context_url(@context, :context_rubrics_url) %>"
|
||||
class="add_rubric_link Button icon-plus"
|
||||
style="<%= hidden if @assignment.rubric_association %>"
|
||||
style="<%= hidden if @assignment.active_rubric_association? %>"
|
||||
>
|
||||
<span aria-hidden="true"><%= t 'links.add_rubric', "Rubric" %></span>
|
||||
<span class="screenreader-only"><%= t('Add Rubric') %></span>
|
||||
|
|
|
@ -564,7 +564,7 @@
|
|||
<div class="rubric-toggle"><a href="#" data-aria="rubric_<%= assignment.id %>" class="screenreader-toggle pull-left"><%= t(:close_rubric, 'Close Rubric') %></a></div>
|
||||
<% if @domain_root_account.feature_enabled?(:non_scoring_rubrics)%>
|
||||
<div
|
||||
class="react_rubric_container rubric <%= "for_grading" if assessment.rubric_association.try(:use_for_grading) %>"
|
||||
class="react_rubric_container rubric <%= "for_grading" if assessment.active_rubric_association? && assessment.rubric_association.try(:use_for_grading) %>"
|
||||
id="<%= assessment.rubric ? "rubric_#{assessment.rubric&.id}" : "default_rubric" %>"
|
||||
data-rubric-id="<%= assessment.rubric ? assessment.rubric.id : "default" %>"
|
||||
data-rubric-assessment-id="<%= assessment ? assessment.id : "none" %>"
|
||||
|
|
|
@ -188,7 +188,7 @@
|
|||
</div>
|
||||
|
||||
<div class="content_box">
|
||||
<% if @assignment.rubric_association %>
|
||||
<% if @assignment.active_rubric_association? %>
|
||||
<div id="rubric_full" style="display: none;">
|
||||
<div id="rubric_full_resizer">
|
||||
<div id="rubric_full_resizer_handle"></div>
|
||||
|
@ -213,7 +213,7 @@
|
|||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @assignment.rubric_association || can_do(@context, @current_user, :manage_grades) || (@context.concluded? && can_do(@context, @current_user, :read_as_admin)) %>
|
||||
<% if @assignment.active_rubric_association? || can_do(@context, @current_user, :manage_grades) || (@context.concluded? && can_do(@context, @current_user, :read_as_admin)) %>
|
||||
<div id="grading">
|
||||
<h2 class="gradebookHeader--rightside"><%= t('headers.assessment', "Assessment") %></h2>
|
||||
<% if can_do(@context, @current_user, :manage_grades) || (@context.concluded? && can_do(@context, @current_user, :read_as_admin)) %>
|
||||
|
@ -227,7 +227,7 @@
|
|||
<div id="grading_details_mount_point"></div>
|
||||
<% end %>
|
||||
|
||||
<% if @assignment.rubric_association %>
|
||||
<% if @assignment.active_rubric_association? %>
|
||||
<div id="rubric_summary_holder">
|
||||
<div id="rubric_assessments_list_and_edit_button_holder">
|
||||
<span id="rubric_assessments_list">
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
%>
|
||||
|
||||
<div id="rubrics" class="rubric_dialog" style="display: none; margin-bottom: 10px;">
|
||||
<% if @assignment.rubric_association && @assignment.rubric_association.rubric %>
|
||||
<% if @assignment.active_rubric_association? && @assignment.rubric_association.rubric %>
|
||||
<%= render :partial => "shared/rubric", :object => @assignment.rubric_association.rubric,
|
||||
:locals => {:association => @assignment,
|
||||
:rubric_association => @assignment.rubric_association,
|
||||
|
@ -31,17 +31,17 @@
|
|||
</div>
|
||||
<div class="assignment_points_possible" style="display: none;"><%= round_if_whole(@assignment.points_possible) %></div>
|
||||
<% if can_do(@assignment, @current_user, :update) %>
|
||||
<%= render :partial => "shared/rubric_dialog", :locals => {:assignment => @assignment, :rubric => @assignment.rubric_association && @assignment.rubric_association.rubric} %>
|
||||
<%= render :partial => "shared/rubric_dialog", :locals => {:assignment => @assignment, :rubric => @assignment.active_rubric_association? ? @assignment.rubric_association.rubric : nil} %>
|
||||
<% end %>
|
||||
<%= render :partial => "shared/rubric_criterion_dialog" %>
|
||||
<%= render :partial => "shared/find_outcome" %>
|
||||
<%= render :partial => "shared/rubric", :object => nil, :locals => {:association => @assignment, :editable => can_do(@assignment, @current_user, :update), :has_assessments => false, :edit_view => true} %>
|
||||
<% if can_do(@assignment, @current_user, :update) %>
|
||||
<div style="text-align: center; font-size: 1.2em; margin-top: 10px; display: none;">
|
||||
<a href="<%= context_url(@context, :context_rubrics_url) %>" class="add_rubric_link rubric" style="<%= hidden if @assignment.rubric_association %>"><%= t 'links.assign_rubric', "Assign Rubric" %></a>
|
||||
<a href="<%= context_url(@context, :context_rubrics_url) %>" class="add_rubric_link rubric" style="<%= hidden if @assignment.active_rubric_association? %>"><%= t 'links.assign_rubric', "Assign Rubric" %></a>
|
||||
</div>
|
||||
<% end %>
|
||||
<a href="#" class="btn add_rubric_link" style="margin-top: 20px; <%= hidden if @assignment && @assignment.rubric_association %>"><%= image_tag "rubric.png", :alt => '' %> <%= t 'links.add_rubric', "Add Rubric" %></a>
|
||||
<a href="#" class="btn add_rubric_link" style="margin-top: 20px; <%= hidden if @assignment && @assignment.active_rubric_association? %>"><%= image_tag "rubric.png", :alt => '' %> <%= t 'links.add_rubric', "Add Rubric" %></a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
context = @context
|
||||
context = context.context if context.is_a?(Group)
|
||||
show_grading = !@assignment || !%w{online_quiz not_graded}.include?(@assignment.submission_types)
|
||||
rubric_association = assessment.rubric_association if assessment
|
||||
rubric_association = assessment.rubric_association if assessment&.active_rubric_association?
|
||||
anonymize_student ||= false
|
||||
%>
|
||||
<% cache(['rubric_render4',
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<% end %>
|
||||
|
||||
<div id="rubrics" style="margin-bottom: 10px;">
|
||||
<% if @assignment.rubric_association && @assignment.rubric_association.rubric %>
|
||||
<% if @assignment.active_rubric_association? && @assignment.rubric_association.rubric %>
|
||||
<%= render :partial => "shared/rubric", :object => @assignment.rubric_association.rubric, :locals => {:association => @assignment, :rubric_association => @assignment.rubric_association, :editable => can_do(@assignment, @current_user, :update), :has_assessments => !@assignment.rubric_association.rubric_assessments.empty?, :edit_view => can_do(@assignment.rubric_association, @current_user, :update) } %>
|
||||
<% end %>
|
||||
<div style="display: none;" id="rubric_parameters">
|
||||
|
@ -34,7 +34,7 @@
|
|||
<input type="hidden" name="rubric_association[purpose]" value="grading"/>
|
||||
</div>
|
||||
<% if can_do(@assignment, @current_user, :update) %>
|
||||
<%= render :partial => "shared/rubric_dialog", :locals => {:assignment => @assignment, :rubric => @assignment.rubric_association && @assignment.rubric_association.rubric} %>
|
||||
<%= render :partial => "shared/rubric_dialog", :locals => {:assignment => @assignment, :rubric => @assignment.active_rubric_association? ? @assignment.rubric_association.rubric : nil} %>
|
||||
<% end %>
|
||||
<%= render :partial => "shared/rubric_criterion_dialog" %>
|
||||
</div>
|
||||
|
@ -44,7 +44,7 @@
|
|||
|
||||
<% if can_do(@assignment, @current_user, :update) %>
|
||||
<div style="text-align: center; font-size: 1.2em; margin-top: 10px; display: none;">
|
||||
<a href="<%= context_url(@context, :context_rubrics_url) %>" class="add_rubric_link rubric" style="<%= hidden if @assignment.rubric_association %>"><%= t 'links.assign_rubric', 'Assign Rubric' %></a>
|
||||
<a href="<%= context_url(@context, :context_rubrics_url) %>" class="add_rubric_link rubric" style="<%= hidden if @assignment.active_rubric_association? %>"><%= t 'links.assign_rubric', 'Assign Rubric' %></a>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render :partial => "shared/sequence_footer", :locals => {:asset => @assignment} if !@assignment.context_module_tags.empty? %>
|
||||
|
|
|
@ -174,7 +174,7 @@
|
|||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @assignment.rubric_association && (
|
||||
<% if @assignment.active_rubric_association? && (
|
||||
@submission.user_can_read_grade?(@current_user, session) ||
|
||||
@assignment.rubric_association.user_can_assess_for?(assessor: @current_user, assessee: @submission.user) ||
|
||||
@assignment.rubric_association.user_did_assess_for?(assessor: @current_user, assessee: @submission.user) ||
|
||||
|
@ -232,7 +232,7 @@
|
|||
<%= t('peer_review_steps_complete', 'You have finished the required steps for this peer review.') %>
|
||||
</div>
|
||||
<div class="assessment_request_incomplete_message" style="<%= hidden if @assessment_request.completed? %>">
|
||||
<% if @assessment_request && @assessment_request.rubric_association %>
|
||||
<% if @assessment_request && @assessment_request.active_rubric_association? %>
|
||||
<%= mt('peer_review_not_done_rubric', "**This peer review is not finished yet.** For it to be considered finished, you need to leave at least one comment and fill out the rubric form to the right.")
|
||||
%>
|
||||
<% else %>
|
||||
|
@ -245,7 +245,7 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if @assignment.rubric_association %>
|
||||
<% if @assignment.active_rubric_association? %>
|
||||
<% @visible_rubric_assessments ||= [] %>
|
||||
<div id="rubric_holder" style="position: absolute; <%= direction('right') %>: 0px; padding: 5px; background-color: #fff; border: 1px solid #aaa; border-<%= direction('right') %>-width: 0; display: none;">
|
||||
<a
|
||||
|
|
|
@ -267,7 +267,7 @@ module Api::V1::Assignment
|
|||
end
|
||||
|
||||
unless opts[:exclude_response_fields].include?('rubric')
|
||||
if assignment.rubric_association
|
||||
if assignment.active_rubric_association?
|
||||
hash['use_rubric_for_grading'] = !!assignment.rubric_association.use_for_grading
|
||||
if assignment.rubric_association.rubric
|
||||
hash['free_form_criterion_comments'] = !!assignment.rubric_association.rubric.free_form_criterion_comments
|
||||
|
|
|
@ -44,7 +44,7 @@ module Api::V1::RubricAssessment
|
|||
json_attributes = API_ALLOWED_RUBRIC_ASSESSMENT_OUTPUT_FIELDS
|
||||
hash = api_json(rubric_assessment, user, session, json_attributes)
|
||||
hash['data'] = rubric_assessment.data if opts[:style] == "full"
|
||||
if opts[:style] == "full" && rubric_assessment.rubric_association.present?
|
||||
if opts[:style] == "full" && rubric_assessment.active_rubric_association?
|
||||
hash['rubric_association'] = rubric_assessment.rubric_association.as_json['rubric_association']
|
||||
end
|
||||
hash['comments'] = rubric_assessment.data.map{|rad| rad[:comments]} if opts[:style] == "comments_only"
|
||||
|
|
|
@ -207,7 +207,7 @@ module CC
|
|||
end
|
||||
node.workflow_state assignment.workflow_state
|
||||
if assignment.rubric
|
||||
assoc = assignment.rubric_association
|
||||
assoc = assignment.active_rubric_association? ? assignment.rubric_association : nil
|
||||
node.rubric_identifierref key_generator.create_key(assignment.rubric)
|
||||
if assignment.rubric && assignment.rubric.context != assignment.context
|
||||
node.rubric_external_identifier assignment.rubric.id
|
||||
|
|
|
@ -21,7 +21,8 @@ module RubricContext
|
|||
def self.included(klass)
|
||||
if klass < ActiveRecord::Base
|
||||
klass.has_many :rubrics, :as => :context, :inverse_of => :context
|
||||
klass.has_many :rubric_associations, -> { preload(:rubric) }, as: :context, inverse_of: :context, dependent: :destroy
|
||||
klass.has_many :rubric_associations_with_deleted, -> { preload(:rubric) }, as: :context, inverse_of: :context, class_name: "RubricAssociation"
|
||||
klass.has_many :rubric_associations, -> { where(workflow_state: "active").preload(:rubric) }, as: :context, inverse_of: :context, dependent: :destroy
|
||||
klass.send :include, InstanceMethods
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3741,46 +3741,73 @@ describe 'Submissions API', type: :request do
|
|||
expect(json['grade']).to eq grade
|
||||
end
|
||||
|
||||
it "allows posting a rubric assessment" do
|
||||
student = user_factory(active_all: true)
|
||||
course_with_teacher(:active_all => true)
|
||||
@course.enroll_student(student).accept!
|
||||
a1 = @course.assignments.create!(:title => 'assignment1', :grading_type => 'points', :points_possible => 12)
|
||||
rubric = rubric_model(:user => @user, :context => @course,
|
||||
:data => larger_rubric_data)
|
||||
a1.create_rubric_association(:rubric => rubric, :purpose => 'grading', :use_for_grading => true, :context => @course)
|
||||
context "posting rubric assessments" do
|
||||
before(:once) do
|
||||
@student = user_factory(active_all: true)
|
||||
course_with_teacher(:active_all => true)
|
||||
@course.enroll_student(@student).accept!
|
||||
@a1 = @course.assignments.create!(:title => 'assignment1', :grading_type => 'points', :points_possible => 12)
|
||||
rubric = rubric_model(:user => @user, :context => @course, :data => larger_rubric_data)
|
||||
@rubric_association = @a1.create_rubric_association(
|
||||
:rubric => rubric,
|
||||
:purpose => 'grading',
|
||||
:use_for_grading => true,
|
||||
:context => @course
|
||||
)
|
||||
end
|
||||
|
||||
api_call(:put,
|
||||
"/api/v1/courses/#{@course.id}/assignments/#{a1.id}/submissions/#{student.id}.json",
|
||||
{ :controller => 'submissions_api', :action => 'update',
|
||||
:format => 'json', :course_id => @course.id.to_s,
|
||||
:assignment_id => a1.id.to_s, :user_id => student.id.to_s },
|
||||
{ :rubric_assessment =>
|
||||
{ :crit1 => { :points => 7 },
|
||||
:crit2 => { :points => 2, :comments => 'Rock on' } } })
|
||||
it "allows posting a rubric assessment" do
|
||||
api_call(
|
||||
:put,
|
||||
"/api/v1/courses/#{@course.id}/assignments/#{@a1.id}/submissions/#{@student.id}.json",
|
||||
{ :controller => 'submissions_api', :action => 'update',
|
||||
:format => 'json', :course_id => @course.id.to_s,
|
||||
:assignment_id => @a1.id.to_s, :user_id => @student.id.to_s },
|
||||
{ :rubric_assessment =>
|
||||
{ :crit1 => { :points => 7 },
|
||||
:crit2 => { :points => 2, :comments => 'Rock on' } } }
|
||||
)
|
||||
|
||||
expect(Submission.count).to eq 1
|
||||
@submission = Submission.first
|
||||
expect(@submission.user_id).to eq student.id
|
||||
expect(@submission.score).to eq 9
|
||||
expect(@submission.rubric_assessment).not_to be_nil
|
||||
expect(@submission.rubric_assessment.data).to eq(
|
||||
[{:description=>"B",
|
||||
:criterion_id=>"crit1",
|
||||
:comments_enabled=>true,
|
||||
:points=>7,
|
||||
:learning_outcome_id=>nil,
|
||||
:id=>"rat2",
|
||||
:comments=>nil},
|
||||
{:description=>"Pass",
|
||||
:criterion_id=>"crit2",
|
||||
:comments_enabled=>true,
|
||||
:points=>2,
|
||||
:learning_outcome_id=>nil,
|
||||
:id=>"rat1",
|
||||
:comments=>"Rock on",
|
||||
:comments_html=>"Rock on"}]
|
||||
)
|
||||
expect(Submission.count).to eq 1
|
||||
@submission = Submission.first
|
||||
expect(@submission.user_id).to eq @student.id
|
||||
expect(@submission.score).to eq 9
|
||||
expect(@submission.rubric_assessment).not_to be_nil
|
||||
expect(@submission.rubric_assessment.data).to eq(
|
||||
[{:description=>"B",
|
||||
:criterion_id=>"crit1",
|
||||
:comments_enabled=>true,
|
||||
:points=>7,
|
||||
:learning_outcome_id=>nil,
|
||||
:id=>"rat2",
|
||||
:comments=>nil},
|
||||
{:description=>"Pass",
|
||||
:criterion_id=>"crit2",
|
||||
:comments_enabled=>true,
|
||||
:points=>2,
|
||||
:learning_outcome_id=>nil,
|
||||
:id=>"rat1",
|
||||
:comments=>"Rock on",
|
||||
:comments_html=>"Rock on"}]
|
||||
)
|
||||
end
|
||||
|
||||
it "does not allow posting a rubric assessment when the rubric association is soft-deleted" do
|
||||
@rubric_association.destroy
|
||||
api_call(
|
||||
:put,
|
||||
"/api/v1/courses/#{@course.id}/assignments/#{@a1.id}/submissions/#{@student.id}.json",
|
||||
{ :controller => 'submissions_api', :action => 'update',
|
||||
:format => 'json', :course_id => @course.id.to_s,
|
||||
:assignment_id => @a1.id.to_s, :user_id => @student.id.to_s },
|
||||
{ :rubric_assessment =>
|
||||
{ :crit1 => { :points => 7 },
|
||||
:crit2 => { :points => 2, :comments => 'Rock on' } } }
|
||||
)
|
||||
|
||||
@submission = Submission.first
|
||||
expect(@submission.rubric_assessment).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it "validates the rubric assessment" do
|
||||
|
|
|
@ -439,6 +439,39 @@ describe ContextController do
|
|||
expect(response).to be_successful
|
||||
expect(assigns[:deleted_items]).to include(g1)
|
||||
end
|
||||
|
||||
describe 'Rubric Associations' do
|
||||
before(:once) do
|
||||
assignment = assignment_model(course: @course)
|
||||
rubric = rubric_model({
|
||||
context: @course,
|
||||
title: 'Test Rubric',
|
||||
data: [{
|
||||
description: 'Some criterion',
|
||||
points: 10,
|
||||
id: 'crit1',
|
||||
ignore_for_scoring: true,
|
||||
ratings: [
|
||||
{ description: 'Good', points: 10, id: 'rat1', criterion_id: 'crit1' }
|
||||
]
|
||||
}]
|
||||
})
|
||||
@association = rubric.associate_with(assignment, @course, purpose: 'grading')
|
||||
end
|
||||
|
||||
it 'shows deleted rubric associations' do
|
||||
@association.destroy
|
||||
user_session(@teacher)
|
||||
get :undelete_index, params: { course_id: @course.id }
|
||||
expect(assigns[:deleted_items]).to include @association
|
||||
end
|
||||
|
||||
it 'does not show active rubric associations' do
|
||||
user_session(@teacher)
|
||||
get :undelete_index, params: { course_id: @course.id }
|
||||
expect(assigns[:deleted_items]).not_to include @association
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST 'undelete_item'" do
|
||||
|
@ -500,6 +533,30 @@ describe ContextController do
|
|||
post :undelete_item, params: { course_id: @course.id, asset_string: @attachment.asset_string }
|
||||
expect(@attachment.reload).not_to be_deleted
|
||||
end
|
||||
|
||||
it 'allows undeleting rubric associations' do
|
||||
assignment = assignment_model(course: @course)
|
||||
rubric = rubric_model({
|
||||
context: @course,
|
||||
title: 'Test Rubric',
|
||||
data: [{
|
||||
description: 'Some criterion',
|
||||
points: 10,
|
||||
id: 'crit1',
|
||||
ignore_for_scoring: true,
|
||||
ratings: [
|
||||
{ description: 'Good', points: 10, id: 'rat1', criterion_id: 'crit1' }
|
||||
]
|
||||
}]
|
||||
})
|
||||
association = rubric.associate_with(assignment, @course, purpose: 'grading')
|
||||
puts "association id is: #{association.id}"
|
||||
association.destroy
|
||||
|
||||
user_session(@teacher)
|
||||
post :undelete_item, params: { course_id: @course.id, asset_string: association.asset_string }
|
||||
expect(association.reload).not_to be_deleted
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET 'roster_user_usage'" do
|
||||
|
|
|
@ -279,16 +279,18 @@ describe RubricAssociationsController do
|
|||
delete 'destroy', params: {:course_id => @course.id, :id => @rubric_association.id}
|
||||
assert_unauthorized
|
||||
end
|
||||
|
||||
it "should delete the rubric if deletable" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
rubric_association_model(:user => @user, :context => @course)
|
||||
delete 'destroy', params: {:course_id => @course.id, :id => @rubric_association.id}
|
||||
expect(response).to be_successful
|
||||
expect(assigns[:association]).not_to be_nil
|
||||
expect(assigns[:association]).to be_frozen
|
||||
expect(assigns[:association]).to be_deleted
|
||||
expect(assigns[:rubric]).not_to be_nil
|
||||
expect(assigns[:rubric]).to be_deleted
|
||||
end
|
||||
|
||||
it "should_not delete the rubric if still created at the context level instead of the assignment level" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
rubric_association_model(:user => @user, :context => @course)
|
||||
|
@ -299,8 +301,9 @@ describe RubricAssociationsController do
|
|||
expect(assigns[:rubric]).not_to be_deleted
|
||||
expect(assigns[:rubric]).not_to be_frozen
|
||||
expect(assigns[:association]).not_to be_nil
|
||||
expect(assigns[:association]).to be_frozen
|
||||
expect(assigns[:association]).to be_deleted
|
||||
end
|
||||
|
||||
it "should delete only the association if the rubric is not deletable" do
|
||||
rubric_association_model
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
|
@ -313,7 +316,7 @@ describe RubricAssociationsController do
|
|||
expect(assigns[:rubric]).not_to be_deleted
|
||||
expect(assigns[:rubric]).not_to be_frozen
|
||||
expect(assigns[:association]).not_to be_nil
|
||||
expect(assigns[:association]).to be_frozen
|
||||
expect(assigns[:association]).to be_deleted
|
||||
end
|
||||
|
||||
it "should remove aligments links" do
|
||||
|
|
|
@ -632,6 +632,7 @@ describe RubricsController do
|
|||
delete 'destroy', params: {:course_id => @course.id, :id => @rubric.id}
|
||||
assert_unauthorized
|
||||
end
|
||||
|
||||
it "should delete the rubric" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
rubric_association_model(:user => @user, :context => @course)
|
||||
|
@ -639,6 +640,17 @@ describe RubricsController do
|
|||
expect(response).to be_successful
|
||||
expect(assigns[:rubric]).to be_deleted
|
||||
end
|
||||
|
||||
# This should probably be fixed, but I want to document how this currently behaves.
|
||||
it "returns a 500 if the rubric cannot be found" do
|
||||
course_with_teacher_logged_in(active_all: true)
|
||||
association = rubric_association_model(user: @user, context: @course)
|
||||
association.destroy
|
||||
delete 'destroy', params: { course_id: @course.id, id: @rubric.id }
|
||||
|
||||
expect(response.status).to eq 500
|
||||
end
|
||||
|
||||
it "should delete the rubric if the rubric is only associated with a course" do
|
||||
course_with_teacher_logged_in :active_all => true
|
||||
Account.site_admin.account_users.create!(user: @user)
|
||||
|
@ -654,6 +666,7 @@ describe RubricsController do
|
|||
@rubric.reload
|
||||
expect(@rubric.deleted?).to be_truthy
|
||||
end
|
||||
|
||||
it "should delete the rubric association even if the rubric doesn't belong to a course" do
|
||||
course_with_teacher_logged_in :active_all => true
|
||||
Account.site_admin.account_users.create!(user: @user)
|
||||
|
|
|
@ -166,4 +166,27 @@ describe AssessmentRequest do
|
|||
expect(@ignore.reload).to eq @ignore
|
||||
end
|
||||
end
|
||||
|
||||
describe "#active_rubric_association?" do
|
||||
before(:once) do
|
||||
rubric_model
|
||||
@association = @rubric.associate_with(@assignment, @course, purpose: 'grading', use_for_grading: true)
|
||||
@request.rubric_association = @association
|
||||
@request.save!
|
||||
end
|
||||
|
||||
it "returns false if there is no rubric association" do
|
||||
@request.update!(rubric_association: nil)
|
||||
expect(@request).not_to be_active_rubric_association
|
||||
end
|
||||
|
||||
it "returns false if the rubric association is soft-deleted" do
|
||||
@association.destroy
|
||||
expect(@request).not_to be_active_rubric_association
|
||||
end
|
||||
|
||||
it "returns true if the rubric association exists and is active" do
|
||||
expect(@request).to be_active_rubric_association
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4519,6 +4519,50 @@ describe Assignment do
|
|||
hash = @assignment.as_json
|
||||
expect(hash["assignment"]["group_category"]).to eq "Something"
|
||||
end
|
||||
|
||||
context "when including rubric_association" do
|
||||
before(:once) do
|
||||
@rubric = Rubric.create!(user: @teacher, context: @course)
|
||||
end
|
||||
|
||||
context "when including root" do
|
||||
let(:json) { @assignment.as_json(include: [:rubric_association])[:assignment] }
|
||||
|
||||
it "does not include a rubric_association when there is no rubric_association" do
|
||||
expect(json).not_to have_key "rubric_association"
|
||||
end
|
||||
|
||||
it "does not include a rubric_association when there is a rubric_association but it is soft-deleted" do
|
||||
rubric_association = @rubric.associate_with(@assignment, @course, purpose: 'grading')
|
||||
rubric_association.destroy
|
||||
expect(json).not_to have_key "rubric_association"
|
||||
end
|
||||
|
||||
it "includes a rubric_association when there is a rubric_association and it is not deleted" do
|
||||
rubric_association = @rubric.associate_with(@assignment, @course, purpose: 'grading')
|
||||
expect(json.dig("rubric_association", "rubric_association", "id")).to eq rubric_association.id
|
||||
end
|
||||
end
|
||||
|
||||
context "when excluding root" do
|
||||
let(:json) { @assignment.as_json(include: [:rubric_association], include_root: false) }
|
||||
|
||||
it "does not include a rubric_association when there is no rubric_association" do
|
||||
expect(json).not_to have_key "rubric_association"
|
||||
end
|
||||
|
||||
it "does not include a rubric_association when there is a rubric_association but it is soft-deleted" do
|
||||
rubric_association = @rubric.associate_with(@assignment, @course, purpose: 'grading')
|
||||
rubric_association.destroy
|
||||
expect(json).not_to have_key "rubric_association"
|
||||
end
|
||||
|
||||
it "includes a rubric_association when there is a rubric_association and it is not deleted" do
|
||||
rubric_association = @rubric.associate_with(@assignment, @course, purpose: 'grading')
|
||||
expect(json.dig("rubric_association", "id")).to eq rubric_association.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "ical" do
|
||||
|
@ -9396,6 +9440,37 @@ describe Assignment do
|
|||
end
|
||||
end
|
||||
|
||||
describe "active_rubric_association?" do
|
||||
before(:once) do
|
||||
@assignment = @course.assignments.create!(assignment_valid_attributes)
|
||||
rubric = @course.rubrics.create! { |r| r.user = @teacher }
|
||||
rubric_association_params = HashWithIndifferentAccess.new({
|
||||
hide_score_total: "0",
|
||||
purpose: "grading",
|
||||
skip_updating_points_possible: false,
|
||||
update_if_existing: true,
|
||||
use_for_grading: "1",
|
||||
association_object: @assignment
|
||||
})
|
||||
@association = RubricAssociation.generate(@teacher, rubric, @course, rubric_association_params)
|
||||
@assignment.update!(rubric_association: @association)
|
||||
end
|
||||
|
||||
it "returns false if there is no rubric association" do
|
||||
@association.destroy_permanently!
|
||||
expect(@assignment.reload).not_to be_active_rubric_association
|
||||
end
|
||||
|
||||
it "returns false if the rubric association is soft-deleted" do
|
||||
@association.destroy
|
||||
expect(@assignment.reload).not_to be_active_rubric_association
|
||||
end
|
||||
|
||||
it "returns true if the rubric association exists and is active" do
|
||||
expect(@assignment).to be_active_rubric_association
|
||||
end
|
||||
end
|
||||
|
||||
def setup_assignment_with_group
|
||||
assignment_model(:group_category => "Study Groups", :course => @course)
|
||||
@group = @a.context.groups.create!(:name => "Study Group 1", :group_category => @a.group_category)
|
||||
|
|
|
@ -286,6 +286,16 @@ describe Context do
|
|||
}])
|
||||
end
|
||||
|
||||
it 'excludes rubrics associated via soft-deleted rubric associations' do
|
||||
c1 = Course.create!(:name => 'c1')
|
||||
r = Rubric.create!(context: c1, title: 'testing')
|
||||
user = user_factory(:active_all => true)
|
||||
association = RubricAssociation.create!(context: c1, rubric: r, purpose: :bookmark, association_object: c1)
|
||||
association.destroy
|
||||
c1.enroll_user(user, "TeacherEnrollment", :enrollment_state => "active")
|
||||
expect(c1.rubric_contexts(user)).to be_empty
|
||||
end
|
||||
|
||||
it 'returns contexts in alphabetically sorted order' do
|
||||
great_grandparent = Account.default
|
||||
grandparent = Account.create!(name: 'AAA', parent_account: great_grandparent)
|
||||
|
|
|
@ -384,6 +384,30 @@ describe ModeratedGrading::ProvisionalGrade do
|
|||
expect(real_assessment.data).to eq provisional_assessment.data
|
||||
end
|
||||
|
||||
it "does not publish rubric assessments when the rubric association is soft-deleted" do
|
||||
outcome_with_rubric(course: course)
|
||||
association = @rubric.associate_with(assignment, course, purpose: 'grading', use_for_grading: true)
|
||||
association.destroy
|
||||
|
||||
submission = assignment.submit_homework(student, submission_type: 'online_text_entry', body: 'hallo')
|
||||
provisional_grade = submission.find_or_create_provisional_grade!(scorer, score: 1)
|
||||
|
||||
association.assess(
|
||||
user: student,
|
||||
assessor: scorer,
|
||||
artifact: provisional_grade,
|
||||
assessment: {
|
||||
assessment_type: 'grading',
|
||||
:"criterion_#{@rubric.criteria_object.first.id}" => {
|
||||
points: 3,
|
||||
comments: "good 4 u"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
expect(submission.rubric_assessments.first).to be_nil
|
||||
end
|
||||
|
||||
it "does not error when a rubric has been deleted after an assessment took place" do
|
||||
outcome_with_rubric(course: course)
|
||||
association = @rubric.associate_with(assignment, course, purpose: 'grading', use_for_grading: true)
|
||||
|
|
|
@ -34,6 +34,37 @@ describe RubricAssessment do
|
|||
@association = @rubric.associate_with(@assignment, @course, :purpose => 'grading', :use_for_grading => true)
|
||||
end
|
||||
|
||||
describe "active_rubric_association?" do
|
||||
before(:once) do
|
||||
@assessment = @association.assess({
|
||||
:user => @student,
|
||||
:assessor => @teacher,
|
||||
:artifact => @assignment.find_or_create_submission(@student),
|
||||
:assessment => {
|
||||
:assessment_type => 'grading',
|
||||
:criterion_crit1 => {
|
||||
:points => 5,
|
||||
:comments => "comments",
|
||||
}
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
it "returns false if there is no rubric association" do
|
||||
@assessment.update!(rubric_association: nil)
|
||||
expect(@assessment).not_to be_active_rubric_association
|
||||
end
|
||||
|
||||
it "returns false if the rubric association is soft-deleted" do
|
||||
@association.destroy
|
||||
expect(@assessment).not_to be_active_rubric_association
|
||||
end
|
||||
|
||||
it "returns true if the rubric association exists and is active" do
|
||||
expect(@assessment).to be_active_rubric_association
|
||||
end
|
||||
end
|
||||
|
||||
it { is_expected.to have_many(:learning_outcome_results).dependent(:destroy) }
|
||||
|
||||
it "should htmlify the rating comments" do
|
||||
|
|
|
@ -442,7 +442,30 @@ describe RubricAssociation do
|
|||
end
|
||||
|
||||
describe "workflow_state" do
|
||||
it "is set to active by default" do
|
||||
before(:once) do
|
||||
@course = Course.create!
|
||||
@rubric = @course.rubrics.create!
|
||||
@association = RubricAssociation.create!(
|
||||
rubric: @rubric,
|
||||
association_object: @course,
|
||||
context: @course,
|
||||
purpose: "bookmark"
|
||||
)
|
||||
end
|
||||
|
||||
it "is set to 'active' by default" do
|
||||
expect(@association).to be_active
|
||||
end
|
||||
|
||||
it "gets set to 'deleted' when soft-deleted" do
|
||||
expect { @association.destroy }.to change {
|
||||
@association.workflow_state
|
||||
}.from("active").to("deleted")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#restore" do
|
||||
it "sets the workflow_state to 'active'" do
|
||||
course = Course.create!
|
||||
rubric = course.rubrics.create!
|
||||
association = RubricAssociation.create!(
|
||||
|
@ -451,8 +474,8 @@ describe RubricAssociation do
|
|||
context: course,
|
||||
purpose: "bookmark"
|
||||
)
|
||||
|
||||
expect(association).to be_active
|
||||
association.destroy
|
||||
expect { association.restore }.to change { association.workflow_state }.from("deleted").to("active")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -237,7 +237,7 @@ describe Rubric do
|
|||
a2 = rubric.associate_with(assignment2, @course, :purpose => 'grading')
|
||||
|
||||
assignment2.destroy
|
||||
expect(RubricAssociation.where(:id => a2).first).to be_nil # association should be destroyed
|
||||
expect(RubricAssociation.where(:id => a2).first).to be_deleted
|
||||
|
||||
rubric.reload
|
||||
expect(rubric).to be_active
|
||||
|
|
|
@ -210,6 +210,31 @@ describe SpeedGrader::Assignment do
|
|||
).to eq comment.provisional_grade_id.to_s
|
||||
end
|
||||
|
||||
context "rubric association" do
|
||||
before(:once) do
|
||||
@assignment = assignment_model(course: @course)
|
||||
end
|
||||
|
||||
let(:json) { SpeedGrader::Assignment.new(@assignment, @user).json }
|
||||
|
||||
it "does not include rubric_association when one does not exist" do
|
||||
expect(json).not_to have_key "rubric_association"
|
||||
end
|
||||
|
||||
it "does not include rubric_association when one exists but it is not active" do
|
||||
rubric = rubric_model
|
||||
association = rubric.associate_with(@assignment, @course, purpose: "grading", use_for_grading: true)
|
||||
association.destroy
|
||||
expect(json).not_to have_key "rubric_association"
|
||||
end
|
||||
|
||||
it "includes a rubric_association when one exists and is active" do
|
||||
rubric = rubric_model
|
||||
association = rubric.associate_with(@assignment, @course, purpose: "grading", use_for_grading: true)
|
||||
expect(json.dig("rubric_association", "id")).to eq association.id.to_s
|
||||
end
|
||||
end
|
||||
|
||||
context "students and active course sections" do
|
||||
before(:once) do
|
||||
@course = course_factory(active_course: true)
|
||||
|
|
Loading…
Reference in New Issue