PageRenderTime 45ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/app/models/account_notification.rb

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