move all DelayedJob Alert code into separate class

fixes CNVS-12411

test-plan:
regression test all alerts

Change-Id: Ie954c3c6c3f034fce130774c5c6e4e1515b02049
Reviewed-on: https://gerrit.instructure.com/33339
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
QA-Review: Jeremy Putnam <jeremyp@instructure.com>
Product-Review: Simon Williams <simon@instructure.com>
This commit is contained in:
Nathan Mills 2014-04-15 10:00:22 -06:00 committed by Simon Williams
parent 339dc7a41f
commit bb244d1f86
5 changed files with 597 additions and 565 deletions

View File

@ -32,6 +32,32 @@ class Alert < ActiveRecord::Base
before_save :infer_defaults
def resolve_recipients(student_id, teachers = nil)
include_student = false
include_teacher = false
include_teachers = false
admin_roles = []
self.recipients.try(:each) do |recipient|
case
when recipient == :student
include_student = true
when recipient == :teachers
include_teachers = true
when recipient.is_a?(String)
admin_roles << recipient
else
raise "Unsupported recipient type!"
end
end
recipients = []
recipients << student_id if include_student
recipients.concat(Array(teachers)) if teachers.present? && include_teachers
recipients.concat context.account_users.where(:membership_type => admin_roles).uniq.pluck(:user_id) if context_type == 'Account' && !admin_roles.empty?
recipients.uniq
end
def infer_defaults
self.repetition = nil if self.repetition.blank?
end
@ -64,102 +90,4 @@ class Alert < ActiveRecord::Base
end
self.criteria.replace(values)
end
def resolve_recipients(student_id, teachers = nil)
include_student = false
include_teacher = false
include_teachers = false
admin_roles = []
self.recipients.try(:each) do |recipient|
case
when recipient == :student
include_student = true
when recipient == :teachers
include_teachers = true
when recipient.is_a?(String)
admin_roles << recipient
else
raise "Unsupported recipient type!"
end
end
recipients = []
recipients << student_id if include_student
recipients.concat(Array(teachers)) if teachers.present? && include_teachers
recipients.concat context.account_users.where(:membership_type => admin_roles).uniq.pluck(:user_id) if context_type == 'Account' && !admin_roles.empty?
recipients.uniq
end
def self.process
Account.root_accounts.active.find_each do |account|
next unless account.settings[:enable_alerts]
self.send_later_if_production_enqueue_args(:evaluate_for_root_account, { :priority => Delayed::LOW_PRIORITY }, account)
end
end
def self.evaluate_for_root_account(account)
return unless account.settings[:enable_alerts]
alerts_cache = {}
account.associated_courses.where(:workflow_state => 'available').find_each do |course|
alerts_cache[course.account_id] ||= course.account.account_chain.map { |a| a.alerts.all }.flatten
self.evaluate_for_course(course, alerts_cache[course.account_id])
end
end
def self.evaluate_for_course(course, account_alerts)
return unless course.available?
alerts = Array.new(account_alerts || [])
alerts.concat course.alerts.all
return if alerts.empty?
student_enrollments = course.student_enrollments.active
student_ids = student_enrollments.map(&:user_id)
return if student_ids.empty?
teacher_enrollments = course.instructor_enrollments.active
teacher_ids = teacher_enrollments.map(&:user_id)
return if teacher_ids.empty?
teacher_student_mapper = Courses::TeacherStudentMapper.new(student_enrollments, teacher_enrollments)
# Evaluate all the criteria for each user for each alert
today = Time.now.beginning_of_day
alert_checkers = {}
alerts.each do |alert|
student_ids.each do |user_id|
matches = true
alert.criteria.each do |criterion|
alert_checker = alert_checkers[criterion.criterion_type] ||= Alerts.const_get(criterion.criterion_type).new(course, student_ids, teacher_ids)
matches = !alert_checker.should_not_receive_message?(user_id, criterion.threshold.to_i)
break unless matches
end
cache_key = [alert, user_id].cache_key
if matches
last_sent = Rails.cache.fetch(cache_key)
if last_sent.blank?
elsif alert.repetition.blank?
matches = false
else
matches = last_sent + alert.repetition.days <= today
end
end
if matches
Rails.cache.write(cache_key, today)
send_alert(alert, alert.resolve_recipients(user_id, teacher_student_mapper.teachers_for_student(user_id)), student_enrollments.to_ary.find { |enrollment| enrollment.user_id == user_id } )
end
end
end
end
def self.send_alert(alert, user_ids, student_enrollment)
notification = Notification.by_name("Alert")
notification.create_message(alert, user_ids, {:asset_context => student_enrollment})
end
end

