optimize grading period assignments fetching

closes EVAL-1246
flag=none

Test Plan:
1. Create a course that uses grading periods. Have at least 2 grading
   periods.
2. Create an assignment. Assign it to one student in the first grading
   period, and the rest of the students ("Everyone Else") in the second
   grading period.
3. Sign in as the "one student" from the previous step. Click on
   "Grades". Select the first grading period from the Grading Period
   dropdown and click "Apply". You should see the assignment show up.
   Then, select the second grading period from the Grading Period
   dropdown and click "Apply". You should no longer see the assignment
   listed.

Change-Id: I69d4b8369784086034a7883460e63f4f259a79ca
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/248932
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Adrian Packel <apackel@instructure.com>
Reviewed-by: Kai Bjorkman <kbjorkman@instructure.com>
QA-Review: Syed Hussain <shussain@instructure.com>
Product-Review: Jody Sailor
This commit is contained in:
Spencer Olson 2020-09-30 10:59:17 -05:00
parent 89941a1dc1
commit 93aee7f242
18 changed files with 77 additions and 1022 deletions

View File

@ -47,7 +47,7 @@ class AssignmentGroupsApiController < ApplicationController
override_dates = value_to_boolean(params[:override_assignment_dates] || true)
assignments = @assignment_group.visible_assignments(@current_user)
if params[:grading_period_id].present?
assignments = GradingPeriod.for(@context).find_by(id: params[:grading_period_id]).assignments(assignments)
assignments = GradingPeriod.for(@context).find_by(id: params[:grading_period_id]).assignments(@context, assignments)
end
if assignments.any? && includes.include?('submission')
submissions = submissions_hash(['submission'], assignments)

View File

@ -330,8 +330,7 @@ class AssignmentGroupsController < ApplicationController
def include_overrides?
override_dates? ||
include_params.include?('all_dates') ||
include_params.include?('overrides') ||
filter_by_grading_period?
include_params.include?('overrides')
end
def assignment_visibilities(course, assignments)
@ -445,9 +444,9 @@ class AssignmentGroupsController < ApplicationController
if params[:scope_assignments_to_student] &&
course.user_is_student?(@current_user, include_future: true, include_fake_student: true)
grading_period.assignments_for_student(assignments, @current_user)
grading_period.assignments_for_student(course, assignments, @current_user)
else
grading_period.assignments(assignments)
grading_period.assignments(course, assignments)
end
end

View File

@ -181,7 +181,7 @@ class GradebooksController < ApplicationController
if grading_periods? && @current_grading_period_id && !view_all_grading_periods?
current_period = GradingPeriod.for(@context).find_by(id: @current_grading_period_id)
visible_assignments = current_period.assignments_for_student(visible_assignments, opts[:student])
visible_assignments = current_period.assignments_for_student(@context, visible_assignments, opts[:student])
end
visible_assignments.map! do |a|
@ -1047,7 +1047,10 @@ class GradebooksController < ApplicationController
def grading_period_assignments
return unless authorized_action(@context, @current_user, [:manage_grades, :view_all_grades])
grading_period_assignments = GradebookGradingPeriodAssignments.new(@context, gradebook_settings(@context.global_id))
grading_period_assignments = GradebookGradingPeriodAssignments.new(
@context,
course_settings: gradebook_settings(@context.global_id)
)
render json: { grading_period_assignments: grading_period_assignments.to_h }
end

View File

@ -431,7 +431,7 @@ class SubmissionsApiController < ApplicationController
assignments = GuardRail.activate(:secondary) do
if params[:grading_period_id].present?
GradingPeriod.active.find(params[:grading_period_id]).assignments(assignment_scope)
GradingPeriod.active.find(params[:grading_period_id]).assignments(@context, assignment_scope)
else
assignment_scope.to_a
end

View File

