filter assignments by overrides when necessary

build query that search for overrides but delegates to assignment if none exist

fixes CNVS-20435

Change-Id: I0b88ccb78027e43a7d82c392a11b9980fff52174
Reviewed-on: https://gerrit.instructure.com/57176
Tested-by: Jenkins
Reviewed-by: Strand McCutchen <smccutchen@instructure.com>
QA-Review: Adrian Foong <afoong@instructure.com>
Product-Review: Matt Fairbourn <mfairbourn@instructure.com>
This commit is contained in:
Derek Bender 2015-07-08 10:53:30 -05:00 committed by Matt Fairbourn
parent 153be42401
commit 570e6d3165
10 changed files with 952 additions and 257 deletions

View File

@ -106,89 +106,12 @@ class AssignmentGroupsController < ApplicationController
# @returns [AssignmentGroup]
def index
if authorized_action(@context.assignment_groups.scoped.new, @current_user, :read)
@groups = @context.assignment_groups.active
params[:include] = Array(params[:include])
assignments_by_group = {}
if params[:include].include? 'assignments'
assignment_includes = [:rubric, :quiz, :external_tool_tag, :rubric_association]
assignment_includes.concat(params[:include] & ["discussion_topic"])
assignment_includes.concat([:assignment_overrides]) if params[:include].include?("all_dates")
all_visible_assignments = AssignmentGroup.visible_assignments(@current_user, @context, @groups, assignment_includes)
.with_student_submission_count
if params[:grading_period_id].present? && multiple_grading_periods?
all_visible_assignments = GradingPeriod
.active
.find(params[:grading_period_id])
.assignments(all_visible_assignments)
end
da_enabled = @context.feature_enabled?(:differentiated_assignments)
include_visibility = Array(params[:include]).include?('assignment_visibility') && @context.grants_any_right?(@current_user, :read_as_admin, :manage_grades, :manage_assignments)
if include_visibility && da_enabled
assignment_visibilities = AssignmentStudentVisibility.users_with_visibility_by_assignment(course_id: @context.id, assignment_id: all_visible_assignments.map(&:id))
else
params[:include].delete('assignment_visibility')
end
# because of a bug with including content_tags, we are preloading here rather than in
# visible_assignments with multiple associations referencing content_tags table and therefore
# aliased table names the conditons on has_many :context_module_tags will break
if params[:include].include? "module_ids"
module_includes = [:context_module_tags,{:discussion_topic => :context_module_tags},{:quiz => :context_module_tags}]
ActiveRecord::Associations::Preloader.new(all_visible_assignments, module_includes).run
end
assignments_by_group = all_visible_assignments.group_by(&:assignment_group_id)
unless params[:exclude_descriptions]
assignment_descriptions = all_visible_assignments.map(&:description)
user_content_attachments = api_bulk_load_user_content_attachments(
assignment_descriptions, @context, @current_user
)
end
override_param = params[:override_assignment_dates] || true
override_dates = value_to_boolean(override_param)
if override_dates
assignments_with_overrides = @context.assignments.active.except(:order)
.joins(:assignment_overrides)
.select("assignments.id")
.uniq
assignments_without_overrides = all_visible_assignments - assignments_with_overrides
assignments_without_overrides.each { |a| a.has_no_overrides = true }
end
end
include_overrides = params[:include].include? 'overrides'
assignments = visible_assignments(@context, @current_user)
respond_to do |format|
format.json {
json = @groups.map { |g|
g.context = @context
assignments = assignments_by_group[g.id] || []
overrides = []
if include_overrides
ActiveRecord::Associations::Preloader.new(assignments, :assignment_overrides).run
assignments.select{ |a| a.assignment_overrides.size == 0 }.
each { |a| a.has_no_overrides = true }
overrides = assignments.map{|assignment| assignment.assignment_overrides.active}
end
assignment_group_json(g, @current_user, session, params[:include],
stringify_json_ids: stringify_json_ids?,
override_assignment_dates: override_dates,
preloaded_user_content_attachments: user_content_attachments,
assignments: assignments,
assignment_visibilities: assignment_visibilities,
differentiated_assignments_enabled: da_enabled,
exclude_descriptions: !!params[:exclude_descriptions],
overrides: overrides.flatten
)
}
render :json => json
}
format.json do
render json: index_groups_json(@context, @current_user, assignments)
end
end
end
end
@ -294,4 +217,150 @@ class AssignmentGroupsController < ApplicationController
end
end
end
private
def assignments_by_group(visible_assignments)
visible_assignments.group_by(&:assignment_group_id)
end
def assignment_includes
includes = [:rubric, :quiz, :external_tool_tag, :rubric_association]
includes << "discussion_topic" if params[:include].include?("discussion_topic")
includes << :assignment_overrides if params[:include].include?('all_dates')
includes
end
def assignment_visibilities(course)
if include_visibility? && differentiated_assignments?
AssignmentStudentVisibility.users_with_visibility_by_assignment(
course_id: course.id,
assignment_id: visible_assignment_ids
)
else
params.fetch(:include, []).delete('assignment_visibility')
AssignmentStudentVisibility.none
end
end
def differentiated_assignments?
@context.feature_enabled?(:differentiated_assignments)
end
def index_groups_json(context, current_user, visible_assignments)
include_overrides = params.fetch(:include, []).include? 'overrides'
context.assignment_groups.active.map do |group|
group.context = context
assignments = assignments_by_group(visible_assignments)[group.id] || []
overrides = []
if include_overrides
# TODO: use above?
ActiveRecord::Associations::Preloader.new(assignments, :assignment_overrides).run
assignments.select{ |a| a.assignment_overrides.size == 0 }.
each { |a| a.has_no_overrides = true }
overrides = assignments.map{|assignment| assignment.assignment_overrides.active}
end
assignment_group_json(
group,
current_user,
session,
params[:include],
{
stringify_json_ids: stringify_json_ids?,
override_assignment_dates: override_dates?,
preloaded_user_content_attachments: user_content_attachments(context, current_user),
assignments: assignments,
assignment_visibilities: assignment_visibilities(context),
differentiated_assignments_enabled: differentiated_assignments?,
exclude_descriptions: !!params[:exclude_descriptions],
overrides: overrides.flatten
}
)
end
end
def include_visibility?
if Array(params[:include]).include?('assignment_visibility')
@context.grants_any_right?(@current_user, :read_as_admin, :manage_grades, :manage_assignments)
end
end
def override_dates?
value_to_boolean(params.fetch(:override_assignment_dates, true))
end
def user_content_attachments(context, current_user)
unless params[:exclude_descriptions]
api_bulk_load_user_content_attachments(
visible_assignment_descriptions, context, current_user
)
end
end
def visible_assignment_descriptions
visible_assignments(@context, @current_user).map(&:description)
end
def visible_assignment_ids
visible_assignments(@context, @current_user).map(&:id)
end
def visible_assignments(context, current_user)
if params[:include] && params[:include].include?('assignments')
# TODO: possible keyword arguments refactor
assignments = AssignmentGroup.visible_assignments(
current_user,
context,
context.assignment_groups.active,
assignment_includes
).with_student_submission_count
if params[:grading_period_id].present? && multiple_grading_periods?
grading_period = GradingPeriod.context_find(
context,
params.fetch(:grading_period_id)
)
assignments = Assignment::FilterWithOverridesByDueAt.new(
assignments: assignments,
grading_period: grading_period,
differentiated_assignments: differentiated_assignments?
).filter_assignments
end
# because of a bug with including content_tags, we are preloading
# here rather than in assignments with multiple associations
# referencing content_tags table and therefore aliased table names
# the conditions on has_many :context_module_tags will break
if params[:include].include? "module_ids"
module_includes = [
:context_module_tags,
{ :discussion_topic => :context_module_tags },
{ :quiz => :context_module_tags }
]
ActiveRecord::Associations::Preloader.new(assignments, module_includes).run
end
# FIXME: determine if this code is necessary as it is potentially
# dead code
if override_dates?
assignments_with_overrides = context
.assignments
.active
.except(:order)
.joins(:assignment_overrides)
.select("assignments.id")
.uniq
assignments_without_overrides = assignments - assignments_with_overrides
assignments_without_overrides.each { |a| a.has_no_overrides = true }
end
assignments
else
Assignment.none
end
end
end