View File

@ -0,0 +1,75 @@
module Alerts
class DelayedAlertSender
def self.process
Account.root_accounts.active.find_each do |account|
next unless account.settings[:enable_alerts]
self.send_later_if_production_enqueue_args(:evaluate_for_root_account, { :priority => Delayed::LOW_PRIORITY }, account)
end
end
def self.evaluate_for_root_account(account)
return unless account.settings[:enable_alerts]
alerts_cache = {}
account.associated_courses.where(:workflow_state => 'available').find_each do |course|
alerts_cache[course.account_id] ||= course.account.account_chain.map { |a| a.alerts.all }.flatten
self.evaluate_for_course(course, alerts_cache[course.account_id])
end
end
def self.evaluate_for_course(course, account_alerts)
return unless course.available?
alerts = Array.new(account_alerts || [])
alerts.concat course.alerts.all
return if alerts.empty?
student_enrollments = course.student_enrollments.active
student_ids = student_enrollments.map(&:user_id)
return if student_ids.empty?
teacher_enrollments = course.instructor_enrollments.active
teacher_ids = teacher_enrollments.map(&:user_id)
return if teacher_ids.empty?
teacher_student_mapper = Courses::TeacherStudentMapper.new(student_enrollments, teacher_enrollments)
# Evaluate all the criteria for each user for each alert
today = Time.now.beginning_of_day
alert_checkers = {}
alerts.each do |alert|
student_ids.each do |user_id|
matches = true
alert.criteria.each do |criterion|
alert_checker = alert_checkers[criterion.criterion_type] ||= Alerts.const_get(criterion.criterion_type).new(course, student_ids, teacher_ids)
matches = !alert_checker.should_not_receive_message?(user_id, criterion.threshold.to_i)
break unless matches
end
cache_key = [alert, user_id].cache_key
if matches
last_sent = Rails.cache.fetch(cache_key)
if last_sent.blank?
elsif alert.repetition.blank?
matches = false
else
matches = last_sent + alert.repetition.days <= today
end
end
if matches
Rails.cache.write(cache_key, today)
send_alert(alert, alert.resolve_recipients(user_id, teacher_student_mapper.teachers_for_student(user_id)), student_enrollments.to_ary.find { |enrollment| enrollment.user_id == user_id } )
end
end
end
end
def self.send_alert(alert, user_ids, student_enrollment)
notification = Notification.by_name("Alert")
notification.create_message(alert, user_ids, {:asset_context => student_enrollment})
end
end
end

View File

@ -94,9 +94,9 @@ if Delayed::Stats.enabled?
end
end
Delayed::Periodic.cron 'Alert.process', '30 11 * * *', :priority => Delayed::LOW_PRIORITY do
Delayed::Periodic.cron 'Alerts::DelayedAlertSender.process', '30 11 * * *', :priority => Delayed::LOW_PRIORITY do
Shard.with_each_shard do
Alert.process
Alerts::DelayedAlertSender.process
end
end

View File

@ -71,470 +71,5 @@ describe Alert do
alert.save.should be_false
end
end
context "basic evaluation" do
it "should not trigger any alerts for unpublished courses" do
course = mock('Course')
course.stubs(:available?, false)
Notification.any_instance.expects(:create_message).never
Alert.evaluate_for_course(course, nil)
end
it "should not trigger any alerts for courses with no alerts" do
course = mock('Course')
course.stubs(:available?).returns(true)
course.stubs(:alerts).returns(stub(:all => []))
Notification.any_instance.expects(:create_message).never
Alert.evaluate_for_course(course, nil)
end
it "should not trigger any alerts when there are no students in the class" do
course = Account.default.courses.create!
course.offer!
course.alerts.create!(:recipients => [:student], :criteria => [{:criterion_type => 'Interaction', :threshold => 7}])
Notification.any_instance.expects(:create_message).never
Alert.evaluate_for_course(course, nil)
end
it "should not trigger any alerts when there are no teachers in the class" do
course_with_student(:active_course => 1)
@course.alerts.create!(:recipients => [:student], :criteria => [{:criterion_type => 'Interaction', :threshold => 7}])
Notification.any_instance.expects(:create_message).never
Alert.evaluate_for_course(@course, nil)
end
it "should not trigger any alerts in subsequent courses" do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
@course.alerts.create!(:recipients => [:student], :criteria => [{:criterion_type => 'Interaction', :threshold => 7}])
@course.start_at = Time.now - 30.days
account_alerts = []
Alert.evaluate_for_course(@course, account_alerts)
account_alerts.should == []
end
end
context 'repetition' do
it "should not keep sending alerts when repetition is nil" do
enable_cache do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
@course.alerts.create!(:recipients => [:student], :criteria => [{:criterion_type => 'Interaction', :threshold => 7}])
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with(anything, [@user.id], anything).once
Alert.evaluate_for_course(@course, nil)
Alert.evaluate_for_course(@course, nil)
end
end
it "should not keep sending alerts when run on the same day" do
enable_cache do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
@course.alerts.create!(:recipients => [:student], :repetition => 1, :criteria => [{:criterion_type => 'Interaction', :threshold => 7}])
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with(anything, [@user.id], anything).once
Alert.evaluate_for_course(@course, nil)
Alert.evaluate_for_course(@course, nil)
end
end
it "should keep sending alerts for daily repetition" do
enable_cache do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
alert = @course.alerts.create!(:recipients => [:student], :repetition => 1, :criteria => [{:criterion_type => 'Interaction', :threshold => 7}])
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with(anything, [@user.id], anything).twice
Alert.evaluate_for_course(@course, nil)
# update sent_at
Rails.cache.write([alert, @user.id].cache_key, (Time.now - 1.day).beginning_of_day)
Alert.evaluate_for_course(@course, nil)
end
end
end
context 'interaction' do
it "should not alert for new courses" do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
Notification.any_instance.expects(:create_message).never
Alert.evaluate_for_course(@course, nil)
end
it "should alert for old courses" do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with(anything, [@user.id], anything)
Alert.evaluate_for_course(@course, nil)
end
it "should not alert for submission comments" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user)
SubmissionComment.create!(:submission => @submission, :comment => 'some comment', :author => @teacher, :recipient => @user)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
Notification.any_instance.expects(:create_message).never
Alert.evaluate_for_course(@course, nil)
end
it "should alert for old submission comments" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user)
SubmissionComment.create!(:submission => @submission, :comment => 'some comment', :author => @teacher, :recipient => @user) do |sc|
sc.created_at = Time.now - 30.days
end
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with(anything, [@user.id], anything)
Alert.evaluate_for_course(@course, nil)
end
it "should not alert for conversation messages" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@conversation = @teacher.initiate_conversation([@user])
@conversation.add_message("hello")
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
Notification.any_instance.expects(:create_message).never
Alert.evaluate_for_course(@course, nil)
end
it "should alert for old conversation messages" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@conversation = @teacher.initiate_conversation([@student, user])
message = @conversation.add_message("hello")
message.created_at = Time.now - 30.days
message.save!
alert = @course.alerts.build(:recipients => [:student], :repetition => 1)
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
Alert.expects(:send_alert).with(anything, [ @student.id ], anything).twice
Alert.evaluate_for_course(@course, nil)
# create a generated message
@conversation.add_participants([user])
@conversation.messages.length.should == 2
# it should still alert, ignoring the new message
# update sent_at so it will send again
Rails.cache.write([alert, @student.id].cache_key, (Time.now - 5.days).beginning_of_day)
Alert.evaluate_for_course(@course, nil)
end
end
it "memoizes alert checker creation" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user)
SubmissionComment.create!(:submission => @submission, :comment => 'some comment', :author => @teacher, :recipient => @user) do |sc|
sc.created_at = Time.now - 30.days
end
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
mock_interaction = stub(should_not_receive_message?: true)
Alerts::Interaction.expects(:new).once.returns(mock_interaction)
Alert.evaluate_for_course(@course, [alert])
end
context 'ungraded count' do
it "should not alert for no submissions" do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedCount', :threshold => 1)
alert.save!
Notification.any_instance.expects(:create_message).never
Alert.evaluate_for_course(@course, nil)
end
it "should alert" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user, :body => 'body')
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedCount', :threshold => 1)
alert.save!
@mock_notification.expects(:create_message).with(anything, [@user.id], anything)
Alert.evaluate_for_course(@course, nil)
end
end
context 'ungraded timespan' do
it "should not alert for no submissions" do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedTimespan', :threshold => 1)
alert.save!
Notification.any_instance.expects(:create_message).never
Alert.evaluate_for_course(@course, nil)
end
it "should not alert for submission within the threshold" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user, :body => 'body')
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedTimespan', :threshold => 7)
alert.save!
Notification.any_instance.expects(:create_message).never
Alert.evaluate_for_course(@course, nil)
end
it "should alert" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user, :body => 'body')
@submission.update_attribute(:submitted_at, Time.now - 30.days);
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedTimespan', :threshold => 7)
alert.save!
@mock_notification.expects(:create_message).with(anything, [@user.id], anything)
Alert.evaluate_for_course(@course, nil)
end
it "should alert for multiple submissions when one matches and one doesn't" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user, :body => 'body')
@submission.update_attribute(:submitted_at, Time.now - 30.days);
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user, :body => 'body')
@submission.update_attribute(:submitted_at, Time.now - 30.days);
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedTimespan', :threshold => 7)
alert.save!
@mock_notification.expects(:create_message).with(anything, [@user.id], anything)
Alert.evaluate_for_course(@course, nil)
end
end
context 'user notes' do
before do
course_with_teacher(:active_all => 1)
root_account = @course.root_account
root_account.enable_user_notes = true
root_account.save!
end
it "should not alert for new courses" do
student_in_course(:active_all => 1)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UserNote', :threshold => 7)
alert.save!
Notification.any_instance.expects(:create_message).never
Alert.evaluate_for_course(@course, nil)
end
it "should alert for old courses" do
student_in_course(:active_all => 1)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UserNote', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with(anything, [@user.id], anything)
Alert.evaluate_for_course(@course, nil)
end
it "should not alert when a note exists" do
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
UserNote.create!(:creator => @teacher, :user => @user)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UserNote', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
Notification.any_instance.expects(:create_message).never
Alert.evaluate_for_course(@course, nil)
end
it "should alert when an old note exists" do
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
UserNote.create!(:creator => @teacher, :user => @user) { |un| un.created_at = Time.now - 30.days }
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UserNote', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with(anything, [@user.id], anything)
Alert.evaluate_for_course(@course, nil)
end
end
end
context "notification alert info" do
before do
Notification.create!(:name => 'Alert')
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@pseudonym = mock('Pseudonym')
@pseudonym.stubs(:destroyed?).returns(false)
Pseudonym.stubs(:find_by_user_id).returns(@pseudonym)
a = @user.communication_channels.create(:path => "a@example.com")
a.confirm!
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user, :body => 'body')
end
it "should tell you what the alert is about timespan" do
@submission.update_attribute(:submitted_at, Time.now - 30.days);
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedTimespan', :threshold => 7)
alert.save!
@mock_notification.expects(:create_message).with do |alert, _, _|
alert.criteria.first.criterion_type.should == 'UngradedTimespan'
end
Alert.evaluate_for_course(@course, nil)
end
it "should tell you what the alert is about count" do
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedCount', :threshold => 1)
alert.save!
@mock_notification.expects(:create_message).with do |alert, _, _|
alert.criteria.first.criterion_type.should == 'UngradedCount'
end
Alert.evaluate_for_course(@course, nil)
end
it "should tell you what the alert is about note" do
root_account = @course.root_account
root_account.enable_user_notes = true
root_account.save!
UserNote.create!(:creator => @teacher, :user => @user) { |un| un.created_at = Time.now - 30.days }
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UserNote', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with do |alert, _, _|
alert.criteria.first.criterion_type.should == 'UserNote'
end
Alert.evaluate_for_course(@course, nil)
end
it "should tell you what the alert is about interaction" do
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with do |alert, _, _|
alert.criteria.first.criterion_type.should == 'Interaction'
end
Alert.evaluate_for_course(@course, nil)
end
end
end

View File

@ -0,0 +1,494 @@
#
# Copyright (C) 2014 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 File.expand_path(File.dirname(__FILE__) + '../../../spec_helper.rb')
module Alerts
describe DelayedAlertSender do
before do
@mock_notification = Notification.new
Notification.stubs(:by_name).returns(@mock_notification)
end
context "basic evaluation" do
it "should not trigger any alerts for unpublished courses" do
course = mock('Course')
course.stubs(:available?, false)
Notification.any_instance.expects(:create_message).never
DelayedAlertSender.evaluate_for_course(course, nil)
end
it "should not trigger any alerts for courses with no alerts" do
course = mock('Course')
course.stubs(:available?).returns(true)
course.stubs(:alerts).returns(stub(:all => []))
Notification.any_instance.expects(:create_message).never
DelayedAlertSender.evaluate_for_course(course, nil)
end
it "should not trigger any alerts when there are no students in the class" do
course = Account.default.courses.create!
course.offer!
course.alerts.create!(:recipients => [:student], :criteria => [{:criterion_type => 'Interaction', :threshold => 7}])
Notification.any_instance.expects(:create_message).never
DelayedAlertSender.evaluate_for_course(course, nil)
end
it "should not trigger any alerts when there are no teachers in the class" do
course_with_student(:active_course => 1)
@course.alerts.create!(:recipients => [:student], :criteria => [{:criterion_type => 'Interaction', :threshold => 7}])
Notification.any_instance.expects(:create_message).never
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should not trigger any alerts in subsequent courses" do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
@course.alerts.create!(:recipients => [:student], :criteria => [{:criterion_type => 'Interaction', :threshold => 7}])
@course.start_at = Time.now - 30.days
account_alerts = []
DelayedAlertSender.evaluate_for_course(@course, account_alerts)
account_alerts.should == []
end
end
context 'repetition' do
it "should not keep sending alerts when repetition is nil" do
enable_cache do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
@course.alerts.create!(:recipients => [:student], :criteria => [{:criterion_type => 'Interaction', :threshold => 7}])
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with(anything, [@user.id], anything).once
DelayedAlertSender.evaluate_for_course(@course, nil)
DelayedAlertSender.evaluate_for_course(@course, nil)
end
end
it "should not keep sending alerts when run on the same day" do
enable_cache do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
@course.alerts.create!(:recipients => [:student], :repetition => 1, :criteria => [{:criterion_type => 'Interaction', :threshold => 7}])
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with(anything, [@user.id], anything).once
DelayedAlertSender.evaluate_for_course(@course, nil)
DelayedAlertSender.evaluate_for_course(@course, nil)
end
end
it "should keep sending alerts for daily repetition" do
enable_cache do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
alert = @course.alerts.create!(:recipients => [:student], :repetition => 1, :criteria => [{:criterion_type => 'Interaction', :threshold => 7}])
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with(anything, [@user.id], anything).twice
DelayedAlertSender.evaluate_for_course(@course, nil)
# update sent_at
Rails.cache.write([alert, @user.id].cache_key, (Time.now - 1.day).beginning_of_day)
DelayedAlertSender.evaluate_for_course(@course, nil)
end
end
end
context 'interaction' do
it "should not alert for new courses" do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
Notification.any_instance.expects(:create_message).never
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should alert for old courses" do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with(anything, [@user.id], anything)
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should not alert for submission comments" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user)
SubmissionComment.create!(:submission => @submission, :comment => 'some comment', :author => @teacher, :recipient => @user)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
Notification.any_instance.expects(:create_message).never
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should alert for old submission comments" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user)
SubmissionComment.create!(:submission => @submission, :comment => 'some comment', :author => @teacher, :recipient => @user) do |sc|
sc.created_at = Time.now - 30.days
end
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with(anything, [@user.id], anything)
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should not alert for conversation messages" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@conversation = @teacher.initiate_conversation([@user])
@conversation.add_message("hello")
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
Notification.any_instance.expects(:create_message).never
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should alert for old conversation messages" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@conversation = @teacher.initiate_conversation([@student, user])
message = @conversation.add_message("hello")
message.created_at = Time.now - 30.days
message.save!
alert = @course.alerts.build(:recipients => [:student], :repetition => 1)
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
DelayedAlertSender.expects(:send_alert).with(anything, [@student.id], anything).twice
DelayedAlertSender.evaluate_for_course(@course, nil)
# create a generated message
@conversation.add_participants([user])
@conversation.messages.length.should == 2
# it should still alert, ignoring the new message
# update sent_at so it will send again
Rails.cache.write([alert, @student.id].cache_key, (Time.now - 5.days).beginning_of_day)
DelayedAlertSender.evaluate_for_course(@course, nil)
end
end
it "memoizes alert checker creation" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user)
SubmissionComment.create!(:submission => @submission, :comment => 'some comment', :author => @teacher, :recipient => @user) do |sc|
sc.created_at = Time.now - 30.days
end
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
mock_interaction = stub(should_not_receive_message?: true)
Alerts::Interaction.expects(:new).once.returns(mock_interaction)
DelayedAlertSender.evaluate_for_course(@course, [alert])
end
context 'ungraded count' do
it "should not alert for no submissions" do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedCount', :threshold => 1)
alert.save!
Notification.any_instance.expects(:create_message).never
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should alert" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user, :body => 'body')
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedCount', :threshold => 1)
alert.save!
@mock_notification.expects(:create_message).with(anything, [@user.id], anything)
DelayedAlertSender.evaluate_for_course(@course, nil)
end
end
context 'ungraded timespan' do
it "should not alert for no submissions" do
course_with_teacher(:active_all => 1)
student_in_course(:active_all => 1)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedTimespan', :threshold => 1)
alert.save!
Notification.any_instance.expects(:create_message).never
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should not alert for submission within the threshold" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user, :body => 'body')
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedTimespan', :threshold => 7)
alert.save!
Notification.any_instance.expects(:create_message).never
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should alert" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user, :body => 'body')
@submission.update_attribute(:submitted_at, Time.now - 30.days);
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedTimespan', :threshold => 7)
alert.save!
@mock_notification.expects(:create_message).with(anything, [@user.id], anything)
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should alert for multiple submissions when one matches and one doesn't" do
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user, :body => 'body')
@submission.update_attribute(:submitted_at, Time.now - 30.days);
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user, :body => 'body')
@submission.update_attribute(:submitted_at, Time.now - 30.days);
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedTimespan', :threshold => 7)
alert.save!
@mock_notification.expects(:create_message).with(anything, [@user.id], anything)
DelayedAlertSender.evaluate_for_course(@course, nil)
end
end
context 'user notes' do
before do
course_with_teacher(:active_all => 1)
root_account = @course.root_account
root_account.enable_user_notes = true
root_account.save!
end
it "should not alert for new courses" do
student_in_course(:active_all => 1)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UserNote', :threshold => 7)
alert.save!
Notification.any_instance.expects(:create_message).never
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should alert for old courses" do
student_in_course(:active_all => 1)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UserNote', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with(anything, [@user.id], anything)
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should not alert when a note exists" do
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
::UserNote.create!(:creator => @teacher, :user => @user)
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UserNote', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
Notification.any_instance.expects(:create_message).never
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should alert when an old note exists" do
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
::UserNote.create!(:creator => @teacher, :user => @user) { |un| un.created_at = Time.now - 30.days }
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UserNote', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with(anything, [@user.id], anything)
DelayedAlertSender.evaluate_for_course(@course, nil)
end
end
context "notification alert info" do
before do
Notification.create!(:name => 'Alert')
course_with_teacher(:active_all => 1)
@teacher = @user
@user = nil
student_in_course(:active_all => 1)
@pseudonym = mock('Pseudonym')
@pseudonym.stubs(:destroyed?).returns(false)
Pseudonym.stubs(:find_by_user_id).returns(@pseudonym)
a = @user.communication_channels.create(:path => "a@example.com")
a.confirm!
@assignment = @course.assignments.new(:title => "some assignment")
@assignment.workflow_state = "published"
@assignment.save
@submission = @assignment.submit_homework(@user, :body => 'body')
end
it "should tell you what the alert is about timespan" do
@submission.update_attribute(:submitted_at, Time.now - 30.days);
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedTimespan', :threshold => 7)
alert.save!
@mock_notification.expects(:create_message).with do |alert, _, _|
alert.criteria.first.criterion_type.should == 'UngradedTimespan'
end
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should tell you what the alert is about count" do
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UngradedCount', :threshold => 1)
alert.save!
@mock_notification.expects(:create_message).with do |alert, _, _|
alert.criteria.first.criterion_type.should == 'UngradedCount'
end
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should tell you what the alert is about note" do
root_account = @course.root_account
root_account.enable_user_notes = true
root_account.save!
::UserNote.create!(:creator => @teacher, :user => @user) { |un| un.created_at = Time.now - 30.days }
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'UserNote', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with do |alert, _, _|
alert.criteria.first.criterion_type.should == 'UserNote'
end
DelayedAlertSender.evaluate_for_course(@course, nil)
end
it "should tell you what the alert is about interaction" do
alert = @course.alerts.build(:recipients => [:student])
alert.criteria.build(:criterion_type => 'Interaction', :threshold => 7)
alert.save!
@course.start_at = Time.now - 30.days
@mock_notification.expects(:create_message).with do |alert, _, _|
alert.criteria.first.criterion_type.should == 'Interaction'
end
DelayedAlertSender.evaluate_for_course(@course, nil)
end
end
end
end