@ -1,62 +0,0 @@
#
# Copyright (C) 2015 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module Assignment::FilterWithOverridesByDueAt
def filter_assignments
ActiveRecord::Associations::Preloader.new.preload(assignments, :assignment_overrides)
assignments.select { |assignment| in_grading_period?(assignment) }
end
private
def in_date_range_end_inclusive?(due_at)
!due_at.nil? && grading_period.in_date_range?(due_at)
end
def in_date_range_of_grading_period?(assignment)
due_at = find_due_at(assignment)
in_date_range_end_inclusive?(due_at)
end
def in_grading_period?(assignment)
due_at_nil_and_last_grading_period?(assignment) ||
in_date_range_of_grading_period?(assignment)
end
def milliseconds_to_zero(due_at)
due_at.change(usec: 0) if due_at
end
def active_overrides(assignment)
# using 'select' instead of calling the 'active' scope
# because we have assignment_overrides eager loaded and
# we don't want to make additional AR calls.
@active_assignment_overrides ||= {}
@active_assignment_overrides[assignment.id] ||=
assignment.assignment_overrides.select do |override|
override.workflow_state == 'active'
end
end
def any_active_overrides?(assignment)
active_overrides(assignment).any?
end
def no_active_overrides?(assignment)
!any_active_overrides?(assignment)
end
end

View File

@ -1,48 +0,0 @@
#
# Copyright (C) 2015 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
class Assignment::FilterWithOverridesByDueAtForClass
# this module provides the public method #filter_assignments
include Assignment::FilterWithOverridesByDueAt
def initialize(assignments:, grading_period:)
@assignments = assignments
@grading_period = grading_period
end
private
attr_reader :assignments, :grading_period
def due_at_nil_and_last_grading_period?(assignment)
return false unless grading_period.last?
return assignment.due_at.nil? if no_active_overrides?(assignment)
active_overrides(assignment).any? { |override| override.due_at.nil? }
end
def find_due_at(assignment)
due_at = any_active_overrides?(assignment) ? filter_date_from_overrides(assignment) : assignment.due_at
milliseconds_to_zero(due_at)
end
def filter_date_from_overrides(assignment)
due_ats = active_overrides(assignment).map(&:due_at)
due_ats << assignment.due_at
due_ats.compact!
date_within_period = due_ats.find { |due_at| in_date_range_end_inclusive?(due_at) }
date_within_period || due_ats.first
end
end

View File

@ -1,72 +0,0 @@
#
# Copyright (C) 2015 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
class Assignment::FilterWithOverridesByDueAtForStudent
# this module provides the public method #filter_assignments
include Assignment::FilterWithOverridesByDueAt
def initialize(assignments:, grading_period:, student:)
@assignments = assignments
@grading_period = grading_period
@student = student
if AssignmentOverrideApplicator.should_preload_override_students?(@assignments, @student, "filter_with_overrides")
AssignmentOverrideApplicator.preload_assignment_override_students(@assignments, @student)
end
end
private
attr_reader :assignments, :grading_period, :student
def due_at_nil_and_last_grading_period?(assignment)
return false unless grading_period.last?
return assignment.due_at.nil? if no_active_overrides?(assignment)
override_dates = override_dates_for_student(assignment)
if override_dates.empty?
assignment.due_at.nil? && assigned_to_everyone_else?(assignment)
else
override_dates.any?(&:nil?)
end
end
def find_due_at(assignment)
most_lenient_due_at(assignment)
end
def assigned_to_everyone_else?(assignment)
!assignment.only_visible_to_overrides?
end
def most_lenient_due_at(assignment)
date_to_use = assignment.due_at if assigned_to_everyone_else?(assignment)
if any_active_overrides?(assignment)
override_dates = override_dates_for_student(assignment)
return nil if override_dates.any?(&:nil?)
most_lenient_date = override_dates.max
date_to_use = most_lenient_date if most_lenient_date
end
milliseconds_to_zero(date_to_use)
end
def override_dates_for_student(assignment)
student_overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(assignment, @student)
student_overrides.map(&:due_at)
end
end

View File

@ -101,19 +101,22 @@ class GradingPeriod < ActiveRecord::Base
grading_period_group.course_id.present?
end
def assignments_for_student(assignments, student)
Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: assignments,
grading_period: self,
student: student
).filter_assignments
def assignments_for_student(course, assignments, student)
assignment_ids = GradebookGradingPeriodAssignments.new(course, student: student).to_h.fetch(id, [])
if assignment_ids.empty?
[]
else
assignments.select { |assignment| assignment_ids.include?(assignment.id.to_s) }
end
end
def assignments(assignments)
Assignment::FilterWithOverridesByDueAtForClass.new(
assignments: assignments,
grading_period: self
).filter_assignments
def assignments(course, assignments)
assignment_ids = GradebookGradingPeriodAssignments.new(course).to_h.fetch(id, [])
if assignment_ids.empty?
[]
else
assignments.select { |assignment| assignment_ids.include?(assignment.id.to_s) }
end
end
def current?

View File

@ -25,7 +25,7 @@ class GradingPeriodGradeSummaryPresenter < GradeSummaryPresenter
def assignments_visible_to_student
grading_period = GradingPeriod.for(@context).where(id: grading_period_id).first
grading_period.assignments_for_student(super, student)
grading_period.assignments_for_student(@context, super, student)
end
def groups

View File

@ -382,7 +382,7 @@ class GradebookExporter
def select_in_grading_period(assignments)
if grading_period
grading_period.assignments(assignments)
grading_period.assignments(@course, assignments)
else
assignments
end

View File

@ -16,11 +16,12 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.
class GradebookGradingPeriodAssignments
def initialize(course, course_settings)
def initialize(course, student: nil, course_settings: nil)
raise "Context must be a course" unless course.is_a?(Course)
raise "Context must have an id" unless course.id
@course = course
@student = student
@settings_for_course = course_settings || {
'show_concluded_enrollments' => 'false',
'show_inactive_enrollments' => 'false'
@ -41,15 +42,19 @@ class GradebookGradingPeriodAssignments
excluded_workflow_states
end
# One Query to rule them all, One Query to find them, One Query to bring them all, and in the darkness bind them to a hash
def the_query
GuardRail.activate(:secondary) do
Submission.
scope = Submission.
active.
joins(:assignment).
joins("INNER JOIN #{Enrollment.quoted_table_name} enrollments ON enrollments.user_id = submissions.user_id").
merge(Assignment.for_course(@course).active).
where(enrollments: { course_id: @course, type: ['StudentEnrollment', 'StudentViewEnrollment'] }).
where.not(grading_period_id: nil).where.not(enrollments: { workflow_state: excluded_workflow_states }).
where.not(grading_period_id: nil).where.not(enrollments: { workflow_state: excluded_workflow_states })
scope = scope.where(user: @student) if @student
scope.
group(:grading_period_id).
pluck(:grading_period_id, Arel.sql("array_agg(DISTINCT assignment_id)")).
to_h

View File

@ -348,6 +348,7 @@ describe AssignmentGroupsController, type: :request do
context "grading periods" do
before :once do
setup_grading_periods
@course.enroll_student(User.create!)
end
describe "#index" do
@ -676,6 +677,7 @@ describe AssignmentGroupsApiController, type: :request do
end
it 'should only return assignments in the given grading period with MGP on' do
@course.enroll_student(User.create!)
setup_grading_periods
json = api_call(:get, "/api/v1/courses/#{@course.id}/assignment_groups/#{@group1.id}?include[]=assignments&grading_period_id=#{@gp_future.id}",

View File

@ -44,6 +44,7 @@ describe AssignmentGroupsController do
student_override = assignment_with_override.assignment_overrides.new.tap do |override|
override.title = 'feb override'
override.due_at = Time.zone.local(2015, 2, 15)
override.due_at_overridden = true
end
student_override.save!
student_override.assignment_override_students.create!(user: student)
@ -52,6 +53,7 @@ describe AssignmentGroupsController do
let(:student) do
dora = User.create!(name: "Dora")
course_with_student(course: course, user: dora, active_enrollment: true)
course_with_student(course: course, user: User.create!, active_enrollment: true)
dora
end

View File

@ -32,7 +32,7 @@ describe GradebookGradingPeriodAssignments do
@assignment4 = @example_course.assignments.create!(due_at: 6.months.from_now)
end
let(:hash) { GradebookGradingPeriodAssignments.new(@example_course, {}).to_h }
let(:hash) { GradebookGradingPeriodAssignments.new(@example_course).to_h }
context "with grading periods" do
before(:once) do
@ -67,6 +67,14 @@ describe GradebookGradingPeriodAssignments do
expect(hash[@period2.id]).to include(@assignment1_in_gp1.id.to_s)
end
it "optionally returns results for a specific student" do
override = @assignment1_in_gp1.assignment_overrides.create!(due_at: 1.day.ago, due_at_overridden: true)
override.assignment_override_students.create!(user: @student2)
hash = GradebookGradingPeriodAssignments.new(@example_course, student: @student2).to_h
expect(hash[@period1.id]).to be_nil
expect(hash[@period2.id]).to include(@assignment1_in_gp1.id.to_s)
end
it "excludes assignments due outside of any grading period" do
expect(hash[@period1.id]).not_to include(@assignment4.id.to_s)
expect(hash[@period2.id]).not_to include(@assignment4.id.to_s)
@ -105,8 +113,7 @@ describe GradebookGradingPeriodAssignments do
@settings = {}
end
let(:hash) { GradebookGradingPeriodAssignments.new(@course, @settings).to_h }
let(:hash) { GradebookGradingPeriodAssignments.new(@course, course_settings: @settings).to_h }
describe 'concluded students' do
before(:once) do
@ -190,10 +197,10 @@ describe GradebookGradingPeriodAssignments do
end
it "raises an exception if context is not a course" do
expect { GradebookGradingPeriodAssignments.new({}, {}) }.to raise_error("Context must be a course")
expect { GradebookGradingPeriodAssignments.new({}) }.to raise_error("Context must be a course")
end
it "raises an exception if context has no id" do
expect { GradebookGradingPeriodAssignments.new(Course.new, {}) }.to raise_error("Context must have an id")
expect { GradebookGradingPeriodAssignments.new(Course.new) }.to raise_error("Context must have an id")
end
end

View File

@ -1,384 +0,0 @@
#
# Copyright (C) 2015 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require_relative '../../spec_helper'
require_relative '../../../app/models/assignment/filter_with_overrides_by_due_at'
describe Assignment::FilterWithOverridesByDueAtForClass do
subject(:assignments) do
Assignment::FilterWithOverridesByDueAtForClass.new(params).filter_assignments
end
let(:account) { Account.create! }
let(:course) { account.courses.create! }
let(:group) { Factories::GradingPeriodGroupHelper.new.legacy_create_for_course(course) }
let(:period) do
group.grading_periods.create!(
title: "a default period",
start_date: 3.years.ago,
end_date: 2.years.ago
)
end
let(:params) do
{
assignments: course.assignments,
grading_period: period
}
end
describe '#filter_assignments' do
let!(:first_period) do
group.grading_periods.create!(
start_date: Time.zone.local(2015, 1, 1),
end_date: Time.zone.local(2015, 1, 31),
title: 'first period'
)
end
let!(:last_period) do
group.grading_periods.create!(
start_date: Time.zone.local(2015, 2, 1),
end_date: Time.zone.local(2015, 2, 28),
title: 'last period'
)
end
let(:date_inside_first_range) { Time.zone.local(2015, 1, 15) }
let(:date_inside_last_range) { Time.zone.local(2015, 2, 15) }
let(:date_outside_range) { Time.zone.local(2999, 12, 31) }
let(:assignment_graph_builder) do
-> (assignment_property:, override_property:, period:) do
date_inside_range = date_range_selector.call(period)
assignment = assignment_builder.call(
assignment_property,
date_inside_range
)
assignment = override_builder.call(
assignment,
override_property,
date_inside_range
)
assignment
end
end
let(:date_range_selector) do
-> (period) do
if period.last?
date_inside_last_range
else
date_inside_first_range
end
end
end
let(:assignment_builder) do
-> (assignment_property, date_inside_range) do
case assignment_property
when :in
course.assignments.create!(
due_at: date_inside_range,
workflow_state: 'active'
)
when nil
course.assignments.create!(
due_at: nil,
workflow_state: 'active'
)
when :out
course.assignments.create!(
due_at: date_outside_range,
workflow_state: 'active'
)
else
raise AssignmentCaseNotFound
end
end
end
let(:override_builder) do
-> (assignment, override_property, date_inside_range) do
case override_property
when :in
assignment.assignment_overrides.build do |override|
override.due_at = date_inside_range
override.workflow_state = 'active'
override.title = 'override inside range'
end.save!
assignment
when nil
assignment.assignment_overrides.build do |override|
override.due_at = nil
override.workflow_state = 'active'
override.title = 'override with nil due_at'
end.save!
assignment
when :none
assignment
when :out
assignment.assignment_overrides.build do |override|
override.due_at = date_outside_range
override.workflow_state = 'active'
override.title = 'override with nil due_at'
end.save!
assignment
else
raise OverrideCaseNotFound
end
end
end
let(:builder_params) do
{
assignment_property: assignment_property,
override_property: override_property,
period: period
}
end
context 'not the last grading period' do
let(:period) { first_period }
context 'given an assignment with no due at' do
let(:assignment_property) { nil }
context 'given an override with no due at' do
let(:override_property) { nil }
it 'does not select assignment' do
assignment_graph_builder.call(**builder_params)
expect(assignments).to be_empty
end
end
context 'given an override in range' do
let(:override_property) { :in }
it 'selects the assignment' do
assignment_with_no_due_at_and_an_override_in_range =
assignment_graph_builder.call(**builder_params)
expect(assignments)
.to eql [assignment_with_no_due_at_and_an_override_in_range]
end
end
context 'given an override outside the range' do
let(:override_property) { :out }
it 'does not select the assignment' do
assignment_graph_builder.call(**builder_params)
expect(assignments).to be_empty
end
end
end
context 'given an assignment in range' do
let(:assignment_property) { :in }
context 'given an override with no due at' do
let(:override_property) { nil }
it 'selects the assignment' do
assignment_in_range = assignment_graph_builder
.call(**builder_params)
expect(assignments).to eql [assignment_in_range]
end
end
context 'given an override in range' do
let(:override_property) { :in }
it 'selects the assignment' do
assignment_in_range_with_an_override_in_range =
assignment_graph_builder.call(**builder_params)
expect(assignments)
.to eql [assignment_in_range_with_an_override_in_range]
end
end
context 'given an override outside the range' do
let(:override_property) { :out }
it 'selects the assignment' do
assignment_in_range_and_override_outside_range =
assignment_graph_builder.call(**builder_params)
expect(assignments)
.to eql [assignment_in_range_and_override_outside_range]
end
end
end
context 'given an assignment outside the range' do
let(:assignment_property) { :out }
context 'given an override with no due at' do
let(:override_property) { nil }
it 'does not select assignment' do
assignment_graph_builder.call(**builder_params)
expect(assignments).to be_empty
end
end
context 'given an override in the range' do
let(:override_property) { :in }
it 'selects the assignment' do
assignment_with_override_in_range =
assignment_graph_builder.call(**builder_params)
expect(assignments)
.to eql [assignment_with_override_in_range]
end
end
context 'given an override outside the range' do
let(:override_property) { :out }
it 'does not select the assignment' do
assignment_graph_builder.call(**builder_params)
expect(assignments).to be_empty
end
end
end
end
context 'when the grading period is the last in the group' do
let(:period) { last_period }
context 'given an assignment with no due at' do
let(:assignment_property) { nil }
context 'given an override with no due at' do
let(:override_property) { nil }
it 'selects the assignment' do
assignment_with_override_and_no_due_ats =
assignment_graph_builder.call(**builder_params)
expect(assignments)
.to eql [assignment_with_override_and_no_due_ats]
end
end
context 'given an override in range' do
let(:override_property) { :in }
it 'selects the assignment' do
assignment_with_no_due_at_and_override_in_range =
assignment_graph_builder.call(**builder_params)
expect(assignments)
.to eql [assignment_with_no_due_at_and_override_in_range]
end
end
context 'given an override outside the range' do
let(:override_property) { :out }
it 'does not select the assignment' do
assignment_graph_builder.call(**builder_params)
expect(assignments)
.to be_empty
end
end
context 'given no override' do
let(:override_property) { :none }
it 'selects the assignment' do
assignment_with_no_due_at_and_no_override =
assignment_graph_builder.call(**builder_params)
expect(assignments)
.to eql [assignment_with_no_due_at_and_no_override]
end
end
end
context 'given an assignment in the date range' do
let(:assignment_property) { :in }
context 'given an override with no due at' do
let(:override_property) { nil }
it 'selects the assignment' do
assignment_in_date_range_and_override_with_no_due_at =
assignment_graph_builder.call(**builder_params)
expect(assignments)
.to eql [assignment_in_date_range_and_override_with_no_due_at]
end
end
context 'given an override in range' do
let(:override_property) { :in }
it 'selects the assignment' do
assignment_with_override_both_inside_range =
assignment_graph_builder.call(**builder_params)
expect(assignments)
.to eql [assignment_with_override_both_inside_range]
end
end
context 'given an override outside the range' do
let(:override_property) { :out }
it 'selects the assignment' do
assignment_inside_range_and_override_outside_range =
assignment_graph_builder.call(**builder_params)
expect(assignments)
.to eql [assignment_inside_range_and_override_outside_range]
end
end
context 'given no override' do
let(:override_property) { :in }
it 'selects the assignment' do
assignment_in_range_and_no_override =
assignment_graph_builder.call(**builder_params)
expect(assignments)
.to eql [assignment_in_range_and_no_override]
end
end
end
context 'given an assignment outside the date range' do
let(:assignment_property) { :out }
context 'given an override with no due at' do
let(:override_property) { nil }
it 'selects the assignment' do
assignment_outside_date_range_and_override_with_no_due_at =
assignment_graph_builder.call(**builder_params)
expect(assignments)
.to eql [assignment_outside_date_range_and_override_with_no_due_at]
end
end
context 'given no override' do
let(:override_property) { :none }
it 'selects the assignment' do
assignment_graph_builder.call(**builder_params)
expect(assignments).to be_empty
end
end
context 'given an override in range' do
let(:override_property) { :in }
it 'selects the assignment' do
assignment_outside_and_override_inside_range =
assignment_graph_builder.call(**builder_params)
expect(assignments)
.to eql [assignment_outside_and_override_inside_range]
end
end
context 'given an override outside the range' do
let(:override_property) { :out }
it 'does not select the assignment' do
assignment_graph_builder.call(**builder_params)
expect(assignments).to be_empty
end
end
end
end
end
end

View File

@ -1,400 +0,0 @@
#
# Copyright (C) 2015 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require_relative '../../spec_helper'
require_relative '../../../app/models/assignment/filter_with_overrides_by_due_at_for_student'
describe Assignment::FilterWithOverridesByDueAtForStudent do
describe '#filter_assignments' do
before :once do
@course = Course.create!
@assignment = @course.assignments.create!
group = Factories::GradingPeriodGroupHelper.new.legacy_create_for_course(@course)
@grading_period = group.grading_periods.create!(
title: 'testing is easy, he said',
start_date: 1.month.ago,
end_date: 2.months.from_now
)
@last_grading_period = group.grading_periods.create!(
title: 'this is the last time',
start_date: 3.months.from_now,
end_date: 28.years.from_now
)
@student = User.create!
@section = @course.course_sections.create!(name: 'Section 1')
@course.enroll_student(@student, section: @section, enrollment_state: 'active')
end
context 'the assignment has one override and the override applies to the student' do
before :once do
@assignment.only_visible_to_overrides = true
@override = @assignment.assignment_overrides.create!
@override.assignment_override_students.create!(user: @student)
end
context 'given that override has a due at' do
it 'selects the assignment if the override due_at falls within the grading period' do
@override.due_at = 2.days.from_now
@override.save!
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to include @assignment
end
it 'does not select assignment if the override due_at does not fall within the grading period' do
@override.due_at = 3.months.from_now
@override.save!
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to_not include @assignment
end
end
context 'given that override does not have a due at' do
before :once do
@override.due_at = nil
@override.save!
end
it 'does not select assignment if the grading period is not the last period' do
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to_not include @assignment
end
it 'selects the assignment if the grading period is the last period' do
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @last_grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to include @assignment
end
end
end
context 'the assignment has one override and the override does not apply to the student' do
before :once do
@assignment.assignment_overrides.create!
end
context 'the assignment has a due at' do
it 'selects the assignment if it is in the grading period and "Everyone Else" has been assigned' do
@assignment.due_at = 2.days.from_now
@assignment.only_visible_to_overrides = false
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to include @assignment
end
it 'does not select the assignment if it is in the grading period, but "Everyone Else" has not been assigned' do
@assignment.due_at = 2.days.from_now
@assignment.only_visible_to_overrides = true
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to_not include @assignment
end
it 'does not select the assignment if it is not in the grading period, and "Everyone Else" has been assigned' do
@assignment.due_at = 3.months.from_now
@assignment.only_visible_to_overrides = false
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to_not include @assignment
end
it 'does not select the assignment if it is not in the grading period,' \
' and "Everyone Else" has not been assigned' do
@assignment.due_at = 3.months.from_now
@assignment.only_visible_to_overrides = true
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to_not include @assignment
end
end
context 'the assignment does not have a due at' do
it 'does not select the assignment if it is not the last grading period,' \
' and "Everyone Else" has been assigned' do
@assignment.due_at = nil
@assignment.only_visible_to_overrides = false
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to_not include @assignment
end
it 'does not select the assignment if it is not the last grading period,' \
' and "Everyone Else" has not been assigned' do
@assignment.due_at = nil
@assignment.only_visible_to_overrides = true
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to_not include @assignment
end
context 'the grading period is the last' do
it 'selects the assignment if "Everyone Else" has been assigned' do
@assignment.due_at = nil
@assignment.only_visible_to_overrides = false
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @last_grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to include @assignment
end
it 'does not select the assignment if "Everyone Else" has not been assigned' do
@assignment.due_at = nil
@assignment.only_visible_to_overrides = true
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @last_grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to_not include @assignment
end
end
end
end
context 'the assignment does not have any overrides' do
context 'the assignment has a due at' do
it 'selects the assignment if its due at falls within the grading period' do
@assignment.due_at = 2.days.from_now
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to include @assignment
end
it 'does not select the assignment if its due is outside of the grading period' do
@assignment.due_at = 3.months.from_now
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to_not include @assignment
end
end
context 'the assignment does not have a due at' do
it 'does not select the assignment if it is not the last grading period' do
@assignment.due_at = nil
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to_not include @assignment
end
it 'selects the assignment if it is the last grading period' do
@assignment.due_at = nil
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @last_grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to include @assignment
end
end
end
context 'the assignment has two overrides that apply to the student' do
before :once do
@student_override = @assignment.assignment_overrides.create!
@student_override.assignment_override_students.create!(user: @student)
@section_override = @assignment.assignment_overrides.new
@section_override.set = @section
@section_override.save!
end
context 'both overrides have a due at' do
it 'selects the assignment if the later due at falls in the grading period' do
@student_override.due_at = 2.months.ago
@student_override.save!
@section_override.due_at = 2.days.from_now
@section_override.save!
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to include @assignment
end
it 'does not select the assignment if the later due at is outside of the grading period' \
' (even if the earlier due at is within the grading period)' do
@student_override.due_at = 2.days.from_now
@student_override.save!
@section_override.due_at = 3.months.from_now
@section_override.save!
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to_not include @assignment
end
end
context 'one of the overrides has a due at, one does not' do
context 'not the last grading period' do
it 'does not select the assignment if the due at is outside of the grading period' do
@student_override.due_at = nil
@student_override.save!
@section_override.due_at = 3.months.from_now
@section_override.save!
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to_not include @assignment
end
it 'does not select the assignment even if the due at is within the grading period' do
@student_override.due_at = nil
@student_override.save!
@section_override.due_at = 2.days.from_now
@section_override.save!
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to_not include @assignment
end
end
context 'the last grading period' do
it 'selects the assignment if the due at is within the grading period' do
@student_override.due_at = nil
@student_override.save!
@section_override.due_at = 2.days.from_now
@section_override.save!
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @last_grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to include @assignment
end
it 'selects the assignment even if the due at is outside of the grading period' do
@student_override.due_at = nil
@student_override.save!
@section_override.due_at = 3.months.from_now
@section_override.save!
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @last_grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to include @assignment
end
end
end
context 'neither override has a due at' do
before :once do
@student_override.due_at = nil
@student_override.save!
@section_override.due_at = nil
@section_override.save!
end
it 'does not select the assignment if it is not the last grading period' do
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to_not include @assignment
end
it 'selects the assignment if it is the last grading period' do
filtered_assignments = Assignment::FilterWithOverridesByDueAtForStudent.new(
assignments: [@assignment],
grading_period: @last_grading_period,
student: @student
).filter_assignments
expect(filtered_assignments).to include @assignment
end
end
end
end
end

View File

@ -628,53 +628,53 @@ describe GradingPeriod do
end
end
# TODO: move all of this to filter_with_overrides_by_due_at_for_class.rb
describe "#assignments" do
let!(:first_assignment) { course.assignments.create!(due_at: first_grading_period.start_date + 1.minute) }
let!(:second_assignment) { course.assignments.create!(due_at: second_grading_period.start_date + 1.minute) }
let!(:third_assignment) { course.assignments.create!(due_at: nil) }
let(:first_grading_period) do
grading_period_group.grading_periods.create!(
title: '1st period',
start_date: 2.months.from_now(now),
end_date: 3.months.from_now(now)
)
end
let(:second_grading_period) do
grading_period_group.grading_periods.create!(
before(:once) do
account = Account.create!
@course = account.courses.create!
student = User.create!
@course.enroll_student(student, enrollment_state: :active)
@grading_period_group = group_helper.legacy_create_for_course(@course)
@first_grading_period = @grading_period_group.grading_periods.create!(
title: '1st period',
start_date: 2.months.from_now(now),
end_date: 3.months.from_now(now)
)
@second_grading_period = @grading_period_group.grading_periods.create!(
title: '2nd period',
start_date: 3.months.from_now(now),
end_date: 4.months.from_now(now)
)
@first_assignment = @course.assignments.create!(due_at: @first_grading_period.start_date + 1.minute)
@second_assignment = @course.assignments.create!(due_at: @second_grading_period.start_date + 1.minute)
@third_assignment = @course.assignments.create!(due_at: nil)
end
let(:grading_period_group) { group_helper.legacy_create_for_course(course) }
it "filters the first grading period" do
assignments = first_grading_period.assignments(course.assignments)
expect(assignments).to eq [first_assignment]
assignments = @first_grading_period.assignments(@course, @course.assignments)
expect(assignments).to eq [@first_assignment]
end
it "filters assignments without a due_at into the last grading period" do
assignments = second_grading_period.assignments(course.assignments)
expect(assignments).to eq [second_assignment, third_assignment]
assignments = @second_grading_period.assignments(@course, @course.assignments)
expect(assignments).to eq [@second_assignment, @third_assignment]
end
describe "when due at is near the edge of a period" do
let!(:fourth_assignment) do
course.assignments.create!(
@course.assignments.create!(
due_at: third_grading_period.end_date - 0.995.seconds
)
end
let!(:fifth_assignment) do
course.assignments.create!(
@course.assignments.create!(
due_at: fourth_grading_period.start_date - 0.005.seconds
)
end
let(:third_grading_period) do
grading_period_group.grading_periods.create!(
@grading_period_group.grading_periods.create!(
title: '3rd period',
start_date: 5.months.from_now(now),
end_date: 6.months.from_now(now)
@ -682,7 +682,7 @@ describe GradingPeriod do
end
let(:fourth_grading_period) do
grading_period_group.grading_periods.create!(
@grading_period_group.grading_periods.create!(
title: '4th period',
start_date: 7.months.from_now(now),
end_date: 8.months.from_now(now)
@ -690,12 +690,12 @@ describe GradingPeriod do
end
it "includes assignments if they are on the future edge of end date" do
assignments = third_grading_period.assignments(course.assignments)
assignments = third_grading_period.assignments(@course, @course.assignments)
expect(assignments).to include fourth_assignment
end
it "does NOT include assignments if they are on the past edge of start date" do
assignments = fourth_grading_period.assignments(course.assignments)
assignments = fourth_grading_period.assignments(@course, @course.assignments)
expect(assignments).not_to include fifth_assignment
end
end

View File

@ -84,7 +84,7 @@ describe GradingPeriodGradeSummaryPresenter do
end
it "includes overridden assignments that are due for the student in the given grading period" do
student_override = @assignment_not_due_in_period.assignment_overrides.create!(due_at: @now)
student_override = @assignment_not_due_in_period.assignment_overrides.create!(due_at: @now, due_at_overridden: true)
student_override.assignment_override_students.create!(user: @student)
expect(presenter.assignments_visible_to_student).to include(@assignment_not_due_in_period)
end
@ -100,7 +100,7 @@ describe GradingPeriodGradeSummaryPresenter do
end
it "includes groups that have overridden assignments due in the given period for the given user" do
student_override = @assignment_not_due_in_period.assignment_overrides.create!(due_at: @now)
student_override = @assignment_not_due_in_period.assignment_overrides.create!(due_at: @now, due_at_overridden: true)
student_override.assignment_override_students.create!(user: @student)
expect(presenter.groups).to include(@second_group)
end