View File

@ -0,0 +1,97 @@
# API Filter Assignments by due_at and respect overrides
#
# Returns a list of assignments that have been filtered by assignment
# due_at and assignment_overrides due_at fields. For example: if any
# due_at fields fall within the date range then then corresponding
# assignment is included.
#
# @argument assignments [ActiveRecord::Relation]
#
# @argument start_date [DateTime]
# @argument end_date [DateTime]
#
# @argument differentiated_assignments [Boolean]
#
# @public assignments
#
# @return [ Assignment ] assignments An array of assignments
#
# @example
# Assignment::FilterWithoutOverridesByDueAt.new(
# assignments: @context.assignments,
# grading_period: grading_period
# differentiated_assignments: @context.feature_enabled?(:differentiated_assignments)
# )
#
class Assignment::FilterWithOverridesByDueAt
def initialize(assignments:, grading_period:, differentiated_assignments:)
@assignments = assignments
@differentiated_assignments = differentiated_assignments
@grading_period = grading_period
end
# @returns [Assignments]
def filter_assignments
assignments.select do |assignment|
filter_criteria(assignment).any?
end
end
private
attr_reader :assignments, :grading_period
def filter_criteria(assignment)
[
differentiated_assignments_and_any_assignment_overrides?(assignment) &&
last_grading_period_and_any_overrides_with_due_at_nil?(assignment),
differentiated_assignments_and_any_assignment_overrides?(assignment) &&
any_overrides_in_date_range?(assignment),
last_grading_period_and_assignment_due_at_nil?(assignment),
in_date_range_end_inclusive?(assignment)
]
end
def differentiated_assignments_and_any_assignment_overrides?(assignment)
differentiated_assignments? && assignment.assignment_overrides.any?
end
def last_grading_period_and_any_overrides_with_due_at_nil?(assignment)
last_grading_period? && any_overrides_with_due_at_nil?(assignment)
end
def last_grading_period_and_assignment_due_at_nil?(assignment)
last_grading_period? && assignment.due_at.nil?
end
def in_date_range_end_inclusive?(assignment_or_override)
return false if assignment_or_override.due_at.nil?
grading_period.start_date < assignment_or_override.due_at &&
assignment_or_override.due_at <= grading_period.end_date
end
def differentiated_assignments?
@differentiated_assignments
end
def last_grading_period?
@grading_period.last?
end
def any_overrides_in_date_range?(assignment)
assignment.assignment_overrides.any? do |override|
in_date_range_end_inclusive?(override)
end
end
def any_overrides_with_due_at_nil?(assignment)
assignment.assignment_overrides.any? do |override|
override.due_at.nil?
end
end
end

