forked from instructure/canvas-lms
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaccount_notification.rb
More file actions
131 lines (112 loc) · 6.08 KB
/
Copy pathaccount_notification.rb
File metadata and controls
131 lines (112 loc) · 6.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
class AccountNotification < ActiveRecord::Base
validates_presence_of :start_at, :end_at, :subject, :message, :account_id
validate :validate_dates
belongs_to :account, :touch => true
belongs_to :user
has_many :account_notification_roles, dependent: :destroy
validates_length_of :message, :maximum => maximum_text_length, :allow_nil => false, :allow_blank => false
sanitize_field :message, CanvasSanitize::SANITIZE
ACCOUNT_SERVICE_NOTIFICATION_FLAGS = %w[account_survey_notifications]
validates_inclusion_of :required_account_service, in: ACCOUNT_SERVICE_NOTIFICATION_FLAGS, allow_nil: true
validates_inclusion_of :months_in_display_cycle, in: 1..48, allow_nil: true
def validate_dates
if self.start_at && self.end_at
errors.add(:end_at, t('errors.invalid_account_notification_end_at', "Account notification end time precedes start time")) if self.end_at < self.start_at
end
end
def self.for_user_and_account(user, account)
if account.site_admin?
current = self.for_account(account)
else
sub_account_ids = UserAccountAssociation.where(user: user).joins(:account).where('COALESCE(accounts.root_account_id,accounts.id)=?', account).pluck(:account_id)
current = self.for_account(account, sub_account_ids)
end
user_role_ids = {}
current.select! do |announcement|
# use role.id instead of role_id to trigger Role#id magic for built in
# roles. try(:id) because the AccountNotificationRole may have an
# explicitly nil role_id to indicate the announcement's intended for
# users not enrolled in any courses
role_ids = announcement.account_notification_roles.map{ |anr| anr.role.try(:id) }
unless role_ids.empty? || user_role_ids.key?(announcement.account_id)
# choose enrollments and account users to inspect
if announcement.account.site_admin?
enrollments = user.enrollments.shard(user).active.distinct.select(:role_id)
account_users = user.account_users.shard(user).distinct.select(:role_id)
else
enrollments = user.enrollments_for_account_and_sub_accounts(account).select(:role_id)
account_users = account.all_account_users_for(user)
end
# preload role objects for those enrollments and account users
ActiveRecord::Associations::Preloader.new.preload(enrollments, [:role])
ActiveRecord::Associations::Preloader.new.preload(account_users, [:role])
# map to role ids. user role.id instead of role_id to trigger Role#id
# magic for built in roles. announcements intended for users not
# enrolled in any courses have the NilEnrollment role type
user_role_ids[announcement.account_id] = enrollments.map{ |e| e.role.id }
user_role_ids[announcement.account_id] = [nil] if user_role_ids[announcement.account_id].empty?
user_role_ids[announcement.account_id] |= account_users.map{ |au| au.role.id }
end
role_ids.empty? || (role_ids & user_role_ids[announcement.account_id]).present?
end
user.shard.activate do
closed_ids = user.preferences[:closed_notifications] || []
# If there are ids marked as 'closed' that are no longer
# applicable, they probably need to be cleared out.
current_ids = current.map(&:id)
if !(closed_ids - current_ids).empty?
closed_ids = user.preferences[:closed_notifications] &= current_ids
user.save!
end
current.reject! { |announcement| closed_ids.include?(announcement.id) }
# filter out announcements that have a periodic cycle of display,
# and the user isn't in the set of users to display it to this month (based
# on user id)
current.reject! do |announcement|
if months_in_period = announcement.months_in_display_cycle
!self.display_for_user?(user.id, months_in_period)
end
end
roles = user.enrollments.shard(user).active.distinct.pluck(:type)
if roles == ['StudentEnrollment'] && !account.include_students_in_global_survey?
current.reject! { |announcement| announcement.required_account_service == 'account_survey_notifications' }
end
end
current
end
def self.for_account(account, sub_account_ids=nil)
# Refreshes every 10 minutes at the longest
sub_account_ids_hash = Digest::MD5.hexdigest sub_account_ids.try(:sort).to_s
Rails.cache.fetch(['account_notifications3', account, sub_account_ids_hash].cache_key, expires_in: 10.minutes) do
now = Time.now.utc
# we always check the given account for the flag, even if the announcement is from the site_admin account
# this allows us to make a global announcement that is filtered to only accounts with this flag
enabled_flags = ACCOUNT_SERVICE_NOTIFICATION_FLAGS & account.allowed_services_hash.keys.map(&:to_s)
account_ids = account.account_chain(include_site_admin: true).map(&:id)
if sub_account_ids
account_ids += sub_account_ids
account_ids.uniq!
end
Shard.partition_by_shard(account_ids) do |a|
AccountNotification.where("account_id IN (?) AND start_at <? AND end_at>?", a, now, now).
where("required_account_service IS NULL OR required_account_service IN (?)", enabled_flags).
order('start_at DESC').
preload(:account, account_notification_roles: :role)
end
end
end
def self.default_months_in_display_cycle
Setting.get("account_notification_default_months_in_display_cycle", "9").to_i
end
# private
def self.display_for_user?(user_id, months_in_period, current_time = Time.now.utc)
# we just need a stable reference point, doesn't matter what it is, so
# let's use unix epoch
start_time = Time.at(0).utc
months_since_start_time = (current_time.year - start_time.year) * 12 + (current_time.month - start_time.month)
periods_since_start_time = months_since_start_time / months_in_period
months_into_current_period = months_since_start_time % months_in_period
mod_value = (Random.new(periods_since_start_time).rand(months_in_period) + months_into_current_period) % months_in_period
user_id % months_in_period == mod_value
end
end