canvas-lms/app/models/notification.rb

583 lines
20 KiB
Ruby

#
# Copyright (C) 2011 - 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 Notification < ActiveRecord::Base
self.shard_category = :unsharded
include Workflow
include TextHelper
TYPES_TO_SHOW_IN_FEED = [
# Assignment
"Assignment Created",
"Assignment Changed",
"Assignment Due Date Changed",
"Assignment Due Date Override Changed",
# Submissions / Grading
"Assignment Graded",
"Assignment Submitted Late",
"Grade Weight Changed",
"Group Assignment Submitted Late",
# Testing
"Show In Feed",
].freeze
FREQ_IMMEDIATELY = 'immediately'
FREQ_DAILY = 'daily'
FREQ_WEEKLY = 'weekly'
FREQ_NEVER = 'never'
has_many :messages
has_many :notification_policies, :dependent => :destroy
before_save :infer_default_content
scope :to_show_in_feed, -> { where("messages.category='TestImmediately' OR messages.notification_name IN (?)", TYPES_TO_SHOW_IN_FEED) }
validates_uniqueness_of :name
after_create { self.class.reset_cache! }
workflow do
state :active do
event :deactivate, :transitions_to => :inactive
end
state :inactive do
event :reactivate, :transitions_to => :active
end
end
def self.all_cached
@all ||= self.all.to_a.each(&:readonly!)
end
def self.find(id, options = {})
(@all_by_id ||= all_cached.index_by(&:id))[id.to_i] or raise ActiveRecord::RecordNotFound
end
def self.reset_cache!
@all = nil
@all_by_id = nil
end
def duplicate
notification = self.clone
notification.id = self.id
notification.send(:remove_instance_variable, :@new_record)
notification
end
def infer_default_content
self.subject ||= t(:no_subject, "No Subject")
end
protected :infer_default_content
# Public: create (and dispatch, and queue delayed) a message
# for this notication, associated with the given asset, sent to the given recipients
#
# asset - what the message applies to. An assignment, a discussion, etc.
# to_list - a list of who to send the message to. the list can contain Users, User ids, or communication channels
# options - a hash of extra options to merge with the options used to build the Message
#
def create_message(asset, to_list, options={})
messages = [] if Rails.env.test?
preload_asset_roles_if_needed(asset)
to_list.each do |to|
msgs = NotificationMessageCreator.new(self, asset, options.merge(:to_list => to)).create_message
messages.concat msgs if Rails.env.test?
to.send(:clear_association_cache) if to.is_a?(User)
end
messages
end
TYPES_TO_PRELOAD_CONTEXT_ROLES = ["Assignment Created", "Assignment Due Date Changed"].freeze
def preload_asset_roles_if_needed(asset)
if TYPES_TO_PRELOAD_CONTEXT_ROLES.include?(self.name)
case asset
when Assignment
ActiveRecord::Associations::Preloader.new.preload(asset, :assignment_overrides)
asset.context.preload_user_roles!
when AssignmentOverride
ActiveRecord::Associations::Preloader.new.preload(asset.assignment, :assignment_overrides)
asset.assignment.context.preload_user_roles!
end
end
end
def category_spaceless
(self.category || "None").gsub(/\s/, "_")
end
def sort_order
case category
when 'Announcement'
1
when 'Grading'
3
when 'Late Grading'
4
when 'Registration'
5
when 'Invitation'
6
when 'Grading Policies'
7
when 'Submission Comment'
8
else
9
end
end
def self.types_to_show_in_feed
TYPES_TO_SHOW_IN_FEED
end
def show_in_feed?
self.category == "TestImmediately" || Notification.types_to_show_in_feed.include?(self.name)
end
def registration?
return self.category == "Registration"
end
def migration?
return self.category == "Migration"
end
def summarizable?
return !self.registration? && !self.migration?
end
def dashboard?
return ["Migration", "Registration", "Summaries", "Alert"].include?(self.category) == false
end
def category_slug
(self.category || "").gsub(/ /, "_").gsub(/[^\w]/, "").downcase
end
# if user is given, categories that aren't relevant to that user will be
# filtered out.
def self.dashboard_categories(user = nil)
seen_types = {}
res = []
Notification.all_cached.each do |n|
if !seen_types[n.category] && (user.nil? || n.relevant_to_user?(user))
seen_types[n.category] = true
res << n if n.category && n.dashboard?
end
end
res.sort_by{|n| n.category == "Other" ? CanvasSort::Last : n.category }
end
# Return a hash with information for a related user option if one exists.
def related_user_setting(user, root_account)
if self.category == 'Grading' && root_account.settings[:allow_sending_scores_in_emails] != false
{
name: :send_scores_in_emails,
value: user.preferences[:send_scores_in_emails],
label: t(<<-EOS),
Include scores when alerting about grades.
If your email is not an institution email this means sensitive content will be sent outside of the institution.
EOS
id: "cat_#{self.id}_option",
}
end
end
def default_frequency(_user = nil)
# user arg is used in plugins
case category
when 'All Submissions'
FREQ_NEVER
when 'Announcement'
FREQ_IMMEDIATELY
when 'Announcement Created By You'
FREQ_NEVER
when 'Calendar'
FREQ_NEVER
when 'Student Appointment Signups'
FREQ_NEVER
when 'Appointment Availability'
FREQ_IMMEDIATELY
when 'Appointment Signups'
FREQ_IMMEDIATELY
when 'Appointment Cancelations'
FREQ_IMMEDIATELY
when 'Course Content'
FREQ_NEVER
when 'Files'
FREQ_NEVER
when 'Discussion'
FREQ_NEVER
when 'DiscussionEntry'
FREQ_DAILY
when 'Announcement Reply'
FREQ_NEVER
when 'Due Date'
FREQ_WEEKLY
when 'Grading'
FREQ_IMMEDIATELY
when 'Grading Policies'
FREQ_WEEKLY
when 'Invitation'
FREQ_IMMEDIATELY
when 'Late Grading'
FREQ_DAILY
when 'Membership Update'
FREQ_DAILY
when 'Other'
FREQ_DAILY
when 'Registration'
FREQ_IMMEDIATELY
when 'Migration'
FREQ_IMMEDIATELY
when 'Submission Comment'
FREQ_DAILY
when 'Reminder'
FREQ_DAILY
when 'TestImmediately'
FREQ_IMMEDIATELY
when 'TestDaily'
FREQ_DAILY
when 'TestWeekly'
FREQ_WEEKLY
when 'TestNever'
FREQ_NEVER
when 'Conversation Message'
FREQ_IMMEDIATELY
when 'Added To Conversation'
FREQ_IMMEDIATELY
when 'Conversation Created'
FREQ_NEVER
when 'Recording Ready'
FREQ_IMMEDIATELY
else
FREQ_DAILY
end
end
# TODO i18n: show the localized notification name in the dashboard (or
# wherever), even if we continue to store the english string in the db
# (it's actually just the titleized message template filename)
def names
t 'names.account_user_notification', 'Account User Notification'
t 'names.account_user_registration', 'Account User Registration'
t 'names.assignment_changed', 'Assignment Changed'
t 'names.assignment_created', 'Assignment Created'
t 'names.assignment_due_date_changed', 'Assignment Due Date Changed'
t 'names.assignment_due_date_override_changed', 'Assignment Due Date Override Changed'
t 'names.assignment_graded', 'Assignment Graded'
t 'names.assignment_resubmitted', 'Assignment Resubmitted'
t 'names.assignment_submitted', 'Assignment Submitted'
t 'names.assignment_submitted_late', 'Assignment Submitted Late'
t 'names.collaboration_invitation', 'Collaboration Invitation'
t 'names.confirm_email_communication_channel', 'Confirm Email Communication Channel'
t 'names.confirm_registration', 'Confirm Registration'
t 'names.confirm_sms_communication_channel', 'Confirm Sms Communication Channel'
t 'names.content_export_failed', 'Content Export Failed'
t 'names.content_export_finished', 'Content Export Finished'
t 'names.enrollment_accepted', 'Enrollment Accepted'
t 'names.enrollment_invitation', 'Enrollment Invitation'
t 'names.enrollment_notification', 'Enrollment Notification'
t 'names.enrollment_registration', 'Enrollment Registration'
t 'names.event_date_changed', 'Event Date Changed'
t 'names.forgot_password', 'Forgot Password'
t 'names.grade_weight_changed', 'Grade Weight Changed'
t 'names.group_assignment_submitted_late', 'Group Assignment Submitted Late'
t 'names.group_membership_accepted', 'Group Membership Accepted'
t 'names.group_membership_rejected', 'Group Membership Rejected'
t 'names.merge_email_communication_channel', 'Merge Email Communication Channel'
t 'names.migration_import_failed', 'Migration Import Failed'
t 'names.migration_import_finished', 'Migration Import Finished'
t 'names.new_account_user', 'New Account User'
t 'names.new_announcement', 'New Announcement'
t 'names.announcement_created_by_you', 'Announcement Created By You'
t 'names.announcement_reply', 'Announcement Reply'
t 'names.new_context_group_membership', 'New Context Group Membership'
t 'names.new_context_group_membership_invitation', 'New Context Group Membership Invitation'
t 'names.new_course', 'New Course'
t 'names.new_discussion_entry', 'New Discussion Entry'
t 'names.new_discussion_topic', 'New Discussion Topic'
t 'names.new_event_created', 'New Event Created'
t 'names.new_file_added', 'New File Added'
t 'names.new_files_added', 'New Files Added'
t 'names.new_student_organized_group', 'New Student Organized Group'
t 'names.new_user', 'New User'
t 'names.pseudonym_registration', 'Pseudonym Registration'
t 'names.pseudonym_registration_done', 'Pseudonym Registration Done'
t 'names.report_generated', 'Report Generated'
t 'names.report_generation_failed', 'Report Generation Failed'
t 'names.rubric_assessment_invitation', 'Rubric Assessment Invitation'
t 'names.rubric_assessment_submission_reminder', 'Rubric Assessment Submission Reminder'
t 'names.rubric_association_created', 'Rubric Association Created'
t 'names.conversation_message', 'Conversation Message'
t 'names.added_to_conversation', 'Added To Conversation'
t 'names.conversation_created', 'Conversation Created'
t 'names.submission_comment', 'Submission Comment'
t 'names.submission_comment_for_teacher', 'Submission Comment For Teacher'
t 'names.submission_grade_changed', 'Submission Grade Changed'
t 'names.submission_graded', 'Submission Graded'
t 'names.summaries', 'Summaries'
t 'names.updated_wiki_page', 'Updated Page'
t 'names.web_conference_invitation', 'Web Conference Invitation'
t 'names.alert', 'Alert'
t 'names.appointment_canceled_by_user', 'Appointment Canceled By User'
t 'names.appointment_deleted_for_user', 'Appointment Deleted For User'
t 'names.appointment_group_deleted', 'Appointment Group Deleted'
t 'names.appointment_group_published', 'Appointment Group Published'
t 'names.appointment_group_updated', 'Appointment Group Updated'
t 'names.appointment_reserved_by_user', 'Appointment Reserved By User'
t 'names.appointment_reserved_for_user', 'Appointment Reserved For User'
t 'names.submission_needs_grading', 'Submission Needs Grading'
t 'names.web_conference_recording_ready', 'Web Conference Recording Ready'
t 'names.blueprint_sync_complete', 'Blueprint Sync Complete'
t 'names.blueprint_content_added', 'Blueprint Content Added'
end
# TODO: i18n ... show these anywhere we show the category today
def category_names
t 'categories.all_submissions', 'All Submissions'
t 'categories.announcement', 'Announcement'
t 'categories.calendar', 'Calendar'
t 'categories.student_appointment_signups', 'Student Appointment Signups'
t 'categories.appointment_availability', 'Appointment Availability'
t 'categories.appointment_signups', 'Appointment Signups'
t 'categories.appointment_cancelations', 'Appointment Cancellations'
t 'categories.course_content', 'Course Content'
t 'categories.discussion', 'Discussion'
t 'categories.discussion_entry', 'DiscussionEntry'
t 'categories.due_date', 'Due Date'
t 'categories.files', 'Files'
t 'categories.grading', 'Grading'
t 'categories.grading_policies', 'Grading Policies'
t 'categories.invitiation', 'Invitation'
t 'categories.late_grading', 'Late Grading'
t 'categories.membership_update', 'Membership Update'
t 'categories.other', 'Other'
t 'categories.registration', 'Registration'
t 'categories.migration', 'Migration'
t 'categories.reminder', 'Reminder'
t 'categories.submission_comment', 'Submission Comment'
t 'categories.recording_ready', 'Recording Ready'
t 'categories.blueprint', 'Blueprint'
end
# Translatable display text to use when representing the category to the user.
# NOTE: If you add a new notification category, update the mapping file for groupings to show up
# on notification preferences page. /app/coffeescripts/notifications/NotificationGroupMappings.coffee
def category_display_name
case category
when 'Announcement'
t(:announcement_display, 'Announcement')
when 'Announcement Created By You'
t(:announcement_created_by_you_display, 'Announcement Created By You')
when 'Course Content'
t(:course_content_display, 'Course Content')
when 'Files'
t(:files_display, 'Files')
when 'Discussion'
t(:discussion_display, 'Discussion')
when 'DiscussionEntry'
t(:discussion_post_display, 'Discussion Post')
when 'Due Date'
t(:due_date_display, 'Due Date')
when 'Grading'
t(:grading_display, 'Grading')
when 'Late Grading'
t(:late_grading_display, 'Late Grading')
when 'All Submissions'
t(:all_submissions_display, 'All Submissions')
when 'Submission Comment'
t(:submission_comment_display, 'Submission Comment')
when 'Grading Policies'
t(:grading_policies_display, 'Grading Policies')
when 'Invitation'
t(:invitation_display, 'Invitation')
when 'Other'
t(:other_display, 'Administrative Notifications')
when 'Calendar'
t(:calendar_display, 'Calendar')
when 'Student Appointment Signups'
t(:student_appointment_display, 'Student Appointment Signups')
when 'Appointment Availability'
t(:appointment_availability_display, 'Appointment Availability')
when 'Appointment Signups'
t(:appointment_signups_display, 'Appointment Signups')
when 'Appointment Cancelations'
t(:appointment_cancelations_display, 'Appointment Cancellations')
when 'Conversation Message'
t(:conversation_message_display, 'Conversation Message')
when 'Added To Conversation'
t(:added_to_conversation_display, 'Added To Conversation')
when 'Conversation Created'
t(:conversation_created_display, 'Conversations Created By Me')
when 'Membership Update'
t(:membership_update_display, 'Membership Update')
when 'Reminder'
t(:reminder_display, 'Reminder')
when 'Recording Ready'
t(:recording_ready_display, 'Recording Ready')
when 'Blueprint'
t(:blueprint_display, 'Blueprint Sync')
else
t(:missing_display_display, "For %{category} notifications", :category => category)
end
end
def category_description
case category
when 'Announcement'
t(:announcement_description, 'New Announcement in your course')
when 'Announcement Created By You'
mt(:announcement_created_by_you_description, <<-EOS)
* Announcements created by you
* Replies to announcements you've created
EOS
when 'Course Content'
mt(:course_content_description, <<-EOS)
Change to course content:
* Page content
* Quiz content
* Assignment content
EOS
when 'Files'
t(:files_description, 'New file added to your course')
when 'Discussion'
t(:discussion_description, 'New discussion topic in your course')
when 'DiscussionEntry'
t(:discussion_post_description, "New discussion post in a topic you're subscribed to")
when 'Due Date'
t(:due_date_description, 'Assignment due date change')
when 'Grading'
mt(:grading_description, <<-EOS)
Includes:
* Assignment/submission grade entered/changed
* Un-muted assignment grade
* Grade weight changed
EOS
when 'Late Grading'
mt(:late_grading_description, <<-EOS)
*Instructor and Admin only:*
Late assignment submission
EOS
when 'All Submissions'
mt(:all_submissions_description, <<-EOS)
*Instructor and Admin only:*
Assignment (except quizzes) submission/resubmission
EOS
when 'Submission Comment'
t(:submission_comment_description, "Assignment submission comment")
when 'Grading Policies'
t(:grading_policies_description, 'Course grading policy change')
when 'Invitation'
mt(:invitation_description, <<-EOS)
Invitation for:
* Web conference
* Group
* Collaboration
* Peer Review & reminder
EOS
when 'Other'
mt(:other_description, <<-EOS)
*Instructor and Admin only:*
* Course enrollment
* Report generated
* Content export
* Migration report
* New account user
* New student group
EOS
when 'Calendar'
t(:calendar_description, 'New and changed items on your course calendar')
when 'Student Appointment Signups'
mt(:student_appointment_description, <<-EOS)
*Instructor and Admin only:*
Student appointment sign-up
EOS
when 'Appointment Availability'
t('New appointment timeslots are available for signup')
when 'Appointment Signups'
t(:appointment_signups_description, 'New appointment on your calendar')
when 'Appointment Cancelations'
t(:appointment_cancelations_description, 'Appointment cancellation')
when 'Conversation Message'
t(:conversation_message_description, 'New Inbox messages')
when 'Added To Conversation'
t(:added_to_conversation_description, 'You are added to a conversation')
when 'Conversation Created'
t(:conversation_created_description, 'You created a conversation')
when 'Recording Ready'
t(:web_conference_recording_ready, 'A conference recording is ready')
when 'Membership Update'
mt(:membership_update_description, <<-EOS)
*Admin only: pending enrollment activated*
* Group enrollment
* accepted/rejected
EOS
when 'Blueprint'
mt(:blueprint_description, <<-EOS)
*Instructor and Admin only:*
Content was synced from a blueprint course to associated courses
EOS
else
t(:missing_description_description, "For %{category} notifications", :category => category)
end
end
def display_category
case category
when 'Student Appointment Signups', 'Appointment Availability',
'Appointment Signups', 'Appointment Cancelations'
'Calendar'
else
category
end
end
def type_name
return category
end
def relevant_to_user?(user)
case category
when 'All Submissions', 'Late Grading'
user.teacher_enrollments.count > 0 || user.ta_enrollments.count > 0
when 'Added To Conversation', 'Conversation Message', 'Conversation Created'
!user.disabled_inbox?
else
true
end
end
end