View File

@ -202,7 +202,7 @@ class AssignmentGroup < ActiveRecord::Base
end
def visible_assignments(user, includes=[])
AssignmentGroup.visible_assignments(user, self.context, [self], includes)
self.class.visible_assignments(user, self.context, [self], includes)
end
def self.visible_assignments(user, context, assignment_groups, includes = [])

View File

@ -1,3 +1,21 @@
#
# Copyright (C) 2015 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 GradingPeriod < ActiveRecord::Base
include Canvas::SoftDeletable
@ -35,8 +53,13 @@ class GradingPeriod < ActiveRecord::Base
.grading_periods
end
def self.context_find(context, id)
self.for(context).detect { |grading_period| grading_period.id == id.to_i }
# Takes a context and a grading_period_id and returns a grading period
# if it is in the for collection. Uses Enumberable#find to query
# collection.
def self.context_find(context, grading_period_id)
self.for(context).find do |grading_period|
grading_period.id == grading_period_id.to_i
end
end
def assignments(assignment_scope)

View File

@ -24,10 +24,12 @@ class GradingPeriod
end
def grading_periods
course_gps = GradingPeriod.active.grading_periods_by(course_id: course.id)
course_gps.present? ?
course_gps :
periods = GradingPeriod.active.grading_periods_by(course_id: course.id)
if periods.present?
periods
else
AccountGradingPeriodFinder.new(course.account).grading_periods
end
end
private

View File

@ -63,7 +63,7 @@ describe AssignmentGroupsController, type: :request do
course_with_teacher(:active_all => true)
end
it "should sort the returned list of assignment groups" do
it "sorts the returned list of assignment groups" do
# the API returns the assignments sorted by
# assignment_groups.position
group1 = @course.assignment_groups.create!(:name => 'group1')

View File

@ -16,125 +16,174 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
require_relative '../spec_helper'
describe AssignmentGroupsController do
def course_assignment
@assignment = @course.assignments.create(:title => "some assignment")
end
def course_group
@group = @course.assignment_groups.create(:name => "some group")
@group = @course.assignment_groups.create(:name => 'some group')
end
def group_assignment
@assignment = @group.assignments.create(:name => "some group assignment")
end
before :once do
course_with_teacher(active_all: true)
student_in_course(active_all: true)
end
describe "GET 'index'" do
it "should require authorization" do
get 'index', :course_id => @course.id
assert_unauthorized
end
it "should assign variables" do
user_session(@student)
get 'index', :course_id => @course.id, :format => :json
expect(assigns[:groups]).not_to be_nil
end
it "should retrieve course groups if they exist" do
user_session(@student)
course_group
@group = course_group
get 'index', :course_id => @course.id, :format => :json
expect(assigns[:groups]).not_to be_nil
expect(assigns[:groups]).not_to be_empty
expect(assigns[:groups][1]).to eql(@group)
end
context "differentiated assignments" do
before do
user_session(@teacher)
course_group
@group = course_group
@course.enable_feature!(:differentiated_assignments)
@assignment = @course.assignments.create!(title: "assignment",
assignment_group: @group,
only_visible_to_overrides: true,
workflow_state: 'published')
describe 'GET index' do
describe 'filteing by grading period and overrides' do
let!(:assignment) { course.assignments.create!(due_at: Date.new(2015, 1, 15)) }
let!(:assignment_with_override) { course.assignments.create!(due_at: Date.new(2015, 1, 15)) }
let!(:feb_override) do
# mass assignment is disabled for AssigmentOverride
assignment_with_override.assignment_overrides.new.tap do |override|
override.title = 'feb override'
override.due_at = Time.zone.local(2015, 2, 15)
end.save!
end
it "should not check visibilities on individual assignemnts" do
# ensures that check is not an N+1 from the gradebook
Assignment.any_instance.expects(:students_with_visibility).never
get 'index', :course_id => @course.id, :include => ["assignments","assignment_visibility"], :format => :json
expect(response).to be_success
let(:jan_grading_period) do
grading_period_group.grading_periods.create!(
start_date: Date.new(2015, 1, 1),
end_date: Date.new(2015, 1, 31),
title: 'Jan Period'
)
end
let(:feb_grading_period) do
grading_period_group.grading_periods.create!(
start_date: Date.new(2015, 2, 1),
end_date: Date.new(2015, 2, 28),
title: 'Feb Period'
)
end
let(:grading_period_group) { course.grading_period_groups.create! }
let(:course) { Course.create! }
let(:root_account) { Account.default }
let(:sub_account) { root_account.sub_accounts.create! }
context 'given a root account with a grading period and a sub account with a grading period' do
before do
root_account.allow_feature!(:multiple_grading_periods)
root_account.enable_feature!(:multiple_grading_periods)
root_account.enable_feature!(:differentiated_assignments)
account_admin_user(account: root_account)
user_session(@admin)
end
it 'when there is an assignment with overrides, filter grading periods by overrides due_at' do
get :index,
course_id: course.id,
include: ['assignments', 'assignment_visibility', 'overrides'],
override_assignment_dates: false,
exclude_descriptions: true,
grading_period_id: feb_grading_period.id,
format: :json
json = JSON.parse(response.body[9..-1])
assignments_ids = json.first['assignments'].map{|a| a['id']}
expect(assignments_ids).to include assignment_with_override.id
expect(assignments_ids).to_not include assignment.id
end
end
end
context "multiple grading periods feature enabled" do
it "should not throw an error when grading_period_id is passed in as empty string" do
@course.root_account.enable_feature!(:multiple_grading_periods)
user_session(@teacher)
get 'index', :course_id => @course.id, :include => ["assignments", "assignment_visibility"], :grading_period_id => "", :format => :json
expect(response).to be_success
context 'given a course with teach and a student in course' do
before :once do
course_with_teacher(active_all: true)
student_in_course(active_all: true)
end
it 'requires authorization' do
get 'index', :course_id => @course.id
assert_unauthorized
end
context 'differentiated assignments' do
before do
user_session(@teacher)
course_group
@group = course_group
@course.enable_feature!(:differentiated_assignments)
@assignment = @course.assignments.create!(
title: 'assignment',
assignment_group: @group,
only_visible_to_overrides: true,
workflow_state: 'published'
)
end
it 'does not check visibilities on individual assignemnts' do
# ensures that check is not an N+1 from the gradebook
Assignment.any_instance.expects(:students_with_visibility).never
get 'index', :course_id => @course.id, :include => ['assignments','assignment_visibility'], :format => :json
expect(response).to be_success
end
end
context 'multiple grading periods feature enabled' do
before do
@course.root_account.enable_feature!(:multiple_grading_periods)
user_session(@teacher)
end
it 'does not throw an error when grading_period_id is passed in as empty string' do
get 'index', :course_id => @course.id, :include => ['assignments', 'assignment_visibility'], :grading_period_id => '', :format => :json
expect(response).to be_success
end
end
end
end
describe "POST 'reorder'" do
it "should require authorization" do
before :once do
course_with_teacher(active_all: true)
student_in_course(active_all: true)
end
it 'requires authorization' do
post 'reorder', :course_id => @course.id
assert_unauthorized
end
it "should not allowe students to reorder" do
it 'does not allow students to reorder' do
user_session(@student)
post 'reorder', :course_id => @course.id
assert_unauthorized
end
it "should reorder assignment groups" do
it 'reorders assignment groups' do
user_session(@teacher)
groups = 3.times.map { course_group }
expect(groups.map(&:position)).to eq [1, 2, 3]
g1, g2, _ = groups
post 'reorder', :course_id => @course.id, :order => "#{g2.id},#{g1.id}"
expect(response).to be_success
groups.each &:reload
groups.each(&:reload)
expect(groups.map(&:position)).to eq [2, 1, 3]
end
end
describe "POST 'reorder_assignments'"do
before(:once) do
@group1 = @course.assignment_groups.create!(:name => "group 1")
@group2 = @course.assignment_groups.create!(:name => "group 2")
@assignment1 = @course.assignments.create!(:title => "assignment 1", :assignment_group => @group1)
@assignment2 = @course.assignments.create!(:title => "assignment 2", :assignment_group => @group1)
@assignment3 = @course.assignments.create!(:title => "assignment 3", :assignment_group => @group2)
before :once do
course_with_teacher(active_all: true)
student_in_course(active_all: true)
@group1 = @course.assignment_groups.create!(:name => 'group 1')
@group2 = @course.assignment_groups.create!(:name => 'group 2')
@assignment1 = @course.assignments.create!(:title => 'assignment 1', :assignment_group => @group1)
@assignment2 = @course.assignments.create!(:title => 'assignment 2', :assignment_group => @group1)
@assignment3 = @course.assignments.create!(:title => 'assignment 3', :assignment_group => @group2)
@order = "#{@assignment1.id},#{@assignment2.id},#{@assignment3.id}"
end
it "should require authorization" do
it 'requires authorization' do
post :reorder_assignments, :course_id => @course.id, :assignment_group_id => @assignment1.assignment_group.id, :order => @order
assert_unauthorized
end
it "should not allow students to reorder" do
it 'does not allow students to reorder' do
user_session(@student)
post :reorder_assignments, :course_id => @course.id, :assignment_group_id => @assignment1.assignment_group.id, :order => @order
assert_unauthorized
end
it "should move the assingment from its current assignment group to another assignment group" do
it 'moves the assingment from its current assignment group to another assignment group' do
user_session(@teacher)
expect(response).to be_success
post :reorder_assignments, :course_id => @course.id, :assignment_group_id => @assignment1.assignment_group.id, :order => @order
@ -146,95 +195,107 @@ describe AssignmentGroupsController do
end
describe "GET 'show'" do
before(:once) { course_group }
before :once do
course_with_teacher(active_all: true)
student_in_course(active_all: true)
course_group
end
it "should require authorization" do
it 'requires authorization' do
get 'show', :course_id => @course.id, :id => @group.id
assert_unauthorized
end
it "should assign variables" do
it 'assigns variables' do
user_session(@student)
get 'show', :course_id => @course.id, :id => @group.id, :format => :json
# response.should be_success
expect(assigns[:assignment_group]).not_to be_nil
expect(assigns[:assignment_group]).to eql(@group)
end
end
describe "POST 'create'" do
it "should require authorization" do
before :once do
course_with_teacher(active_all: true)
student_in_course(active_all: true)
end
it 'requires authorization' do
post 'create', :course_id => @course.id
assert_unauthorized
end
it "should not allow students to create" do
it 'does not allow students to create' do
user_session(@student)
post 'create', :course_id => @course.id
assert_unauthorized
end
it "should create a new group" do
it 'creates a new group' do
user_session(@teacher)
post 'create', :course_id => @course.id, :assignment_group => {:name => "some test group"}
post 'create', :course_id => @course.id, :assignment_group => {:name => 'some test group'}
expect(response).to be_redirect
expect(assigns[:assignment_group]).not_to be_nil
expect(assigns[:assignment_group].name).to eql("some test group")
expect(assigns[:assignment_group].name).to eql('some test group')
expect(assigns[:assignment_group].position).to eql(1)
end
end
describe "PUT 'update'" do
before(:once) { course_group }
before :once do
course_with_teacher(active_all: true)
student_in_course(active_all: true)
course_group
end
it "should require authorization" do
it 'requires authorization' do
put 'update', :course_id => @course.id, :id => @group.id
assert_unauthorized
end
it "should not allow students to update" do
it 'does not allow students to update' do
user_session(@student)
put 'update', :course_id => @course.id, :id => @group.id
assert_unauthorized
end
it "should update group" do
it 'updates group' do
user_session(@teacher)
put 'update', :course_id => @course.id, :id => @group.id, :assignment_group => {:name => "new group name"}
expect(assigns[:assignment_group]).not_to be_nil
put 'update', :course_id => @course.id, :id => @group.id, :assignment_group => {:name => 'new group name'}
expect(assigns[:assignment_group]).to eql(@group)
expect(assigns[:assignment_group].name).to eql("new group name")
expect(assigns[:assignment_group].name).to eql('new group name')
end
end
describe "DELETE 'destroy'" do
before(:once) { course_group }
before :once do
course_with_teacher(active_all: true)
student_in_course(active_all: true)
course_group
end
it "should require authorization" do
it 'requires authorization' do
delete 'destroy', :course_id => @course.id, :id => @group.id
assert_unauthorized
end
it "should not allow students to delete" do
it 'does not allow students to delete' do
user_session(@student)
delete 'destroy', :course_id => @course.id, :id => @group.id
assert_unauthorized
end
it "should delete group" do
it 'deletes group' do
user_session(@teacher)
delete 'destroy', :course_id => @course.id, :id => @group.id
expect(assigns[:assignment_group]).not_to be_nil
expect(assigns[:assignment_group]).to eql(@group)
expect(assigns[:assignment_group]).not_to be_frozen
expect(assigns[:assignment_group]).to be_deleted
end
it "should delete assignments in the group" do
it 'delete assignments in the group' do
user_session(@teacher)
@group1 = @course.assignment_groups.create!(:name => "group 1")
@assignment1 = @course.assignments.create!(:title => "assignment 1", :assignment_group => @group1)
@group1 = @course.assignment_groups.create!(:name => 'group 1')
@assignment1 = @course.assignments.create!(:title => 'assignment 1', :assignment_group => @group1)
delete 'destroy', :course_id => @course.id, :id => @group1.id
expect(assigns[:assignment_group]).to eql(@group1)
expect(assigns[:assignment_group]).to be_deleted
@ -243,12 +304,12 @@ describe AssignmentGroupsController do
expect(@group1.assignments.active.length).to eql(0)
end
it "should move assignments to a different group if specified" do
it 'moves assignments to a different group if specified' do
user_session(@teacher)
@group1 = @course.assignment_groups.create!(:name => "group 1")
@assignment1 = @course.assignments.create!(:title => "assignment 1", :assignment_group => @group1)
@group2 = @course.assignment_groups.create!(:name => "group 2")
@assignment2 = @course.assignments.create!(:title => "assignment 2", :assignment_group => @group2)
@group1 = @course.assignment_groups.create!(:name => 'group 1')
@assignment1 = @course.assignments.create!(:title => 'assignment 1', :assignment_group => @group1)
@group2 = @course.assignment_groups.create!(:name => 'group 2')
@assignment2 = @course.assignments.create!(:title => 'assignment 2', :assignment_group => @group2)
expect(@assignment1.position).to eql(1)
expect(@assignment1.assignment_group_id).to eql(@group1.id)
expect(@assignment2.position).to eql(1)
@ -267,13 +328,15 @@ describe AssignmentGroupsController do
expect(@assignment2.assignment_group_id).to eql(@group1.id)
end
it "does not allow users to delete assignment groups with frozen assignments" do
it 'does not allow users to delete assignment groups with frozen assignments' do
PluginSetting.stubs(:settings_for_plugin).returns(title: 'yes')
user_session(@teacher)
group = @course.assignment_groups.create!(name: "group 1")
assignment = @course.assignments.create!(title: "assignment",
assignment_group: group,
freeze_on_copy: true)
group = @course.assignment_groups.create!(name: 'group 1')
assignment = @course.assignments.create!(
title: 'assignment',
assignment_group: group,
freeze_on_copy: true
)
expect(assignment.position).to eq 1
assignment.copied = true
assignment.save!
@ -281,9 +344,9 @@ describe AssignmentGroupsController do
expect(response).not_to be_success
end
it "should return JSON if requested" do
it 'returns JSON if requested' do
user_session(@teacher)
delete 'destroy', :format => "json", :course_id => @course.id, :id => @group.id
delete 'destroy', :format => 'json', :course_id => @course.id, :id => @group.id
expect(response).to be_success
end
end

View File

@ -23,7 +23,6 @@ def assignment_model(opts={})
@group_category = course.group_categories.create(:name => group_category) if group_category
opts[:group_category] = @group_category if @group_category
@assignment = factory_with_protected_attributes(course.assignments, assignment_valid_attributes.merge(opts))
expect(@assignment.context).to eq course
@a = @assignment
@c = course
@a
@ -33,7 +32,7 @@ def assignment_valid_attributes
{
:title => "value for title",
:description => "value for description",
:due_at => Time.now,
:due_at => Time.zone.now,
:points_possible => "1.5"
}
end

View File

@ -0,0 +1,431 @@
require_relative '../../../app/models/assignment/filter_with_overrides_by_due_at'
describe Assignment::FilterWithOverridesByDueAt do
describe '#filter_assignments' do
subject(:assignments) do
Assignment::FilterWithOverridesByDueAt.new(params).filter_assignments
end
let(:params) do
{
assignments: course.assignments,
grading_period: period,
differentiated_assignments: differentiated_assignments
}
end
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(:group) { course.grading_period_groups.create! }
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(:course) { account.courses.create! }
let(:account) { Account.create! }
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 'differentiated assignments is false' do
# differentiated assignments implies :none for override_property
let(:differentiated_assignments) { false }
let(:override_property) { :none }
context 'not the last grading period' do
let(:period) { first_period }
context 'given an assignment with no due at' do
let(:assignment_property) { nil }
it 'does not select assignments' do
assignment_graph_builder.call(builder_params)
expect(assignments).to be_empty
end
end
context 'given an assignment with a due at in range' do
let(:assignment_property) { :in }
it 'selects the assignment' do
assignment = assignment_graph_builder.call(builder_params)
expect(assignments).to eql [assignment]
end
end
context 'given an assignment with a due at outside the range' do
let(:assignment_property) { :out }
it 'does not select the assignment' do
assignment_graph_builder.call(builder_params)
expect(assignments).to be_empty
end
end
end
context 'last grading period' do
let(:period) { last_period }
context 'given an assignment with no due at' do
let(:assignment_property) { nil }
it 'selects the assignment' do
assignment_with_nil_due_at = assignment_graph_builder
.call(builder_params)
expect(assignments).to eql [assignment_with_nil_due_at]
end
end
context 'given an assignment with a due at in range' do
let(:assignment_property) { :in }
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 assignment with a due at outside the range' do
let(:assignment_property) { :out }
it 'selects the assignment' do
assignment_graph_builder.call(builder_params)
expect(assignments).to eql []
end
end
end
end
context 'differentiated assignments is true' do
let(:differentiated_assignments) { true }
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 eql []
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 eql []
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 eql []
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 'selects the assignment' do
assignment_with_no_due_at_and_override_outside_range =
assignment_graph_builder.call(builder_params)
expect(assignments)
.to eql [assignment_with_no_due_at_and_override_outside_range]
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
end

