summaryrefslogtreecommitdiff
path: root/app/services/notification_recipient_service.rb
diff options
context:
space:
mode:
authorhttp://jneen.net/ <jneen@jneen.net>2017-07-20 12:21:47 -0700
committerhttp://jneen.net/ <jneen@jneen.net>2017-08-03 09:06:15 -0700
commitb1334ed94e09f584505f9804566e51a34817c8da (patch)
treecdc1f5a4b9d5dac3d7a2644dc7941ec83742b1c0 /app/services/notification_recipient_service.rb
parentcce1bc9a2f3bbba5ad2a256c96d85eb2276e6ac6 (diff)
downloadgitlab-ce-b1334ed94e09f584505f9804566e51a34817c8da.tar.gz
move the builders to classes with a #build
Diffstat (limited to 'app/services/notification_recipient_service.rb')
-rw-r--r--app/services/notification_recipient_service.rb476
1 files changed, 257 insertions, 219 deletions
diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb
index 92049add32d..97bbc47efb3 100644
--- a/app/services/notification_recipient_service.rb
+++ b/app/services/notification_recipient_service.rb
@@ -20,297 +20,335 @@ class NotificationRecipientService
@project = project
end
- def build_recipients(target, current_user, action:, previous_assignee: nil, skip_current_user: true)
- custom_action = build_custom_key(action, target)
-
- recipients = participants(target, current_user)
- recipients = add_project_watchers(recipients)
- recipients = add_custom_notifications(recipients, custom_action)
- recipients = reject_mention_users(recipients)
-
- # Re-assign is considered as a mention of the new assignee so we add the
- # new assignee to the list of recipients after we rejected users with
- # the "on mention" notification level
- case custom_action
- when :reassign_merge_request
- recipients << previous_assignee if previous_assignee
- recipients << target.assignee
- when :reassign_issue
- previous_assignees = Array(previous_assignee)
- recipients.concat(previous_assignees)
- recipients.concat(target.assignees)
- end
-
- recipients = reject_muted_users(recipients)
- recipients = add_subscribed_users(recipients, target)
+ module Builder
- if [:new_issue, :new_merge_request].include?(custom_action)
- recipients = add_labels_subscribers(recipients, target)
- end
+ class Base
+ attr_reader :project
+ def initialize(project)
+ @project = project
+ end
- recipients = reject_unsubscribed_users(recipients, target)
- recipients = reject_users_without_access(recipients, target)
+ def build(*)
+ raise 'abstract'
+ end
- recipients.delete(current_user) if skip_current_user && !current_user.notified_of_own_activity?
+ # Remove users with disabled notifications from array
+ # Also remove duplications and nil recipients
+ def reject_muted_users(users)
+ reject_users(users, :disabled)
+ end
- recipients.uniq
- end
+ protected
- def build_pipeline_recipients(target, current_user, action:)
- return [] unless current_user
+ # Ensure that if we modify this array, we aren't modifying the memoised
+ # participants on the target.
+ def participants(target, user)
+ return unless target.respond_to?(:participants)
- custom_action =
- case action.to_s
- when 'failed'
- :failed_pipeline
- when 'success'
- :success_pipeline
+ target.participants(user).dup
end
- notification_setting = NotificationRecipientService.notification_setting_for_user_project(current_user, target.project)
+ # Get project/group users with CUSTOM notification level
+ def add_custom_notifications(recipients, action)
+ user_ids = []
- return [] if notification_setting.mention? || notification_setting.disabled?
+ # Users with a notification setting on group or project
+ user_ids += user_ids_notifiable_on(project, :custom, action)
+ user_ids += user_ids_notifiable_on(project.group, :custom, action)
- return [] if notification_setting.custom? && !notification_setting.event_enabled?(custom_action)
+ # Users with global level custom
+ user_ids_with_project_level_global = user_ids_notifiable_on(project, :global)
+ user_ids_with_group_level_global = user_ids_notifiable_on(project.group, :global)
- return [] if (notification_setting.watch? || notification_setting.participating?) && NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(custom_action)
+ global_users_ids = user_ids_with_project_level_global.concat(user_ids_with_group_level_global)
+ user_ids += user_ids_with_global_level_custom(global_users_ids, action)
- reject_users_without_access([current_user], target)
- end
+ recipients.concat(User.find(user_ids))
+ end
- def build_relabeled_recipients(target, current_user, labels:)
- recipients = add_labels_subscribers([], target, labels: labels)
- recipients = reject_unsubscribed_users(recipients, target)
- recipients = reject_users_without_access(recipients, target)
- recipients.delete(current_user) unless current_user.notified_of_own_activity?
- recipients.uniq
- end
+ def add_project_watchers(recipients)
+ recipients.concat(project_watchers).compact
+ end
- def build_new_note_recipients(note)
- target = note.noteable
+ # Get project users with WATCH notification level
+ def project_watchers
+ project_members_ids = user_ids_notifiable_on(project)
- ability, subject = if note.for_personal_snippet?
- [:read_personal_snippet, note.noteable]
- else
- [:read_project, note.project]
- end
+ user_ids_with_project_global = user_ids_notifiable_on(project, :global)
+ user_ids_with_group_global = user_ids_notifiable_on(project.group, :global)
- mentioned_users = note.mentioned_users.select { |user| user.can?(ability, subject) }
+ user_ids = user_ids_with_global_level_watch((user_ids_with_project_global + user_ids_with_group_global).uniq)
- # Add all users participating in the thread (author, assignee, comment authors)
- recipients = participants(target, note.author) || mentioned_users
+ user_ids_with_project_setting = select_project_members_ids(project, user_ids_with_project_global, user_ids)
+ user_ids_with_group_setting = select_group_members_ids(project.group, project_members_ids, user_ids_with_group_global, user_ids)
- unless note.for_personal_snippet?
- # Merge project watchers
- recipients = add_project_watchers(recipients)
+ User.where(id: user_ids_with_project_setting.concat(user_ids_with_group_setting).uniq).to_a
+ end
- # Merge project with custom notification
- recipients = add_custom_notifications(recipients, :new_note)
- end
+ # Remove users with notification level 'Mentioned'
+ def reject_mention_users(users)
+ reject_users(users, :mention)
+ end
- # Reject users with Mention notification level, except those mentioned in _this_ note.
- recipients = reject_mention_users(recipients - mentioned_users)
- recipients = recipients + mentioned_users
+ def add_subscribed_users(recipients, target)
+ return recipients unless target.respond_to? :subscribers
- recipients = reject_muted_users(recipients)
+ recipients + target.subscribers(project)
+ end
- recipients = add_subscribed_users(recipients, note.noteable)
- recipients = reject_unsubscribed_users(recipients, note.noteable)
- recipients = reject_users_without_access(recipients, note.noteable)
+ def user_ids_notifiable_on(resource, notification_level = nil, action = nil)
+ return [] unless resource
- recipients.delete(note.author) unless note.author.notified_of_own_activity?
- recipients.uniq
- end
+ if notification_level
+ settings = resource.notification_settings.where(level: NotificationSetting.levels[notification_level])
+ settings = settings.select { |setting| setting.event_enabled?(action) } if action.present?
+ settings.map(&:user_id)
+ else
+ resource.notification_settings.pluck(:user_id)
+ end
+ end
- # Remove users with disabled notifications from array
- # Also remove duplications and nil recipients
- def reject_muted_users(users)
- reject_users(users, :disabled)
- end
+ # Build a list of user_ids based on project notification settings
+ def select_project_members_ids(project, global_setting, user_ids_global_level_watch)
+ user_ids = user_ids_notifiable_on(project, :watch)
- protected
+ # If project setting is global, add to watch list if global setting is watch
+ global_setting.each do |user_id|
+ if user_ids_global_level_watch.include?(user_id)
+ user_ids << user_id
+ end
+ end
- # Ensure that if we modify this array, we aren't modifying the memoised
- # participants on the target.
- def participants(target, user)
- return unless target.respond_to?(:participants)
+ user_ids
+ end
- target.participants(user).dup
- end
+ # Build a list of user_ids based on group notification settings
+ def select_group_members_ids(group, project_members, global_setting, user_ids_global_level_watch)
+ uids = user_ids_notifiable_on(group, :watch)
+
+ # Group setting is watch, add to user_ids list if user is not project member
+ user_ids = []
+ uids.each do |user_id|
+ if project_members.exclude?(user_id)
+ user_ids << user_id
+ end
+ end
+
+ # Group setting is global, add to user_ids list if global setting is watch
+ global_setting.each do |user_id|
+ if project_members.exclude?(user_id) && user_ids_global_level_watch.include?(user_id)
+ user_ids << user_id
+ end
+ end
+
+ user_ids
+ end
- # Get project/group users with CUSTOM notification level
- def add_custom_notifications(recipients, action)
- user_ids = []
+ def user_ids_with_global_level_watch(ids)
+ settings_with_global_level_of(:watch, ids).pluck(:user_id)
+ end
- # Users with a notification setting on group or project
- user_ids += user_ids_notifiable_on(project, :custom, action)
- user_ids += user_ids_notifiable_on(project.group, :custom, action)
+ def user_ids_with_global_level_custom(ids, action)
+ settings = settings_with_global_level_of(:custom, ids)
+ settings = settings.select { |setting| setting.event_enabled?(action) }
+ settings.map(&:user_id)
+ end
- # Users with global level custom
- user_ids_with_project_level_global = user_ids_notifiable_on(project, :global)
- user_ids_with_group_level_global = user_ids_notifiable_on(project.group, :global)
+ def settings_with_global_level_of(level, ids)
+ NotificationSetting.where(
+ user_id: ids,
+ source_type: nil,
+ level: NotificationSetting.levels[level]
+ )
+ end
- global_users_ids = user_ids_with_project_level_global.concat(user_ids_with_group_level_global)
- user_ids += user_ids_with_global_level_custom(global_users_ids, action)
+ # Reject users which has certain notification level
+ #
+ # Example:
+ # reject_users(users, :watch, project)
+ #
+ def reject_users(users, level)
+ level = level.to_s
- recipients.concat(User.find(user_ids))
- end
+ unless NotificationSetting.levels.keys.include?(level)
+ raise 'Invalid notification level'
+ end
- def add_project_watchers(recipients)
- recipients.concat(project_watchers).compact
- end
+ users = users.to_a.compact.uniq
- # Get project users with WATCH notification level
- def project_watchers
- project_members_ids = user_ids_notifiable_on(project)
+ users.reject do |user|
+ setting = NotificationRecipientService.notification_setting_for_user_project(user, project)
+ setting.present? && setting.level == level
+ end
+ end
- user_ids_with_project_global = user_ids_notifiable_on(project, :global)
- user_ids_with_group_global = user_ids_notifiable_on(project.group, :global)
+ def reject_unsubscribed_users(recipients, target)
+ return recipients unless target.respond_to? :subscriptions
- user_ids = user_ids_with_global_level_watch((user_ids_with_project_global + user_ids_with_group_global).uniq)
+ recipients.reject do |user|
+ subscription = target.subscriptions.find_by_user_id(user.id)
+ subscription && !subscription.subscribed
+ end
+ end
- user_ids_with_project_setting = select_project_members_ids(project, user_ids_with_project_global, user_ids)
- user_ids_with_group_setting = select_group_members_ids(project.group, project_members_ids, user_ids_with_group_global, user_ids)
+ def reject_users_without_access(recipients, target)
+ recipients = recipients.select { |u| u.can?(:receive_notifications) }
- User.where(id: user_ids_with_project_setting.concat(user_ids_with_group_setting).uniq).to_a
- end
+ ability = case target
+ when Issuable
+ :"read_#{target.to_ability_name}"
+ when Ci::Pipeline
+ :read_build # We have build trace in pipeline emails
+ end
- # Remove users with notification level 'Mentioned'
- def reject_mention_users(users)
- reject_users(users, :mention)
- end
+ return recipients unless ability
- def add_subscribed_users(recipients, target)
- return recipients unless target.respond_to? :subscribers
+ recipients.select do |user|
+ user.can?(ability, target)
+ end
+ end
- recipients + target.subscribers(project)
- end
+ def add_labels_subscribers(recipients, target, labels: nil)
+ return recipients unless target.respond_to? :labels
- def user_ids_notifiable_on(resource, notification_level = nil, action = nil)
- return [] unless resource
+ (labels || target.labels).each do |label|
+ recipients += label.subscribers(project)
+ end
- if notification_level
- settings = resource.notification_settings.where(level: NotificationSetting.levels[notification_level])
- settings = settings.select { |setting| setting.event_enabled?(action) } if action.present?
- settings.map(&:user_id)
- else
- resource.notification_settings.pluck(:user_id)
- end
- end
+ recipients
+ end
- # Build a list of user_ids based on project notification settings
- def select_project_members_ids(project, global_setting, user_ids_global_level_watch)
- user_ids = user_ids_notifiable_on(project, :watch)
+ # Build event key to search on custom notification level
+ # Check NotificationSetting::EMAIL_EVENTS
+ def build_custom_key(action, object)
+ "#{action}_#{object.class.model_name.name.underscore}".to_sym
+ end
+ end
- # If project setting is global, add to watch list if global setting is watch
- global_setting.each do |user_id|
- if user_ids_global_level_watch.include?(user_id)
- user_ids << user_id
+ class Default < Base
+ def build(target, current_user, action:, previous_assignee: nil, skip_current_user: true)
+ custom_action = build_custom_key(action, target)
+
+ recipients = participants(target, current_user)
+ recipients = add_project_watchers(recipients)
+ recipients = add_custom_notifications(recipients, custom_action)
+ recipients = reject_mention_users(recipients)
+
+ # Re-assign is considered as a mention of the new assignee so we add the
+ # new assignee to the list of recipients after we rejected users with
+ # the "on mention" notification level
+ case custom_action
+ when :reassign_merge_request
+ recipients << previous_assignee if previous_assignee
+ recipients << target.assignee
+ when :reassign_issue
+ previous_assignees = Array(previous_assignee)
+ recipients.concat(previous_assignees)
+ recipients.concat(target.assignees)
+ end
+
+ recipients = reject_muted_users(recipients)
+ recipients = add_subscribed_users(recipients, target)
+
+ if [:new_issue, :new_merge_request].include?(custom_action)
+ recipients = add_labels_subscribers(recipients, target)
+ end
+
+ recipients = reject_unsubscribed_users(recipients, target)
+ recipients = reject_users_without_access(recipients, target)
+
+ recipients.delete(current_user) if skip_current_user && !current_user.notified_of_own_activity?
+
+ recipients.uniq
end
end
- user_ids
- end
+ class Pipeline < Base
+ def build(target, current_user, action:)
+ return [] unless current_user
+
+ custom_action =
+ case action.to_s
+ when 'failed'
+ :failed_pipeline
+ when 'success'
+ :success_pipeline
+ end
+
+ notification_setting = NotificationRecipientService.notification_setting_for_user_project(current_user, target.project)
+
+ return [] if notification_setting.mention? || notification_setting.disabled?
- # Build a list of user_ids based on group notification settings
- def select_group_members_ids(group, project_members, global_setting, user_ids_global_level_watch)
- uids = user_ids_notifiable_on(group, :watch)
+ return [] if notification_setting.custom? && !notification_setting.event_enabled?(custom_action)
- # Group setting is watch, add to user_ids list if user is not project member
- user_ids = []
- uids.each do |user_id|
- if project_members.exclude?(user_id)
- user_ids << user_id
+ return [] if (notification_setting.watch? || notification_setting.participating?) && NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(custom_action)
+
+ reject_users_without_access([current_user], target)
end
end
- # Group setting is global, add to user_ids list if global setting is watch
- global_setting.each do |user_id|
- if project_members.exclude?(user_id) && user_ids_global_level_watch.include?(user_id)
- user_ids << user_id
+ class Relabeled < Base
+ def build(target, current_user, labels:)
+ recipients = add_labels_subscribers([], target, labels: labels)
+ recipients = reject_unsubscribed_users(recipients, target)
+ recipients = reject_users_without_access(recipients, target)
+ recipients.delete(current_user) unless current_user.notified_of_own_activity?
+ recipients.uniq
end
end
- user_ids
- end
+ class NewNote < Base
+ def build(note)
+ target = note.noteable
- def user_ids_with_global_level_watch(ids)
- settings_with_global_level_of(:watch, ids).pluck(:user_id)
- end
+ ability, subject = if note.for_personal_snippet?
+ [:read_personal_snippet, note.noteable]
+ else
+ [:read_project, note.project]
+ end
- def user_ids_with_global_level_custom(ids, action)
- settings = settings_with_global_level_of(:custom, ids)
- settings = settings.select { |setting| setting.event_enabled?(action) }
- settings.map(&:user_id)
- end
+ mentioned_users = note.mentioned_users.select { |user| user.can?(ability, subject) }
- def settings_with_global_level_of(level, ids)
- NotificationSetting.where(
- user_id: ids,
- source_type: nil,
- level: NotificationSetting.levels[level]
- )
- end
+ # Add all users participating in the thread (author, assignee, comment authors)
+ recipients = participants(target, note.author) || mentioned_users
- # Reject users which has certain notification level
- #
- # Example:
- # reject_users(users, :watch, project)
- #
- def reject_users(users, level)
- level = level.to_s
+ unless note.for_personal_snippet?
+ # Merge project watchers
+ recipients = add_project_watchers(recipients)
- unless NotificationSetting.levels.keys.include?(level)
- raise 'Invalid notification level'
- end
+ # Merge project with custom notification
+ recipients = add_custom_notifications(recipients, :new_note)
+ end
- users = users.to_a.compact.uniq
+ # Reject users with Mention notification level, except those mentioned in _this_ note.
+ recipients = reject_mention_users(recipients - mentioned_users)
+ recipients = recipients + mentioned_users
- users.reject do |user|
- setting = NotificationRecipientService.notification_setting_for_user_project(user, project)
- setting.present? && setting.level == level
- end
- end
+ recipients = reject_muted_users(recipients)
- def reject_unsubscribed_users(recipients, target)
- return recipients unless target.respond_to? :subscriptions
+ recipients = add_subscribed_users(recipients, note.noteable)
+ recipients = reject_unsubscribed_users(recipients, note.noteable)
+ recipients = reject_users_without_access(recipients, note.noteable)
- recipients.reject do |user|
- subscription = target.subscriptions.find_by_user_id(user.id)
- subscription && !subscription.subscribed
+ recipients.delete(note.author) unless note.author.notified_of_own_activity?
+ recipients.uniq
+ end
end
end
- def reject_users_without_access(recipients, target)
- recipients = recipients.select { |u| u.can?(:receive_notifications) }
-
- ability = case target
- when Issuable
- :"read_#{target.to_ability_name}"
- when Ci::Pipeline
- :read_build # We have build trace in pipeline emails
- end
-
- return recipients unless ability
-
- recipients.select do |user|
- user.can?(ability, target)
- end
+ def build_recipients(*a)
+ Builder::Default.new(@project).build(*a)
end
- def add_labels_subscribers(recipients, target, labels: nil)
- return recipients unless target.respond_to? :labels
-
- (labels || target.labels).each do |label|
- recipients += label.subscribers(project)
- end
+ def build_pipeline_recipients(*a)
+ Builder::Pipeline.new(@project).build(*a)
+ end
- recipients
+ def build_relabeled_recipients(*a)
+ Builder::Relabeled.new(@project).build(*a)
end
- # Build event key to search on custom notification level
- # Check NotificationSetting::EMAIL_EVENTS
- def build_custom_key(action, object)
- "#{action}_#{object.class.model_name.name.underscore}".to_sym
+ def build_new_note_recipients(note)
+ Builder::NewNote.new(@project).build(note)
end
end