PageRenderTime 34ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/app/services/notification_recipient_service.rb

https://gitlab.com/kivlor/gitlab-ce
Ruby | 302 lines | 209 code | 65 blank | 28 comment | 8 complexity | 9773b705abaff99059c9529bca89dd0e MD5 | raw file
  1. #
  2. # Used by NotificationService to determine who should receive notification
  3. #
  4. module NotificationRecipientService
  5. def self.notifiable_users(users, *args)
  6. users.compact.map { |u| NotificationRecipient.new(u, *args) }.select(&:notifiable?).map(&:user)
  7. end
  8. def self.notifiable?(user, *args)
  9. NotificationRecipient.new(user, *args).notifiable?
  10. end
  11. def self.build_recipients(*a)
  12. Builder::Default.new(*a).notification_recipients
  13. end
  14. def self.build_new_note_recipients(*a)
  15. Builder::NewNote.new(*a).notification_recipients
  16. end
  17. module Builder
  18. class Base
  19. def initialize(*)
  20. raise 'abstract'
  21. end
  22. def build!
  23. raise 'abstract'
  24. end
  25. def filter!
  26. recipients.select!(&:notifiable?)
  27. end
  28. def acting_user
  29. current_user
  30. end
  31. def target
  32. raise 'abstract'
  33. end
  34. # rubocop:disable Rails/Delegate
  35. def project
  36. target.project
  37. end
  38. def recipients
  39. @recipients ||= []
  40. end
  41. def add_recipients(users, type, reason)
  42. if users.is_a?(ActiveRecord::Relation)
  43. users = users.includes(:notification_settings)
  44. end
  45. users = Array(users).compact
  46. recipients.concat(users.map { |u| make_recipient(u, type, reason) })
  47. end
  48. def user_scope
  49. User.includes(:notification_settings)
  50. end
  51. def make_recipient(user, type, reason)
  52. NotificationRecipient.new(
  53. user, type,
  54. reason: reason,
  55. project: project,
  56. custom_action: custom_action,
  57. target: target,
  58. acting_user: acting_user
  59. )
  60. end
  61. def notification_recipients
  62. @notification_recipients ||=
  63. begin
  64. build!
  65. filter!
  66. recipients = self.recipients.sort_by { |r| NotificationReason.priority(r.reason) }.uniq(&:user)
  67. recipients.freeze
  68. end
  69. end
  70. def custom_action
  71. nil
  72. end
  73. protected
  74. def add_participants(user)
  75. return unless target.respond_to?(:participants)
  76. add_recipients(target.participants(user), :participating, nil)
  77. end
  78. def add_mentions(user, target:)
  79. return unless target.respond_to?(:mentioned_users)
  80. add_recipients(target.mentioned_users(user), :mention, NotificationReason::MENTIONED)
  81. end
  82. # Get project/group users with CUSTOM notification level
  83. def add_custom_notifications
  84. user_ids = []
  85. # Users with a notification setting on group or project
  86. user_ids += user_ids_notifiable_on(project, :custom)
  87. user_ids += user_ids_notifiable_on(project.group, :custom)
  88. # Users with global level custom
  89. user_ids_with_project_level_global = user_ids_notifiable_on(project, :global)
  90. user_ids_with_group_level_global = user_ids_notifiable_on(project.group, :global)
  91. global_users_ids = user_ids_with_project_level_global.concat(user_ids_with_group_level_global)
  92. user_ids += user_ids_with_global_level_custom(global_users_ids, custom_action)
  93. add_recipients(user_scope.where(id: user_ids), :watch, nil)
  94. end
  95. def add_project_watchers
  96. add_recipients(project_watchers, :watch, nil)
  97. end
  98. # Get project users with WATCH notification level
  99. def project_watchers
  100. project_members_ids = user_ids_notifiable_on(project)
  101. user_ids_with_project_global = user_ids_notifiable_on(project, :global)
  102. user_ids_with_group_global = user_ids_notifiable_on(project.group, :global)
  103. user_ids = user_ids_with_global_level_watch((user_ids_with_project_global + user_ids_with_group_global).uniq)
  104. user_ids_with_project_setting = select_project_members_ids(user_ids_with_project_global, user_ids)
  105. user_ids_with_group_setting = select_group_members_ids(project.group, project_members_ids, user_ids_with_group_global, user_ids)
  106. user_scope.where(id: user_ids_with_project_setting.concat(user_ids_with_group_setting).uniq)
  107. end
  108. def add_subscribed_users
  109. return unless target.respond_to? :subscribers
  110. add_recipients(target.subscribers(project), :subscription, nil)
  111. end
  112. def user_ids_notifiable_on(resource, notification_level = nil)
  113. return [] unless resource
  114. scope = resource.notification_settings
  115. if notification_level
  116. scope = scope.where(level: NotificationSetting.levels[notification_level])
  117. end
  118. scope.pluck(:user_id)
  119. end
  120. # Build a list of user_ids based on project notification settings
  121. def select_project_members_ids(global_setting, user_ids_global_level_watch)
  122. user_ids = user_ids_notifiable_on(project, :watch)
  123. # If project setting is global, add to watch list if global setting is watch
  124. user_ids + (global_setting & user_ids_global_level_watch)
  125. end
  126. # Build a list of user_ids based on group notification settings
  127. def select_group_members_ids(group, project_members, global_setting, user_ids_global_level_watch)
  128. uids = user_ids_notifiable_on(group, :watch)
  129. # Group setting is global, add to user_ids list if global setting is watch
  130. uids + (global_setting & user_ids_global_level_watch) - project_members
  131. end
  132. def user_ids_with_global_level_watch(ids)
  133. settings_with_global_level_of(:watch, ids).pluck(:user_id)
  134. end
  135. def user_ids_with_global_level_custom(ids, action)
  136. settings_with_global_level_of(:custom, ids).pluck(:user_id)
  137. end
  138. def settings_with_global_level_of(level, ids)
  139. NotificationSetting.where(
  140. user_id: ids,
  141. source_type: nil,
  142. level: NotificationSetting.levels[level]
  143. )
  144. end
  145. def add_labels_subscribers(labels: nil)
  146. return unless target.respond_to? :labels
  147. (labels || target.labels).each do |label|
  148. add_recipients(label.subscribers(project), :subscription, nil)
  149. end
  150. end
  151. end
  152. class Default < Base
  153. attr_reader :target
  154. attr_reader :current_user
  155. attr_reader :action
  156. attr_reader :previous_assignee
  157. attr_reader :skip_current_user
  158. def initialize(target, current_user, action:, previous_assignee: nil, skip_current_user: true)
  159. @target = target
  160. @current_user = current_user
  161. @action = action
  162. @previous_assignee = previous_assignee
  163. @skip_current_user = skip_current_user
  164. end
  165. def build!
  166. add_participants(current_user)
  167. add_project_watchers
  168. add_custom_notifications
  169. # Re-assign is considered as a mention of the new assignee
  170. case custom_action
  171. when :reassign_merge_request
  172. add_recipients(previous_assignee, :mention, nil)
  173. add_recipients(target.assignee, :mention, NotificationReason::ASSIGNED)
  174. when :reassign_issue
  175. previous_assignees = Array(previous_assignee)
  176. add_recipients(previous_assignees, :mention, nil)
  177. add_recipients(target.assignees, :mention, NotificationReason::ASSIGNED)
  178. end
  179. add_subscribed_users
  180. if [:new_issue, :new_merge_request].include?(custom_action)
  181. # These will all be participants as well, but adding with the :mention
  182. # type ensures that users with the mention notification level will
  183. # receive them, too.
  184. add_mentions(current_user, target: target)
  185. # Add the assigned users, if any
  186. assignees = custom_action == :new_issue ? target.assignees : target.assignee
  187. # We use the `:participating` notification level in order to match existing legacy behavior as captured
  188. # in existing specs (notification_service_spec.rb ~ line 507)
  189. add_recipients(assignees, :participating, NotificationReason::ASSIGNED) if assignees
  190. add_labels_subscribers
  191. end
  192. end
  193. def acting_user
  194. current_user if skip_current_user
  195. end
  196. # Build event key to search on custom notification level
  197. # Check NotificationSetting::EMAIL_EVENTS
  198. def custom_action
  199. @custom_action ||= "#{action}_#{target.class.model_name.name.underscore}".to_sym
  200. end
  201. end
  202. class NewNote < Base
  203. attr_reader :note
  204. def initialize(note)
  205. @note = note
  206. end
  207. def target
  208. note.noteable
  209. end
  210. # NOTE: may be nil, in the case of a PersonalSnippet
  211. #
  212. # (this is okay because NotificationRecipient is written
  213. # to handle nil projects)
  214. def project
  215. note.project
  216. end
  217. def build!
  218. # Add all users participating in the thread (author, assignee, comment authors)
  219. add_participants(note.author)
  220. add_mentions(note.author, target: note)
  221. if note.for_project_noteable?
  222. # Merge project watchers
  223. add_project_watchers
  224. # Merge project with custom notification
  225. add_custom_notifications
  226. end
  227. add_subscribed_users
  228. end
  229. def custom_action
  230. :new_note
  231. end
  232. def acting_user
  233. note.author
  234. end
  235. end
  236. end
  237. end