View File

@ -4,82 +4,93 @@ require File.expand_path(File.dirname(__FILE__) + '/helpers/groups_common')
describe "gradebook2" do
include_context "in-process server selenium tests"
context "multiple grading periods" do
let!(:enable_mgp_and_navigate_to_gradebook) do
describe "multiple grading periods" do
let!(:enable_mgp) do
course_with_admin_logged_in
student_in_course
@course.root_account.enable_feature!(:multiple_grading_periods)
group = @course.root_account.grading_period_groups.create
group.grading_periods.create start_date: 4.months.ago,
end_date: 2.months.ago,
title: "Period in the Past"
group.grading_periods.create start_date: 1.month.ago,
end_date: 2.months.from_now,
title: "Current Period"
end
let(:select_period_in_the_past) do
f(".grading-period-select-button").click
f("#ui-id-4").click # The id of the Period in the Past
end
let(:sign_in_as_a_teacher) do
teacher_in_course
user_session(@teacher)
end
let(:uneditable_cells) { f('.cannot_edit') }
let(:gradebook_header) { f('#gradebook_grid .container_1 .slick-header') }
it "should load gradebook when no grading periods have been created", priority: "1", test_id: 210011 do
it "loads gradebook when no grading periods have been created", priority: "1", test_id: 210011 do
get "/courses/#{@course.id}/gradebook2"
expect(f('#gradebook-grid-wrapper')).to be_displayed
end
context "assignments in past grading periods" do
let!(:assignment_in_the_past) do
@course.assignments.create! due_at: 3.months.ago,
title: "past-due assignment"
describe 'with a current and past grading period' do
let!(:create_period_group_and_default_periods) do
group = @course.root_account.grading_period_groups.create
group.grading_periods.create(
start_date: 4.months.ago,
end_date: 2.months.ago,
title: "Period in the Past"
)
group.grading_periods.create(
start_date: 1.month.ago,
end_date: 2.months.from_now,
title: "Current Period"
)
end
it "admins should be able to edit", priority: "1", test_id: 210012 do
get "/courses/#{@course.id}/gradebook2"
select_period_in_the_past
expect(gradebook_header).to include_text("past-due assignment")
expect(uneditable_cells).to_not be_present
let(:select_period_in_the_past) do
f(".grading-period-select-button").click
f("#ui-id-4").click # The id of the Period in the Past
end
it "teachers should not be able to edit", priority: "1", test_id: 210023 do
sign_in_as_a_teacher
get "/courses/#{@course.id}/gradebook2"
select_period_in_the_past
expect(gradebook_header).to include_text("past-due assignment")
expect(uneditable_cells).to be_present
end
end
context "assignments with no due_at" do
let!(:assignment_without_due_at) do
@course.assignments.create! title: "No Due Date"
let(:sign_in_as_a_teacher) do
teacher_in_course
user_session(@teacher)
end
it "admins should be able to edit", priority: "1", test_id: 210014 do
get "/courses/#{@course.id}/gradebook2"
let(:uneditable_cells) { f('.cannot_edit') }
let(:gradebook_header) { f('#gradebook_grid .container_1 .slick-header') }
expect(gradebook_header).to include_text("No Due Date")
expect(uneditable_cells).to_not be_present
context "assignments in past grading periods" do
let!(:assignment_in_the_past) do
@course.assignments.create!(
due_at: 3.months.ago,
title: "past-due assignment"
)
end
it "admins should be able to edit", priority: "1", test_id: 210012 do
get "/courses/#{@course.id}/gradebook2"
select_period_in_the_past
expect(gradebook_header).to include_text("past-due assignment")
expect(uneditable_cells).to_not be_present
end
it "teachers should not be able to edit", priority: "1", test_id: 210023 do
sign_in_as_a_teacher
get "/courses/#{@course.id}/gradebook2"
select_period_in_the_past
expect(gradebook_header).to include_text("past-due assignment")
expect(uneditable_cells).to be_present
end
end
it "teachers should be able to edit", priority: "1", test_id: 210015 do
sign_in_as_a_teacher
context "assignments with no due_at" do
let!(:assignment_without_due_at) do
@course.assignments.create! title: "No Due Date"
end
get "/courses/#{@course.id}/gradebook2"
it "admins should be able to edit", priority: "1", test_id: 210014 do
get "/courses/#{@course.id}/gradebook2"
expect(gradebook_header).to include_text("No Due Date")
expect(uneditable_cells).to_not be_present
expect(gradebook_header).to include_text("No Due Date")
expect(uneditable_cells).to_not be_present
end
it "teachers should be able to edit", priority: "1", test_id: 210015 do
sign_in_as_a_teacher
get "/courses/#{@course.id}/gradebook2"
expect(gradebook_header).to include_text("No Due Date")
expect(uneditable_cells).to_not be_present
end
end
end
end