Improve calculation of effective due dates for students
closes CNVS-33110 closes CNVS-32641 Test plan: * regression test changing group weighting schemes * regression test moving assignments with any due date in a closed grading period (this could be from an override or the "Everyone Else" due date) into another assignment group * An assignment that is "locked" (that is, one with a due date that falls in a closed grading period) should not be movable. Change-Id: I9bb87dcd7c0b83535a31d079c669b86c5eee55b0 Reviewed-on: https://gerrit.instructure.com/94939 Reviewed-by: Jeremy Neander <jneander@instructure.com> Reviewed-by: Keith T. Garner <kgarner@instructure.com> Tested-by: Jenkins QA-Review: KC Naegle <knaegle@instructure.com> Product-Review: Keith T. Garner <kgarner@instructure.com>
This commit is contained in:
parent
b6f60b4a30
commit
f7814b9c53
|
@ -150,7 +150,6 @@ class AssignmentGroupsApiController < ApplicationController
|
|||
def can_update_assignment_group?(assignment_group)
|
||||
return true if @context.account_membership_allows(@current_user)
|
||||
return true unless assignment_group.group_weight_changed? || assignment_group.rules_changed?
|
||||
return true unless @context.feature_enabled?(:multiple_grading_periods)
|
||||
!assignment_group.has_assignment_due_in_closed_grading_period?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -151,7 +151,7 @@ class AssignmentGroupsController < ApplicationController
|
|||
if authorized_action(@group, @current_user, :update)
|
||||
order = params[:order].split(',').map{|id| id.to_i }
|
||||
group_ids = ([@group.id] + (order.empty? ? [] : @context.assignments.where(id: order).uniq.except(:order).pluck(:assignment_group_id)))
|
||||
assignments = @context.active_assignments.where(id: order).preload(:active_assignment_overrides)
|
||||
assignments = @context.active_assignments.where(id: order)
|
||||
|
||||
return render_unauthorized_action unless can_reorder_assignments?(assignments, @group)
|
||||
|
||||
|
@ -386,12 +386,15 @@ class AssignmentGroupsController < ApplicationController
|
|||
end
|
||||
|
||||
def can_reorder_assignments?(assignments, group)
|
||||
return true if @context.account_membership_allows(@current_user)
|
||||
return true unless @context.feature_enabled?(:multiple_grading_periods)
|
||||
periods = GradingPeriod.for(@context)
|
||||
return true if @context.account_membership_allows(@current_user)
|
||||
|
||||
effective_due_dates = EffectiveDueDates.for_course(@context, assignments)
|
||||
assignments.none? do |assignment|
|
||||
# if the assignment is being moved into a different group and it's in
|
||||
# a closed period, do not allow it to be moved.
|
||||
assignment.assignment_group_id != group.id &&
|
||||
assignment.due_for_any_student_in_closed_grading_period?(periods)
|
||||
effective_due_dates.in_closed_grading_period?(assignment.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2686,9 +2686,6 @@ class CoursesController < ApplicationController
|
|||
def can_change_group_weighting_scheme?
|
||||
return true unless @course.feature_enabled?(:multiple_grading_periods)
|
||||
return true if @course.account_membership_allows(@current_user)
|
||||
periods = GradingPeriod.for(@course)
|
||||
@course.active_assignments.preload(:active_assignment_overrides).none? do |assignment|
|
||||
assignment.due_for_any_student_in_closed_grading_period?(periods)
|
||||
end
|
||||
!@course.any_assignment_in_closed_grading_period?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1055,7 +1055,7 @@ class Assignment < ActiveRecord::Base
|
|||
given do |user, session|
|
||||
self.context.grants_right?(user, session, :manage_assignments) &&
|
||||
(self.context.account_membership_allows(user) ||
|
||||
!due_for_any_student_in_closed_grading_period?)
|
||||
!in_closed_grading_period?)
|
||||
end
|
||||
can :delete
|
||||
end
|
||||
|
@ -2173,15 +2173,9 @@ class Assignment < ActiveRecord::Base
|
|||
s.excused?
|
||||
end
|
||||
|
||||
def due_for_any_student_in_closed_grading_period?(periods = nil)
|
||||
periods ||= GradingPeriod.for(self.course)
|
||||
due_in_closed_period = !self.only_visible_to_overrides &&
|
||||
GradingPeriodHelper.date_in_closed_grading_period?(self.due_date, periods)
|
||||
due_in_closed_period ||= self.active_assignment_overrides.any? do |override|
|
||||
GradingPeriodHelper.date_in_closed_grading_period?(override.due_at, periods)
|
||||
end
|
||||
|
||||
due_in_closed_period
|
||||
def in_closed_grading_period?
|
||||
@effective_due_dates ||= EffectiveDueDates.for_course(context, id)
|
||||
@effective_due_dates.in_closed_grading_period?(id)
|
||||
end
|
||||
|
||||
# simply versioned models are always marked new_record, but for our purposes
|
||||
|
|
|
@ -199,14 +199,14 @@ class AssignmentGroup < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def has_assignment_due_in_closed_grading_period?
|
||||
published_assignments = context.assignments.published.where(assignment_group_id: self).
|
||||
preload(:active_assignment_overrides, :context)
|
||||
periods = GradingPeriod.for(self.course)
|
||||
published_assignments.any? do |assignment|
|
||||
assignment.due_for_any_student_in_closed_grading_period?(periods)
|
||||
end
|
||||
effective_due_dates.any_in_closed_grading_period?
|
||||
end
|
||||
|
||||
def effective_due_dates
|
||||
@effective_due_dates ||= EffectiveDueDates.for_course(context, published_assignments)
|
||||
end
|
||||
private :effective_due_dates
|
||||
|
||||
def visible_assignments(user, includes=[])
|
||||
self.class.visible_assignments(user, self.context, [self], includes)
|
||||
end
|
||||
|
|
|
@ -3098,4 +3098,17 @@ class Course < ActiveRecord::Base
|
|||
def apply_nickname_for!(user)
|
||||
@nickname = nickname_for(user, nil)
|
||||
end
|
||||
|
||||
# delegate but with #assignment_in_closed_grading_period?
|
||||
delegate :in_closed_grading_period?, to: :effective_due_dates, prefix: 'assignment'
|
||||
|
||||
def any_assignment_in_closed_grading_period?
|
||||
# delegate but with a slightly different name
|
||||
effective_due_dates.any_in_closed_grading_period?
|
||||
end
|
||||
|
||||
def effective_due_dates
|
||||
@effective_due_dates ||= EffectiveDueDates.for_course(self)
|
||||
end
|
||||
private :effective_due_dates
|
||||
end
|
||||
|
|
|
@ -60,7 +60,21 @@ module Api::V1::Assignment
|
|||
}.freeze
|
||||
|
||||
def assignments_json(assignments, user, session, opts = {})
|
||||
assignments.map{ |assignment| assignment_json(assignment, user, session, opts) }
|
||||
# check if all assignments being serialized belong to the same course
|
||||
contexts = assignments.map {|a| [a.context_id, a.context_type] }.uniq
|
||||
if contexts.length == 1
|
||||
# if so, calculate their effective due dates in one go, rather than individually
|
||||
opts.merge!({exclude_has_due_date_in_closed_grading_period: true})
|
||||
due_dates = EffectiveDueDates.for_course(assignments.first.context, assignments)
|
||||
end
|
||||
|
||||
assignments.map do |assignment|
|
||||
json = assignment_json(assignment, user, session, opts)
|
||||
unless json.key? 'has_due_date_in_closed_grading_period'
|
||||
json['has_due_date_in_closed_grading_period'] = due_dates.in_closed_grading_period?(assignment)
|
||||
end
|
||||
json
|
||||
end
|
||||
end
|
||||
|
||||
def assignment_json(assignment, user, session, opts = {})
|
||||
|
@ -89,8 +103,9 @@ module Api::V1::Assignment
|
|||
hash['name'] = assignment.title
|
||||
hash['submission_types'] = assignment.submission_types_array
|
||||
hash['has_submitted_submissions'] = assignment.has_submitted_submissions?
|
||||
hash['has_due_date_in_closed_grading_period'] =
|
||||
assignment.due_for_any_student_in_closed_grading_period?
|
||||
unless opts[:exclude_has_due_date_in_closed_grading_period]
|
||||
hash['has_due_date_in_closed_grading_period'] = assignment.in_closed_grading_period?
|
||||
end
|
||||
|
||||
if !opts[:overrides].blank?
|
||||
hash['overrides'] = assignment_overrides_json(opts[:overrides], user)
|
||||
|
|
|
@ -50,9 +50,6 @@ module Api::V1::AssignmentGroup
|
|||
Assignment.preload_context_module_tags(assignments) # running this again is fine
|
||||
end
|
||||
|
||||
hash['has_assignment_due_in_closed_grading_period'] =
|
||||
group.has_assignment_due_in_closed_grading_period?
|
||||
|
||||
hash['assignments'] = assignments.map { |a|
|
||||
overrides = opts[:overrides].select{|override| override.assignment_id == a.id } unless opts[:overrides].nil?
|
||||
a.context = group.context
|
||||
|
@ -71,6 +68,9 @@ module Api::V1::AssignmentGroup
|
|||
submission: includes.include?('submission') ? opts[:submissions][a.id] : nil
|
||||
)
|
||||
}
|
||||
|
||||
hash['has_assignment_due_in_closed_grading_period'] =
|
||||
hash["assignments"].any?{ |assn| assn["has_due_date_in_closed_grading_period"] }
|
||||
end
|
||||
|
||||
hash
|
||||
|
|
|
@ -0,0 +1,314 @@
|
|||
class EffectiveDueDates
|
||||
attr_reader :context
|
||||
|
||||
# This class will find the effective due dates for all students
|
||||
# and assignments in a course. You can pass it a list of assignments,
|
||||
# assignment id's, or a relation, but they MUST be from the same course.
|
||||
# Also cross-shard id's won't work.
|
||||
def initialize(context, *assignment_collection)
|
||||
raise "Context must be a course" unless context.is_a?(Course)
|
||||
raise "Context must have an id" unless context.id
|
||||
|
||||
@context = context
|
||||
@assignments = assignment_collection
|
||||
end
|
||||
|
||||
# EffectiveDueDates.for_course(...) just reads more
|
||||
# like Canvas code than EffectiveDueDates.new(...)
|
||||
singleton_class.send :alias_method, :for_course, :new
|
||||
|
||||
# This beauty of a method brings together assignment overrides,
|
||||
# due dates, multiple grading periods, course/group enrollments,
|
||||
# etc to calculate each student's effective due date and whether
|
||||
# or not that due date is in a closed grading period. If a
|
||||
# student is not included in this hash, that student cannot see
|
||||
# this assignment. The format of the returned hash is:
|
||||
# {
|
||||
# assignment_id => {
|
||||
# student_id => {
|
||||
# due_at: some date or nil (if assigned but no explicit due date),
|
||||
# grading_period_id: id or nil,
|
||||
# in_closed_grading_period: true or false
|
||||
# }, ...
|
||||
# }, ...
|
||||
# }
|
||||
def to_hash
|
||||
return @hash unless @hash.nil?
|
||||
|
||||
# default to all active assignments on this course if nothing is passed
|
||||
assignment_collection = @assignments.empty? ? [@context.active_assignments] : @assignments
|
||||
|
||||
if assignment_collection.length == 1 &&
|
||||
assignment_collection.first.respond_to?(:to_sql) &&
|
||||
!assignment_collection.first.loaded?
|
||||
# it's a relation, let's not load it unnecessarily out here
|
||||
assignment_collection = assignment_collection.first.except(:order).select(:id).to_sql
|
||||
else
|
||||
# otherwise, map through the array as necessary to get id's
|
||||
assignment_collection.flatten!
|
||||
assignment_collection.map!{ |assignment| assignment.try(:id) } if assignment_collection.first.is_a?(Assignment)
|
||||
assignment_collection.compact!
|
||||
return {} if assignment_collection.empty?
|
||||
assignment_collection = assignment_collection.join(',')
|
||||
end
|
||||
|
||||
@hash = ActiveRecord::Base.connection.select_all("
|
||||
-- fetch the assignment itself
|
||||
WITH models AS (
|
||||
SELECT *
|
||||
FROM #{Assignment.quoted_table_name}
|
||||
WHERE
|
||||
id IN (#{assignment_collection}) AND
|
||||
workflow_state <> 'deleted' AND
|
||||
context_id = #{@context.id} AND context_type = 'Course'
|
||||
),
|
||||
|
||||
-- fetch all overrides for this assignment
|
||||
overrides AS (
|
||||
SELECT
|
||||
o.*
|
||||
FROM
|
||||
models a
|
||||
INNER JOIN #{AssignmentOverride.quoted_table_name} o ON o.assignment_id = a.id
|
||||
WHERE
|
||||
o.workflow_state = 'active' AND o.due_at_overridden
|
||||
),
|
||||
|
||||
-- fetch all students affected by adhoc overrides
|
||||
override_adhoc_students AS (
|
||||
SELECT
|
||||
os.user_id AS student_id,
|
||||
o.assignment_id,
|
||||
o.id AS override_id,
|
||||
o.due_at,
|
||||
o.set_type AS override_type,
|
||||
1 AS priority
|
||||
FROM
|
||||
overrides o
|
||||
INNER JOIN #{AssignmentOverrideStudent.quoted_table_name} os ON os.assignment_override_id = o.id
|
||||
WHERE
|
||||
o.set_type = 'ADHOC'
|
||||
),
|
||||
|
||||
-- fetch all students affected by group overrides
|
||||
override_groups_students AS (
|
||||
SELECT
|
||||
gm.user_id AS student_id,
|
||||
o.assignment_id,
|
||||
o.id AS override_id,
|
||||
o.due_at,
|
||||
o.set_type AS override_type,
|
||||
1 AS priority
|
||||
FROM
|
||||
overrides o
|
||||
INNER JOIN #{Group.quoted_table_name} g ON g.id = o.set_id
|
||||
INNER JOIN #{GroupMembership.quoted_table_name} gm ON gm.group_id = g.id
|
||||
WHERE
|
||||
o.set_type = 'Group' AND
|
||||
g.workflow_state <> 'deleted' AND
|
||||
gm.workflow_state = 'accepted'
|
||||
),
|
||||
|
||||
-- fetch all students affected by section overrides
|
||||
override_sections_students AS (
|
||||
SELECT
|
||||
e.user_id AS student_id,
|
||||
o.assignment_id,
|
||||
o.id AS override_id,
|
||||
o.due_at,
|
||||
o.set_type AS override_type,
|
||||
1 AS priority
|
||||
FROM
|
||||
overrides o
|
||||
INNER JOIN #{CourseSection.quoted_table_name} s ON s.id = o.set_id
|
||||
INNER JOIN #{Enrollment.quoted_table_name} e ON e.course_section_id = s.id
|
||||
WHERE
|
||||
o.set_type = 'CourseSection' AND
|
||||
s.workflow_state <> 'deleted' AND
|
||||
e.workflow_state NOT IN ('rejected', 'completed', 'deleted', 'inactive') AND
|
||||
e.type IN ('StudentEnrollment', 'StudentViewEnrollment')
|
||||
),
|
||||
|
||||
-- fetch all students who have an 'Everyone Else'
|
||||
-- due date applied to them from the assignment
|
||||
override_everyonelse_students AS (
|
||||
SELECT
|
||||
student.id AS student_id,
|
||||
a.id as assignment_id,
|
||||
NULL::integer AS override_id,
|
||||
a.due_at,
|
||||
'Everyone Else'::varchar AS override_type,
|
||||
2 AS priority
|
||||
FROM
|
||||
models a
|
||||
INNER JOIN #{Course.quoted_table_name} c ON a.context_id = c.id AND a.context_type = 'Course'
|
||||
INNER JOIN #{Enrollment.quoted_table_name} e ON e.course_id = c.id
|
||||
INNER JOIN #{User.quoted_table_name} student ON e.user_id = student.id
|
||||
WHERE
|
||||
e.workflow_state NOT IN ('rejected', 'completed', 'deleted', 'inactive') AND
|
||||
e.type IN ('StudentEnrollment', 'StudentViewEnrollment') AND
|
||||
a.only_visible_to_overrides IS NOT TRUE
|
||||
),
|
||||
|
||||
-- fetch all students who have graded submissions
|
||||
-- because if the student received a grade, they
|
||||
-- shouldn't lose visibility to the assignment
|
||||
override_submissions_students AS (
|
||||
SELECT
|
||||
s.user_id AS student_id,
|
||||
s.assignment_id,
|
||||
NULL::integer AS override_id,
|
||||
NULL::timestamp AS due_at,
|
||||
'Submission'::varchar AS override_type,
|
||||
3 AS priority
|
||||
FROM
|
||||
models a
|
||||
INNER JOIN #{Submission.quoted_table_name} s ON s.assignment_id = a.id
|
||||
WHERE s.workflow_state = 'graded'
|
||||
),
|
||||
|
||||
-- join all these students together into a single table
|
||||
override_all_students AS (
|
||||
SELECT * FROM override_adhoc_students
|
||||
UNION ALL
|
||||
SELECT * FROM override_groups_students
|
||||
UNION ALL
|
||||
SELECT * FROM override_sections_students
|
||||
UNION ALL
|
||||
SELECT * FROM override_everyonelse_students
|
||||
UNION ALL
|
||||
SELECT * FROM override_submissions_students
|
||||
),
|
||||
|
||||
-- and pick the latest override date as the effective due date
|
||||
calculated_overrides AS (
|
||||
SELECT DISTINCT ON (student_id, assignment_id)
|
||||
*
|
||||
FROM override_all_students
|
||||
ORDER BY student_id ASC, assignment_id ASC, priority ASC, due_at DESC NULLS FIRST
|
||||
),
|
||||
|
||||
-- now find all grading periods, including both
|
||||
-- legacy course periods and newer account-level periods
|
||||
course_and_account_grading_periods AS (
|
||||
SELECT DISTINCT ON (gp.id)
|
||||
gp.*,
|
||||
gpg.course_id,
|
||||
gpg.account_id
|
||||
FROM
|
||||
models a
|
||||
INNER JOIN #{Course.quoted_table_name} c ON c.id = a.context_id AND a.context_type = 'Course'
|
||||
INNER JOIN #{EnrollmentTerm.quoted_table_name} term ON c.enrollment_term_id = term.id
|
||||
LEFT OUTER JOIN #{GradingPeriodGroup.quoted_table_name} gpg ON
|
||||
gpg.course_id = c.id OR gpg.id = term.grading_period_group_id
|
||||
LEFT OUTER JOIN #{GradingPeriod.quoted_table_name} gp ON gp.grading_period_group_id = gpg.id
|
||||
WHERE
|
||||
gpg.workflow_state = 'active' AND
|
||||
gp.workflow_state = 'active'
|
||||
),
|
||||
|
||||
-- then filter down to the grading periods we care about:
|
||||
-- if legacy periods exist, only return those. Otherwise,
|
||||
-- return the account-level periods.
|
||||
applied_grading_periods AS (
|
||||
SELECT *
|
||||
FROM course_and_account_grading_periods
|
||||
WHERE
|
||||
EXISTS (
|
||||
SELECT 1 FROM course_and_account_grading_periods WHERE course_id IS NOT NULL
|
||||
) AND course_id IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT *
|
||||
FROM course_and_account_grading_periods
|
||||
WHERE
|
||||
NOT EXISTS (
|
||||
SELECT 1 FROM course_and_account_grading_periods WHERE course_id IS NOT NULL
|
||||
) AND account_id IS NOT NULL
|
||||
),
|
||||
|
||||
-- infinite due dates are put in the last grading period.
|
||||
-- better to fetch it once since we'll likely reference it multiple times below
|
||||
last_period AS (
|
||||
SELECT id, close_date FROM applied_grading_periods ORDER BY end_date DESC LIMIT 1
|
||||
)
|
||||
|
||||
-- finally bring it all together!
|
||||
SELECT
|
||||
overrides.assignment_id,
|
||||
overrides.student_id,
|
||||
overrides.due_at,
|
||||
overrides.override_type,
|
||||
overrides.override_id,
|
||||
CASE
|
||||
-- check whether or not this due date falls in a closed grading period
|
||||
WHEN overrides.due_at IS NOT NULL AND CURRENT_TIMESTAMP >= periods.close_date THEN TRUE
|
||||
-- when no explicit due date is provided, we treat it as if it's in the latest grading period
|
||||
WHEN overrides.due_at IS NULL AND
|
||||
overrides.override_type <> 'Submission' AND
|
||||
CURRENT_TIMESTAMP >= (SELECT close_date FROM last_period) THEN TRUE
|
||||
ELSE FALSE
|
||||
END AS closed,
|
||||
CASE
|
||||
-- if infinite due date, put it in the last grading period
|
||||
WHEN overrides.due_at IS NULL AND
|
||||
overrides.override_type <> 'Submission' THEN (SELECT id FROM last_period)
|
||||
-- otherwise, put it in whatever grading period id we found for it
|
||||
ELSE periods.id
|
||||
END AS grading_period_id
|
||||
FROM calculated_overrides overrides
|
||||
-- match the effective due date with its grading period
|
||||
LEFT OUTER JOIN applied_grading_periods periods ON
|
||||
overrides.due_at >= periods.start_date AND overrides.due_at < periods.end_date
|
||||
").each_with_object({}) do |row, hsh|
|
||||
assignment_id = row.delete("assignment_id").to_i
|
||||
student_id = row.delete("student_id").to_i
|
||||
hsh[assignment_id] ||= {}
|
||||
hsh[assignment_id][student_id] = {
|
||||
due_at: row["due_at"] && Time.zone.parse(row["due_at"]),
|
||||
grading_period_id: row["grading_period_id"] && row["grading_period_id"].to_i,
|
||||
in_closed_grading_period: row["closed"] == "t",
|
||||
override_id: row["override_id"] && row["override_id"].to_i,
|
||||
override_source: row["override_type"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# This iterates through a course's EffectiveDueDate hash with multiple
|
||||
# assignments to see if any of them are in a closed grading period.
|
||||
def any_in_closed_grading_period?
|
||||
return @any_in_closed_grading_period unless @any_in_closed_grading_period.nil?
|
||||
|
||||
@any_in_closed_grading_period = mgp_enabled? &&
|
||||
to_hash.any? do |_, assignment_due_dates|
|
||||
any_student_in_closed_grading_period?(assignment_due_dates)
|
||||
end
|
||||
end
|
||||
|
||||
# This iterates through a single assignment's EffectiveDueDate hash to see
|
||||
# if any students in them are in a closed grading period.
|
||||
def in_closed_grading_period?(assignment_id)
|
||||
assignment_id = assignment_id.id if assignment_id.is_a?(Assignment)
|
||||
return false if assignment_id.nil?
|
||||
|
||||
# obviously false if MGP isn't even enabled
|
||||
return false unless mgp_enabled?
|
||||
# if we've already checked all assignments and it was false,
|
||||
# no need to check this one specifically
|
||||
return false if @any_in_closed_grading_period == false
|
||||
|
||||
assignment_due_dates = to_hash[assignment_id]
|
||||
any_student_in_closed_grading_period?(assignment_due_dates)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mgp_enabled?
|
||||
return @mgp_enabled unless @mgp_enabled.nil?
|
||||
@mgp_enabled = @context.feature_enabled?(:multiple_grading_periods)
|
||||
end
|
||||
|
||||
def any_student_in_closed_grading_period?(assignment_due_dates)
|
||||
return false unless assignment_due_dates
|
||||
assignment_due_dates.any? { |_, student| student[:in_closed_grading_period] }
|
||||
end
|
||||
end
|
|
@ -752,9 +752,7 @@ describe AssignmentGroupsApiController, type: :request do
|
|||
before :once do
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
@grading_period_group = Factories::GradingPeriodGroupHelper.new.create_for_account(@course.root_account)
|
||||
term = @course.enrollment_term
|
||||
term.grading_period_group = @grading_period_group
|
||||
term.save!
|
||||
@grading_period_group.enrollment_terms << @course.enrollment_term
|
||||
Factories::GradingPeriodHelper.new.create_for_group(@grading_period_group, {
|
||||
start_date: 2.weeks.ago, end_date: 2.days.ago, close_date: 1.day.ago
|
||||
})
|
||||
|
@ -764,8 +762,12 @@ describe AssignmentGroupsApiController, type: :request do
|
|||
context "as a teacher" do
|
||||
before :each do
|
||||
@current_user = @teacher
|
||||
student_in_course(course: @course, active_all: true)
|
||||
@assignment = @course.assignments.create!({
|
||||
title: 'assignment', assignment_group: @assignment_group, due_at: 1.week.ago
|
||||
title: 'assignment',
|
||||
assignment_group: @assignment_group,
|
||||
due_at: 1.week.ago,
|
||||
workflow_state: 'published'
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
@ -989,6 +989,16 @@ describe CoursesController, type: :request do
|
|||
@course.reload
|
||||
expect(@course.group_weighting_scheme).to eql("percent")
|
||||
end
|
||||
|
||||
it 'cannot change group_weighting_scheme if any effective due dates in the whole course are in a closed grading period' do
|
||||
Course.any_instance.expects(:any_assignment_in_closed_grading_period?).returns(true)
|
||||
@new_values["course"]["group_weighting_scheme"] = "percent"
|
||||
teacher_in_course(course: @course, active_all: true)
|
||||
raw_api_call(:put, @path, @params, @new_values)
|
||||
expect(response.code).to eql '401'
|
||||
@course.reload
|
||||
expect(@course.group_weighting_scheme).not_to eql("percent")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -326,6 +326,24 @@ describe AssignmentGroupsController do
|
|||
expect(@assignment3.reload.assignment_group_id).to eq(@group2.id)
|
||||
end
|
||||
|
||||
it 'allows assignments with no effective due date in a closed grading period to be moved into different groups' do
|
||||
user_session(@teacher)
|
||||
student = @course.students.first
|
||||
|
||||
override = @assignment2.assignment_overrides.create!(due_at: 1.month.from_now, due_at_overridden: true)
|
||||
override.assignment_override_students.create!(user: student)
|
||||
|
||||
@order = "#{@assignment3.id},#{@assignment2.id}"
|
||||
|
||||
post :reorder_assignments, course_id: @course.id, assignment_group_id: @group2.id, order: @order
|
||||
expect(response).to be_success
|
||||
expect(@assignment1.reload.assignment_group_id).to eq(@group1.id)
|
||||
expect(@assignment2.reload.assignment_group_id).to eq(@group2.id)
|
||||
expect(@assignment3.reload.assignment_group_id).to eq(@group2.id)
|
||||
expect(@assignment2.position).to eql(2)
|
||||
expect(@assignment3.position).to eql(1)
|
||||
end
|
||||
|
||||
it 'allows assignments not in closed grading periods to be moved into different assignment groups' do
|
||||
user_session(@teacher)
|
||||
order = "#{@assignment3.id},#{@assignment2.id}"
|
||||
|
|
|
@ -21,9 +21,9 @@ module Factories
|
|||
override_for = opts.delete(:set)
|
||||
assignment = opts.delete(:assignment) || opts.delete(:quiz) || assignment_model(opts)
|
||||
attrs = assignment_override_valid_attributes.merge(opts)
|
||||
attrs[:due_at_overridden] = true if opts[:due_at]
|
||||
attrs[:lock_at_overridden] = true if opts[:lock_at]
|
||||
attrs[:unlock_at_overridden] = true if opts[:unlock_at]
|
||||
attrs[:due_at_overridden] = opts.key?(:due_at)
|
||||
attrs[:lock_at_overridden] = opts.key?(:lock_at)
|
||||
attrs[:unlock_at_overridden] = opts.key?(:unlock_at)
|
||||
attrs[:set] = override_for if override_for
|
||||
@override = factory_with_protected_attributes(assignment.assignment_overrides, attrs)
|
||||
end
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -19,7 +19,6 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
|
||||
|
||||
describe AssignmentGroup do
|
||||
|
||||
before(:once) do
|
||||
@valid_attributes = {
|
||||
:name => "value for name",
|
||||
|
@ -428,6 +427,16 @@ describe AssignmentGroup do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_assignment_due_in_closed_grading_period?' do
|
||||
it 'calls EffectiveDueDates#in_closed_grading_period?' do
|
||||
assignment_group_model
|
||||
edd = EffectiveDueDates.for_course(@ag.context, @ag.published_assignments)
|
||||
EffectiveDueDates.expects(:for_course).with(@ag.context, @ag.published_assignments).returns(edd)
|
||||
edd.expects(:any_in_closed_grading_period?).returns(true)
|
||||
expect(@ag.has_assignment_due_in_closed_grading_period?).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def assignment_group_model(opts={})
|
||||
|
|
|
@ -3656,107 +3656,13 @@ describe Assignment do
|
|||
end
|
||||
end
|
||||
|
||||
describe "due_for_any_student_in_closed_grading_period?" do
|
||||
before(:once) do
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
grading_period_group = @course.root_account.grading_period_groups.create!(title: "Example Group")
|
||||
grading_period_group.enrollment_terms << @course.enrollment_term
|
||||
@course.enrollment_term.save!
|
||||
|
||||
grading_period_group.grading_periods.create!({
|
||||
title: "Closed Grading Period",
|
||||
start_date: 5.weeks.ago,
|
||||
end_date: 3.weeks.ago,
|
||||
close_date: 1.week.ago
|
||||
})
|
||||
@last_period = grading_period_group.grading_periods.create!({
|
||||
title: "Open Grading Period",
|
||||
start_date: 3.weeks.ago,
|
||||
end_date: 1.week.ago,
|
||||
close_date: 1.week.from_now
|
||||
})
|
||||
|
||||
@assignment = @course.assignments.create!(title: "Example Assignment", only_visible_to_overrides: false)
|
||||
end
|
||||
|
||||
it "is true when the assignment is due in a closed grading period" do
|
||||
@assignment.update_attributes(due_at: 4.weeks.ago)
|
||||
expect(@assignment.due_for_any_student_in_closed_grading_period?).to eql true
|
||||
end
|
||||
|
||||
it "is false when the assignment is due in an open grading period" do
|
||||
@assignment.update_attributes(due_at: 2.weeks.ago)
|
||||
expect(@assignment.due_for_any_student_in_closed_grading_period?).to eql false
|
||||
end
|
||||
|
||||
it "is false when the assignment is due after all grading periods" do
|
||||
@assignment.update_attributes(due_at: 1.day.from_now)
|
||||
expect(@assignment.due_for_any_student_in_closed_grading_period?).to eql false
|
||||
end
|
||||
|
||||
it "is false when the assignment is due before all grading periods" do
|
||||
@assignment.update_attributes(due_at: 6.weeks.ago)
|
||||
expect(@assignment.due_for_any_student_in_closed_grading_period?).to eql false
|
||||
end
|
||||
|
||||
context "when the assignment has no due date" do
|
||||
it "is true when the last grading period is closed" do
|
||||
@assignment.update_attributes(due_at: nil)
|
||||
@last_period.update_attributes(close_date: 1.week.ago)
|
||||
expect(@assignment.due_for_any_student_in_closed_grading_period?).to eql true
|
||||
end
|
||||
|
||||
it "is false when the last grading period is open" do
|
||||
@assignment.update_attributes(due_at: nil)
|
||||
expect(@assignment.due_for_any_student_in_closed_grading_period?).to eql false
|
||||
end
|
||||
end
|
||||
|
||||
it "is true when the assignment has an override with a due date in a closed grading period" do
|
||||
@assignment.update_attributes(due_at: 2.days.from_now)
|
||||
override = @assignment.assignment_overrides.build
|
||||
override.set = @course.default_section
|
||||
override.override_due_at(4.weeks.ago)
|
||||
override.save!
|
||||
expect(@assignment.reload.due_for_any_student_in_closed_grading_period?).to eql true
|
||||
end
|
||||
|
||||
context "when the assignment has an override with no due date" do
|
||||
it "is true when the last grading period is closed" do
|
||||
@last_period.update_attributes(close_date: 1.week.ago)
|
||||
@assignment.update_attributes(due_at: nil)
|
||||
override = @assignment.assignment_overrides.build
|
||||
override.set = @course.default_section
|
||||
override.save!
|
||||
expect(@assignment.reload.due_for_any_student_in_closed_grading_period?).to eql true
|
||||
end
|
||||
|
||||
it "is false when the last grading period is open" do
|
||||
@assignment.update_attributes(due_at: nil)
|
||||
override = @assignment.assignment_overrides.build
|
||||
override.set = @course.default_section
|
||||
override.save!
|
||||
expect(@assignment.reload.due_for_any_student_in_closed_grading_period?).to eql false
|
||||
end
|
||||
end
|
||||
|
||||
it "is false when the assignment has a deleted override with a due date in a closed grading period" do
|
||||
@assignment.update_attributes(due_at: 2.days.from_now)
|
||||
override = @assignment.assignment_overrides.build
|
||||
override.set = @course.default_section
|
||||
override.override_due_at(4.weeks.ago)
|
||||
override.save!
|
||||
override.destroy
|
||||
expect(@assignment.reload.due_for_any_student_in_closed_grading_period?).to eql false
|
||||
end
|
||||
|
||||
it "is false when the assignment is due in a closed grading period and only visible to overrides" do
|
||||
@assignment.update_attributes(due_at: 4.weeks.ago, only_visible_to_overrides: true)
|
||||
override = @assignment.assignment_overrides.build
|
||||
override.set = @course.default_section
|
||||
override.override_due_at(2.days.from_now)
|
||||
override.save!
|
||||
expect(@assignment.reload.due_for_any_student_in_closed_grading_period?).to eql false
|
||||
describe '#in_closed_grading_period?' do
|
||||
it 'calls EffectiveDueDates#in_closed_grading_period?' do
|
||||
assignment_model(course: @course)
|
||||
edd = EffectiveDueDates.for_course(@course, @assignment)
|
||||
EffectiveDueDates.expects(:for_course).with(@course, @assignment.id).returns(edd)
|
||||
edd.expects(:in_closed_grading_period?).with(@assignment.id).returns(true)
|
||||
expect(@assignment.in_closed_grading_period?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4461,7 +4461,7 @@ describe Course, 'touch_root_folder_if_necessary' do
|
|||
@course.tab_configuration = [{"id" => Course::TAB_FILES, "hidden" => true}]
|
||||
@course.save!
|
||||
AdheresToPolicy::Cache.clear # this happens between requests; we're testing the Rails cache
|
||||
expect(@root_folder.reload.grants_right?(@student, :read_contents)).to be_falsy
|
||||
expect(@root_folder.reload.grants_right?(@student, :read_contents)).to be_falsey
|
||||
end
|
||||
|
||||
@course.tab_configuration = [{"id" => Course::TAB_FILES}]
|
||||
|
@ -4667,4 +4667,24 @@ describe Course, "#filter_users_by_permission" do
|
|||
expect(@course.filter_users_by_permission(users, :read_forum)).to eq users # should still work since it is a retroactive permission
|
||||
expect(@course.filter_users_by_permission(users, :moderate_forum)).to be_empty # unlike this one
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Course, '#assignment_in_closed_grading_period?' do
|
||||
it 'delegates to EffectiveDueDates#in_closed_grading_period?' do
|
||||
test_course = Course.create!
|
||||
edd = EffectiveDueDates.for_course(test_course)
|
||||
EffectiveDueDates.expects(:for_course).with(test_course).returns(edd)
|
||||
edd.expects(:in_closed_grading_period?).with(7).returns(true)
|
||||
expect(test_course.assignment_in_closed_grading_period?(7)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe Course, '#any_assignment_in_closed_grading_period?' do
|
||||
it 'delegates to EffectiveDueDates#any_in_closed_grading_period?' do
|
||||
test_course = Course.create!
|
||||
edd = EffectiveDueDates.for_course(test_course)
|
||||
EffectiveDueDates.expects(:for_course).with(test_course).returns(edd)
|
||||
edd.expects(:any_in_closed_grading_period?).returns(true)
|
||||
expect(test_course.any_assignment_in_closed_grading_period?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue