Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-10 15:08:16 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-10 15:08:16 +0300
commit1fa79760ad2d4bd67f5c5a27f372a7533b9b7c69 (patch)
treeffdfbd9113743831ff4f1290959a62cf6567fde5 /app/services/notification_recipients/builder
parent82fa8a3d1e8466ef36b58604d20fcc145ea12118 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/services/notification_recipients/builder')
-rw-r--r--app/services/notification_recipients/builder/base.rb217
-rw-r--r--app/services/notification_recipients/builder/default.rb74
-rw-r--r--app/services/notification_recipients/builder/merge_request_unmergeable.rb26
-rw-r--r--app/services/notification_recipients/builder/new_note.rb56
-rw-r--r--app/services/notification_recipients/builder/new_release.rb25
-rw-r--r--app/services/notification_recipients/builder/project_maintainers.rb24
6 files changed, 422 insertions, 0 deletions
diff --git a/app/services/notification_recipients/builder/base.rb b/app/services/notification_recipients/builder/base.rb
new file mode 100644
index 00000000000..3aa00c09ba2
--- /dev/null
+++ b/app/services/notification_recipients/builder/base.rb
@@ -0,0 +1,217 @@
+# frozen_string_literal: true
+
+module NotificationRecipients
+ module Builder
+ class Base
+ def initialize(*)
+ raise 'abstract'
+ end
+
+ def build!
+ raise 'abstract'
+ end
+
+ def filter!
+ recipients.select!(&:notifiable?)
+ end
+
+ def acting_user
+ current_user
+ end
+
+ def target
+ raise 'abstract'
+ end
+
+ def project
+ target.project
+ end
+
+ def group
+ project&.group || target.try(:group)
+ end
+
+ def recipients
+ @recipients ||= []
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def add_recipients(users, type, reason)
+ if users.is_a?(ActiveRecord::Relation)
+ users = users.includes(:notification_settings)
+ end
+
+ users = Array(users).compact
+ recipients.concat(users.map { |u| make_recipient(u, type, reason) })
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def user_scope
+ User.includes(:notification_settings)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def make_recipient(user, type, reason)
+ NotificationRecipient.new(
+ user, type,
+ reason: reason,
+ project: project,
+ group: group,
+ custom_action: custom_action,
+ target: target,
+ acting_user: acting_user
+ )
+ end
+
+ def notification_recipients
+ @notification_recipients ||=
+ begin
+ build!
+ filter!
+ recipients = self.recipients.sort_by { |r| NotificationReason.priority(r.reason) }.uniq(&:user)
+ recipients.freeze
+ end
+ end
+
+ def custom_action
+ nil
+ end
+
+ protected
+
+ def add_participants(user)
+ return unless target.respond_to?(:participants)
+
+ add_recipients(target.participants(user), :participating, nil)
+ end
+
+ def add_mentions(user, target:)
+ return unless target.respond_to?(:mentioned_users)
+
+ add_recipients(target.mentioned_users(user), :mention, NotificationReason::MENTIONED)
+ end
+
+ # Get project/group users with CUSTOM notification level
+ # rubocop: disable CodeReuse/ActiveRecord
+ def add_custom_notifications
+ user_ids = []
+
+ # Users with a notification setting on group or project
+ user_ids += user_ids_notifiable_on(project, :custom)
+ user_ids += user_ids_notifiable_on(group, :custom)
+
+ # 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(group, :global)
+
+ 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, custom_action)
+
+ add_recipients(user_scope.where(id: user_ids), :custom, nil)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def add_project_watchers
+ add_recipients(project_watchers, :watch, nil) if project
+ end
+
+ def add_group_watchers
+ add_recipients(group_watchers, :watch, nil)
+ end
+
+ # Get project users with WATCH notification level
+ # rubocop: disable CodeReuse/ActiveRecord
+ def project_watchers
+ project_members_ids = user_ids_notifiable_on(project)
+
+ user_ids_with_project_global = user_ids_notifiable_on(project, :global)
+ user_ids_with_group_global = user_ids_notifiable_on(project.group, :global)
+
+ user_ids = user_ids_with_global_level_watch((user_ids_with_project_global + user_ids_with_group_global).uniq)
+
+ user_ids_with_project_setting = select_project_members_ids(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)
+
+ user_scope.where(id: user_ids_with_project_setting.concat(user_ids_with_group_setting).uniq)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def group_watchers
+ user_ids_with_group_global = user_ids_notifiable_on(group, :global)
+ user_ids = user_ids_with_global_level_watch(user_ids_with_group_global)
+ user_ids_with_group_setting = select_group_members_ids(group, [], user_ids_with_group_global, user_ids)
+
+ user_scope.where(id: user_ids_with_group_setting)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def add_subscribed_users
+ return unless target.respond_to? :subscribers
+
+ add_recipients(target.subscribers(project), :subscription, NotificationReason::SUBSCRIBED)
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def user_ids_notifiable_on(resource, notification_level = nil)
+ return [] unless resource
+
+ scope = resource.notification_settings
+
+ if notification_level
+ scope = scope.where(level: NotificationSetting.levels[notification_level])
+ end
+
+ scope.pluck(:user_id)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # Build a list of user_ids based on project notification settings
+ def select_project_members_ids(global_setting, user_ids_global_level_watch)
+ user_ids = user_ids_notifiable_on(project, :watch)
+
+ # If project setting is global, add to watch list if global setting is watch
+ user_ids + (global_setting & user_ids_global_level_watch)
+ 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 global, add to user_ids list if global setting is watch
+ uids + (global_setting & user_ids_global_level_watch) - project_members
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def user_ids_with_global_level_watch(ids)
+ settings_with_global_level_of(:watch, ids).pluck(:user_id)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def user_ids_with_global_level_custom(ids, action)
+ settings_with_global_level_of(:custom, ids).pluck(:user_id)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def settings_with_global_level_of(level, ids)
+ NotificationSetting.where(
+ user_id: ids,
+ source_type: nil,
+ level: NotificationSetting.levels[level]
+ )
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def add_labels_subscribers(labels: nil)
+ return unless target.respond_to? :labels
+
+ (labels || target.labels).each do |label|
+ add_recipients(label.subscribers(project), :subscription, NotificationReason::SUBSCRIBED)
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/notification_recipients/builder/default.rb b/app/services/notification_recipients/builder/default.rb
new file mode 100644
index 00000000000..790ce57452c
--- /dev/null
+++ b/app/services/notification_recipients/builder/default.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+module NotificationRecipients
+ module Builder
+ class Default < Base
+ MENTION_TYPE_ACTIONS = [:new_issue, :new_merge_request].freeze
+
+ attr_reader :target
+ attr_reader :current_user
+ attr_reader :action
+ attr_reader :previous_assignees
+ attr_reader :skip_current_user
+
+ def initialize(target, current_user, action:, custom_action: nil, previous_assignees: nil, skip_current_user: true)
+ @target = target
+ @current_user = current_user
+ @action = action
+ @custom_action = custom_action
+ @previous_assignees = previous_assignees
+ @skip_current_user = skip_current_user
+ end
+
+ def add_watchers
+ add_project_watchers
+ end
+
+ def build!
+ add_participants(current_user)
+ add_watchers
+ add_custom_notifications
+
+ # Re-assign is considered as a mention of the new assignee
+ case custom_action
+ when :reassign_merge_request, :reassign_issue
+ add_recipients(previous_assignees, :mention, nil)
+ add_recipients(target.assignees, :mention, NotificationReason::ASSIGNED)
+ end
+
+ add_subscribed_users
+
+ if self.class.mention_type_actions.include?(custom_action)
+ # These will all be participants as well, but adding with the :mention
+ # type ensures that users with the mention notification level will
+ # receive them, too.
+ add_mentions(current_user, target: target)
+
+ # We use the `:participating` notification level in order to match existing legacy behavior as captured
+ # in existing specs (notification_service_spec.rb ~ line 507)
+ if target.is_a?(Issuable)
+ add_recipients(target.assignees, :participating, NotificationReason::ASSIGNED)
+ end
+
+ add_labels_subscribers
+ end
+ end
+
+ def acting_user
+ current_user if skip_current_user
+ end
+
+ # Build event key to search on custom notification level
+ # Check NotificationSetting.email_events
+ def custom_action
+ @custom_action ||= "#{action}_#{target.class.model_name.name.underscore}".to_sym
+ end
+
+ def self.mention_type_actions
+ MENTION_TYPE_ACTIONS.dup
+ end
+ end
+ end
+end
+
+NotificationRecipients::Builder::Default.prepend_if_ee('EE::NotificationRecipients::Builder::Default')
diff --git a/app/services/notification_recipients/builder/merge_request_unmergeable.rb b/app/services/notification_recipients/builder/merge_request_unmergeable.rb
new file mode 100644
index 00000000000..24d96b98002
--- /dev/null
+++ b/app/services/notification_recipients/builder/merge_request_unmergeable.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module NotificationRecipients
+ module Builder
+ class MergeRequestUnmergeable < Base
+ attr_reader :target
+ def initialize(merge_request)
+ @target = merge_request
+ end
+
+ def build!
+ target.merge_participants.each do |user|
+ add_recipients(user, :participating, nil)
+ end
+ end
+
+ def custom_action
+ :unmergeable_merge_request
+ end
+
+ def acting_user
+ nil
+ end
+ end
+ end
+end
diff --git a/app/services/notification_recipients/builder/new_note.rb b/app/services/notification_recipients/builder/new_note.rb
new file mode 100644
index 00000000000..27699a0d9cc
--- /dev/null
+++ b/app/services/notification_recipients/builder/new_note.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module NotificationRecipients
+ module Builder
+ class NewNote < Base
+ attr_reader :note
+ def initialize(note)
+ @note = note
+ end
+
+ def target
+ note.noteable
+ end
+
+ # NOTE: may be nil, in the case of a PersonalSnippet
+ #
+ # (this is okay because NotificationRecipient is written
+ # to handle nil projects)
+ def project
+ note.project
+ end
+
+ def group
+ if note.for_project_noteable?
+ project.group
+ else
+ target.try(:group)
+ end
+ end
+
+ def build!
+ # Add all users participating in the thread (author, assignee, comment authors)
+ add_participants(note.author)
+ add_mentions(note.author, target: note)
+
+ if note.for_project_noteable?
+ # Merge project watchers
+ add_project_watchers
+ else
+ add_group_watchers
+ end
+
+ add_custom_notifications
+ add_subscribed_users
+ end
+
+ def custom_action
+ :new_note
+ end
+
+ def acting_user
+ note.author
+ end
+ end
+ end
+end
diff --git a/app/services/notification_recipients/builder/new_release.rb b/app/services/notification_recipients/builder/new_release.rb
new file mode 100644
index 00000000000..67676b6eec8
--- /dev/null
+++ b/app/services/notification_recipients/builder/new_release.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module NotificationRecipients
+ module Builder
+ class NewRelease < Base
+ attr_reader :target
+
+ def initialize(target)
+ @target = target
+ end
+
+ def build!
+ add_recipients(target.project.authorized_users, :custom, nil)
+ end
+
+ def custom_action
+ :new_release
+ end
+
+ def acting_user
+ target.author
+ end
+ end
+ end
+end
diff --git a/app/services/notification_recipients/builder/project_maintainers.rb b/app/services/notification_recipients/builder/project_maintainers.rb
new file mode 100644
index 00000000000..e8f22c00a83
--- /dev/null
+++ b/app/services/notification_recipients/builder/project_maintainers.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module NotificationRecipients
+ module Builder
+ class ProjectMaintainers < Base
+ attr_reader :target
+
+ def initialize(target, action:)
+ @target = target
+ @action = action
+ end
+
+ def build!
+ return [] unless project
+
+ add_recipients(project.team.maintainers, :mention, nil)
+ end
+
+ def acting_user
+ nil
+ end
+ end
+ end
+end