mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-13 14:46:05 +10:00
Merge branch '12743-subscribe-to-label' into 'master'
Allow subscribing to labels This implement #12743 and supersedes !2799 (thanks @timothyandrew!). - Subscribe (and unsubscribe) for labels - When an issue/merge request is created, notify all subscribers of all its labels - When an issue/merge request is edited, notify all subscribers of all its newly-added labels ## Done - [x] Verify that a user is signed in to subscribe/unsubscribe from a label. - [x] Merge conflicts - [x] Integration tests - [x] `issuable.subscribed?` should check subscribers and participants - [x] When an issuable is relabeled, notify subscribers of the *newly added* labels only - [x] Screenshots: ### Labels page  ### HTML email  ### Plain text email  PS: I've set the milestone to 8.6 since it's getting late for 8.5... See merge request !3115
This commit is contained in:
@@ -28,6 +28,7 @@ v 8.6.0 (unreleased)
|
||||
- Update documentation to reflect Guest role not being enforced on internal projects
|
||||
- Allow search for logged out users
|
||||
- Allow to define on which builds the current one depends on
|
||||
- Allow user subscription to a label: get notified for issues/merge requests related to that label (Timothy Andrew)
|
||||
- Fix bug where Bitbucket `closed` issues were imported as `opened` (Iuri de Silvio)
|
||||
- Don't show Issues/MRs from archived projects in Groups view
|
||||
- Fix wrong "iid of max iid" in Issuable sidebar for some merged MRs
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
class @Subscription
|
||||
constructor: (url) ->
|
||||
$(".subscribe-button").unbind("click").click (event)=>
|
||||
btn = $(event.currentTarget)
|
||||
action = btn.find("span").text()
|
||||
current_status = $(".subscription-status").attr("data-status")
|
||||
btn.prop("disabled", true)
|
||||
|
||||
$.post url, =>
|
||||
btn.prop("disabled", false)
|
||||
status = if current_status == "subscribed" then "unsubscribed" else "subscribed"
|
||||
$(".subscription-status").attr("data-status", status)
|
||||
action = if status == "subscribed" then "Unsubscribe" else "Subscribe"
|
||||
btn.find("span").text(action)
|
||||
$(".subscription-status>div").toggleClass("hidden")
|
||||
constructor: (container) ->
|
||||
$container = $(container)
|
||||
@url = $container.attr('data-url')
|
||||
@subscribe_button = $container.find('.subscribe-button')
|
||||
@subscription_status = $container.find('.subscription-status')
|
||||
@subscribe_button.unbind('click').click(@toggleSubscription)
|
||||
|
||||
|
||||
toggleSubscription: (event) =>
|
||||
btn = $(event.currentTarget)
|
||||
action = btn.find('span').text()
|
||||
current_status = @subscription_status.attr('data-status')
|
||||
btn.prop('disabled', true)
|
||||
|
||||
$.post @url, =>
|
||||
btn.prop('disabled', false)
|
||||
status = if current_status == 'subscribed' then 'unsubscribed' else 'subscribed'
|
||||
@subscription_status.attr('data-status', status)
|
||||
action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe'
|
||||
btn.find('span').text(action)
|
||||
@subscription_status.find('>div').toggleClass('hidden')
|
||||
|
||||
@@ -41,3 +41,7 @@
|
||||
.color-label {
|
||||
padding: 3px 4px;
|
||||
}
|
||||
|
||||
.label-subscription {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
module ToggleSubscriptionAction
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def toggle_subscription
|
||||
return unless current_user
|
||||
|
||||
subscribable_resource.toggle_subscription(current_user)
|
||||
|
||||
render nothing: true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def subscribable_resource
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,8 @@
|
||||
class Projects::IssuesController < Projects::ApplicationController
|
||||
include ToggleSubscriptionAction
|
||||
|
||||
before_action :module_enabled
|
||||
before_action :issue, only: [:edit, :update, :show, :toggle_subscription]
|
||||
before_action :issue, only: [:edit, :update, :show]
|
||||
|
||||
# Allow read any issue
|
||||
before_action :authorize_read_issue!
|
||||
@@ -110,12 +112,6 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||
redirect_back_or_default(default: { action: 'index' }, options: { notice: "#{result[:count]} issues updated" })
|
||||
end
|
||||
|
||||
def toggle_subscription
|
||||
@issue.toggle_subscription(current_user)
|
||||
|
||||
render nothing: true
|
||||
end
|
||||
|
||||
def closed_by_merge_requests
|
||||
@closed_by_merge_requests ||= @issue.closed_by_merge_requests(current_user)
|
||||
end
|
||||
@@ -129,6 +125,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||
redirect_old
|
||||
end
|
||||
end
|
||||
alias_method :subscribable_resource, :issue
|
||||
|
||||
def authorize_update_issue!
|
||||
return render_404 unless can?(current_user, :update_issue, @issue)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
class Projects::LabelsController < Projects::ApplicationController
|
||||
include ToggleSubscriptionAction
|
||||
|
||||
before_action :module_enabled
|
||||
before_action :label, only: [:edit, :update, :destroy]
|
||||
before_action :authorize_read_label!
|
||||
before_action :authorize_admin_labels!, except: [:index]
|
||||
before_action :authorize_admin_labels!, only: [
|
||||
:new, :create, :edit, :update, :generate, :destroy
|
||||
]
|
||||
|
||||
respond_to :js, :html
|
||||
|
||||
@@ -73,8 +77,9 @@ class Projects::LabelsController < Projects::ApplicationController
|
||||
end
|
||||
|
||||
def label
|
||||
@label = @project.labels.find(params[:id])
|
||||
@label ||= @project.labels.find(params[:id])
|
||||
end
|
||||
alias_method :subscribable_resource, :label
|
||||
|
||||
def authorize_admin_labels!
|
||||
return render_404 unless can?(current_user, :admin_label, @project)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
class Projects::MergeRequestsController < Projects::ApplicationController
|
||||
include ToggleSubscriptionAction
|
||||
include DiffHelper
|
||||
|
||||
before_action :module_enabled
|
||||
before_action :merge_request, only: [
|
||||
:edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
|
||||
:ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds
|
||||
:ci_status, :cancel_merge_when_build_succeeds
|
||||
]
|
||||
before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits, :builds]
|
||||
before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds]
|
||||
@@ -233,12 +234,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||
render json: response
|
||||
end
|
||||
|
||||
def toggle_subscription
|
||||
@merge_request.toggle_subscription(current_user)
|
||||
|
||||
render nothing: true
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def selected_target_project
|
||||
@@ -252,6 +247,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||
def merge_request
|
||||
@merge_request ||= @project.merge_requests.find_by!(iid: params[:id])
|
||||
end
|
||||
alias_method :subscribable_resource, :merge_request
|
||||
|
||||
def closes_issues
|
||||
@closes_issues ||= @merge_request.closes_issues
|
||||
|
||||
@@ -124,6 +124,14 @@ module LabelsHelper
|
||||
options_from_collection_for_select(grouped_labels, 'name', 'title', params[:label_name])
|
||||
end
|
||||
|
||||
def label_subscription_status(label)
|
||||
label.subscribed?(current_user) ? 'subscribed' : 'unsubscribed'
|
||||
end
|
||||
|
||||
def label_subscription_toggle_button_text(label)
|
||||
label.subscribed?(current_user) ? 'Unsubscribe' : 'Subscribe'
|
||||
end
|
||||
|
||||
# Required for Banzai::Filter::LabelReferenceFilter
|
||||
module_function :render_colored_label, :render_colored_cross_project_label,
|
||||
:text_color_for_bg, :escape_once
|
||||
|
||||
@@ -16,7 +16,15 @@ module Emails
|
||||
def closed_issue_email(recipient_id, issue_id, updated_by_user_id)
|
||||
setup_issue_mail(issue_id, recipient_id)
|
||||
|
||||
@updated_by = User.find updated_by_user_id
|
||||
@updated_by = User.find(updated_by_user_id)
|
||||
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id))
|
||||
end
|
||||
|
||||
def relabeled_issue_email(recipient_id, issue_id, label_names, updated_by_user_id)
|
||||
setup_issue_mail(issue_id, recipient_id)
|
||||
|
||||
@label_names = label_names
|
||||
@labels_url = namespace_project_labels_url(@project.namespace, @project)
|
||||
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id))
|
||||
end
|
||||
|
||||
@@ -24,20 +32,12 @@ module Emails
|
||||
setup_issue_mail(issue_id, recipient_id)
|
||||
|
||||
@issue_status = status
|
||||
@updated_by = User.find updated_by_user_id
|
||||
@updated_by = User.find(updated_by_user_id)
|
||||
mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def issue_thread_options(sender_id, recipient_id)
|
||||
{
|
||||
from: sender(sender_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@issue.title} (##{@issue.iid})")
|
||||
}
|
||||
end
|
||||
|
||||
def setup_issue_mail(issue_id, recipient_id)
|
||||
@issue = Issue.find(issue_id)
|
||||
@project = @issue.project
|
||||
@@ -45,5 +45,13 @@ module Emails
|
||||
|
||||
@sent_notification = SentNotification.record(@issue, recipient_id, reply_key)
|
||||
end
|
||||
|
||||
def issue_thread_options(sender_id, recipient_id)
|
||||
{
|
||||
from: sender(sender_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@issue.title} (##{@issue.iid})")
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,50 +3,43 @@ module Emails
|
||||
def new_merge_request_email(recipient_id, merge_request_id)
|
||||
setup_merge_request_mail(merge_request_id, recipient_id)
|
||||
|
||||
mail_new_thread(@merge_request,
|
||||
from: sender(@merge_request.author_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
|
||||
mail_new_thread(@merge_request, merge_request_thread_options(@merge_request.author_id, recipient_id))
|
||||
end
|
||||
|
||||
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id)
|
||||
setup_merge_request_mail(merge_request_id, recipient_id)
|
||||
|
||||
@previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
|
||||
mail_answer_thread(@merge_request,
|
||||
from: sender(updated_by_user_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
|
||||
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id))
|
||||
end
|
||||
|
||||
def relabeled_merge_request_email(recipient_id, merge_request_id, label_names, updated_by_user_id)
|
||||
setup_merge_request_mail(merge_request_id, recipient_id)
|
||||
|
||||
@label_names = label_names
|
||||
@labels_url = namespace_project_labels_url(@project.namespace, @project)
|
||||
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id))
|
||||
end
|
||||
|
||||
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
|
||||
setup_merge_request_mail(merge_request_id, recipient_id)
|
||||
|
||||
@updated_by = User.find updated_by_user_id
|
||||
mail_answer_thread(@merge_request,
|
||||
from: sender(updated_by_user_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
|
||||
@updated_by = User.find(updated_by_user_id)
|
||||
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id))
|
||||
end
|
||||
|
||||
def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
|
||||
setup_merge_request_mail(merge_request_id, recipient_id)
|
||||
|
||||
mail_answer_thread(@merge_request,
|
||||
from: sender(updated_by_user_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
|
||||
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id))
|
||||
end
|
||||
|
||||
def merge_request_status_email(recipient_id, merge_request_id, status, updated_by_user_id)
|
||||
setup_merge_request_mail(merge_request_id, recipient_id)
|
||||
|
||||
@mr_status = status
|
||||
@updated_by = User.find updated_by_user_id
|
||||
mail_answer_thread(@merge_request,
|
||||
from: sender(updated_by_user_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
|
||||
@updated_by = User.find(updated_by_user_id)
|
||||
mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id))
|
||||
end
|
||||
|
||||
private
|
||||
@@ -54,11 +47,17 @@ module Emails
|
||||
def setup_merge_request_mail(merge_request_id, recipient_id)
|
||||
@merge_request = MergeRequest.find(merge_request_id)
|
||||
@project = @merge_request.project
|
||||
@target_url = namespace_project_merge_request_url(@project.namespace,
|
||||
@project,
|
||||
@merge_request)
|
||||
@target_url = namespace_project_merge_request_url(@project.namespace, @project, @merge_request)
|
||||
|
||||
@sent_notification = SentNotification.record(@merge_request, recipient_id, reply_key)
|
||||
end
|
||||
|
||||
def merge_request_thread_options(sender_id, recipient_id)
|
||||
{
|
||||
from: sender(sender_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@merge_request.title} (##{@merge_request.iid})")
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,6 +8,7 @@ module Issuable
|
||||
extend ActiveSupport::Concern
|
||||
include Participable
|
||||
include Mentionable
|
||||
include Subscribable
|
||||
include StripAttribute
|
||||
|
||||
included do
|
||||
@@ -18,7 +19,6 @@ module Issuable
|
||||
has_many :notes, as: :noteable, dependent: :destroy
|
||||
has_many :label_links, as: :target, dependent: :destroy
|
||||
has_many :labels, through: :label_links
|
||||
has_many :subscriptions, dependent: :destroy, as: :subscribable
|
||||
|
||||
validates :author, presence: true
|
||||
validates :title, presence: true, length: { within: 0..255 }
|
||||
@@ -149,28 +149,10 @@ module Issuable
|
||||
notes.awards.where(note: "thumbsup").count
|
||||
end
|
||||
|
||||
def subscribed?(user)
|
||||
subscription = subscriptions.find_by_user_id(user.id)
|
||||
|
||||
if subscription
|
||||
return subscription.subscribed
|
||||
end
|
||||
|
||||
def subscribed_without_subscriptions?(user)
|
||||
participants(user).include?(user)
|
||||
end
|
||||
|
||||
def toggle_subscription(user)
|
||||
subscriptions.
|
||||
find_or_initialize_by(user_id: user.id).
|
||||
update(subscribed: !subscribed?(user))
|
||||
end
|
||||
|
||||
def unsubscribe(user)
|
||||
subscriptions.
|
||||
find_or_initialize_by(user_id: user.id).
|
||||
update(subscribed: false)
|
||||
end
|
||||
|
||||
def to_hook_data(user)
|
||||
hook_data = {
|
||||
object_kind: self.class.name.underscore,
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# == Subscribable concern
|
||||
#
|
||||
# Users can subscribe to these models.
|
||||
#
|
||||
# Used by Issue, MergeRequest, Label
|
||||
#
|
||||
|
||||
module Subscribable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :subscriptions, dependent: :destroy, as: :subscribable
|
||||
end
|
||||
|
||||
def subscribed?(user)
|
||||
if subscription = subscriptions.find_by_user_id(user.id)
|
||||
subscription.subscribed
|
||||
else
|
||||
subscribed_without_subscriptions?(user)
|
||||
end
|
||||
end
|
||||
|
||||
# Override this method to define custom logic to consider a subscribable as
|
||||
# subscribed without an explicit subscription record.
|
||||
def subscribed_without_subscriptions?(user)
|
||||
false
|
||||
end
|
||||
|
||||
def subscribers
|
||||
subscriptions.where(subscribed: true).map(&:user)
|
||||
end
|
||||
|
||||
def toggle_subscription(user)
|
||||
subscriptions.
|
||||
find_or_initialize_by(user_id: user.id).
|
||||
update(subscribed: !subscribed?(user))
|
||||
end
|
||||
|
||||
def unsubscribe(user)
|
||||
subscriptions.
|
||||
find_or_initialize_by(user_id: user.id).
|
||||
update(subscribed: false)
|
||||
end
|
||||
end
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
class Label < ActiveRecord::Base
|
||||
include Referable
|
||||
include Subscribable
|
||||
|
||||
# Represents a "No Label" state used for filtering Issues and Merge
|
||||
# Requests that have no label assigned.
|
||||
LabelStruct = Struct.new(:title, :name)
|
||||
|
||||
@@ -15,7 +15,7 @@ class Subscription < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
belongs_to :subscribable, polymorphic: true
|
||||
|
||||
validates :user_id,
|
||||
validates :user_id,
|
||||
uniqueness: { scope: [:subscribable_id, :subscribable_type] },
|
||||
presence: true
|
||||
end
|
||||
|
||||
@@ -11,7 +11,10 @@ class IssuableBaseService < BaseService
|
||||
issuable, issuable.project, current_user, issuable.milestone)
|
||||
end
|
||||
|
||||
def create_labels_note(issuable, added_labels, removed_labels)
|
||||
def create_labels_note(issuable, old_labels)
|
||||
added_labels = issuable.labels - old_labels
|
||||
removed_labels = old_labels - issuable.labels
|
||||
|
||||
SystemNoteService.change_label(
|
||||
issuable, issuable.project, current_user, added_labels, removed_labels)
|
||||
end
|
||||
@@ -71,20 +74,19 @@ class IssuableBaseService < BaseService
|
||||
end
|
||||
end
|
||||
|
||||
def has_changes?(issuable, options = {})
|
||||
def has_changes?(issuable, old_labels: [])
|
||||
valid_attrs = [:title, :description, :assignee_id, :milestone_id, :target_branch]
|
||||
|
||||
attrs_changed = valid_attrs.any? do |attr|
|
||||
issuable.previous_changes.include?(attr.to_s)
|
||||
end
|
||||
|
||||
old_labels = options[:old_labels]
|
||||
labels_changed = old_labels && issuable.labels != old_labels
|
||||
labels_changed = issuable.labels != old_labels
|
||||
|
||||
attrs_changed || labels_changed
|
||||
end
|
||||
|
||||
def handle_common_system_notes(issuable, options = {})
|
||||
def handle_common_system_notes(issuable, old_labels: [])
|
||||
if issuable.previous_changes.include?('title')
|
||||
create_title_change_note(issuable, issuable.previous_changes['title'].first)
|
||||
end
|
||||
@@ -93,9 +95,6 @@ class IssuableBaseService < BaseService
|
||||
create_task_status_note(issuable)
|
||||
end
|
||||
|
||||
old_labels = options[:old_labels]
|
||||
if old_labels && (issuable.labels != old_labels)
|
||||
create_labels_note(issuable, issuable.labels - old_labels, old_labels - issuable.labels)
|
||||
end
|
||||
create_labels_note(issuable, old_labels) if issuable.labels != old_labels
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,8 +4,8 @@ module Issues
|
||||
update(issue)
|
||||
end
|
||||
|
||||
def handle_changes(issue, options = {})
|
||||
if has_changes?(issue, options)
|
||||
def handle_changes(issue, old_labels: [])
|
||||
if has_changes?(issue, old_labels: old_labels)
|
||||
todo_service.mark_pending_todos_as_done(issue, current_user)
|
||||
end
|
||||
|
||||
@@ -23,6 +23,11 @@ module Issues
|
||||
notification_service.reassigned_issue(issue, current_user)
|
||||
todo_service.reassigned_issue(issue, current_user)
|
||||
end
|
||||
|
||||
added_labels = issue.labels - old_labels
|
||||
if added_labels.present?
|
||||
notification_service.relabeled_issue(issue, added_labels, current_user)
|
||||
end
|
||||
end
|
||||
|
||||
def reopen_service
|
||||
|
||||
@@ -14,8 +14,8 @@ module MergeRequests
|
||||
update(merge_request)
|
||||
end
|
||||
|
||||
def handle_changes(merge_request, options = {})
|
||||
if has_changes?(merge_request, options)
|
||||
def handle_changes(merge_request, old_labels: [])
|
||||
if has_changes?(merge_request, old_labels: old_labels)
|
||||
todo_service.mark_pending_todos_as_done(merge_request, current_user)
|
||||
end
|
||||
|
||||
@@ -44,6 +44,15 @@ module MergeRequests
|
||||
merge_request.previous_changes.include?('source_branch')
|
||||
merge_request.mark_as_unchecked
|
||||
end
|
||||
|
||||
added_labels = merge_request.labels - old_labels
|
||||
if added_labels.present?
|
||||
notification_service.relabeled_merge_request(
|
||||
merge_request,
|
||||
added_labels,
|
||||
current_user
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def reopen_service
|
||||
|
||||
@@ -24,16 +24,17 @@ class NotificationService
|
||||
end
|
||||
end
|
||||
|
||||
# When create an issue we should send next emails:
|
||||
# When create an issue we should send an email to:
|
||||
#
|
||||
# * issue assignee if their notification level is not Disabled
|
||||
# * project team members with notification level higher then Participating
|
||||
# * watchers of the issue's labels
|
||||
#
|
||||
def new_issue(issue, current_user)
|
||||
new_resource_email(issue, issue.project, 'new_issue_email')
|
||||
end
|
||||
|
||||
# When we close an issue we should send next emails:
|
||||
# When we close an issue we should send an email to:
|
||||
#
|
||||
# * issue author if their notification level is not Disabled
|
||||
# * issue assignee if their notification level is not Disabled
|
||||
@@ -43,7 +44,7 @@ class NotificationService
|
||||
close_resource_email(issue, issue.project, current_user, 'closed_issue_email')
|
||||
end
|
||||
|
||||
# When we reassign an issue we should send next emails:
|
||||
# When we reassign an issue we should send an email to:
|
||||
#
|
||||
# * issue old assignee if their notification level is not Disabled
|
||||
# * issue new assignee if their notification level is not Disabled
|
||||
@@ -52,16 +53,25 @@ class NotificationService
|
||||
reassign_resource_email(issue, issue.project, current_user, 'reassigned_issue_email')
|
||||
end
|
||||
|
||||
# When we add labels to an issue we should send an email to:
|
||||
#
|
||||
# * watchers of the issue's labels
|
||||
#
|
||||
def relabeled_issue(issue, added_labels, current_user)
|
||||
relabeled_resource_email(issue, added_labels, current_user, 'relabeled_issue_email')
|
||||
end
|
||||
|
||||
# When create a merge request we should send next emails:
|
||||
# When create a merge request we should send an email to:
|
||||
#
|
||||
# * mr assignee if their notification level is not Disabled
|
||||
# * project team members with notification level higher then Participating
|
||||
# * watchers of the mr's labels
|
||||
#
|
||||
def new_merge_request(merge_request, current_user)
|
||||
new_resource_email(merge_request, merge_request.target_project, 'new_merge_request_email')
|
||||
end
|
||||
|
||||
# When we reassign a merge_request we should send next emails:
|
||||
# When we reassign a merge_request we should send an email to:
|
||||
#
|
||||
# * merge_request old assignee if their notification level is not Disabled
|
||||
# * merge_request assignee if their notification level is not Disabled
|
||||
@@ -70,6 +80,14 @@ class NotificationService
|
||||
reassign_resource_email(merge_request, merge_request.target_project, current_user, 'reassigned_merge_request_email')
|
||||
end
|
||||
|
||||
# When we add labels to a merge request we should send an email to:
|
||||
#
|
||||
# * watchers of the mr's labels
|
||||
#
|
||||
def relabeled_merge_request(merge_request, added_labels, current_user)
|
||||
relabeled_resource_email(merge_request, added_labels, current_user, 'relabeled_merge_request_email')
|
||||
end
|
||||
|
||||
def close_mr(merge_request, current_user)
|
||||
close_resource_email(merge_request, merge_request.target_project, current_user, 'closed_merge_request_email')
|
||||
end
|
||||
@@ -91,7 +109,8 @@ class NotificationService
|
||||
reopen_resource_email(
|
||||
merge_request,
|
||||
merge_request.target_project,
|
||||
current_user, 'merge_request_status_email',
|
||||
current_user,
|
||||
'merge_request_status_email',
|
||||
'reopened'
|
||||
)
|
||||
end
|
||||
@@ -348,19 +367,23 @@ class NotificationService
|
||||
end
|
||||
|
||||
def add_subscribed_users(recipients, target)
|
||||
return recipients unless target.respond_to? :subscriptions
|
||||
return recipients unless target.respond_to? :subscribers
|
||||
|
||||
subscriptions = target.subscriptions
|
||||
recipients + target.subscribers
|
||||
end
|
||||
|
||||
if subscriptions.any?
|
||||
recipients + subscriptions.where(subscribed: true).map(&:user)
|
||||
else
|
||||
recipients
|
||||
def add_labels_subscribers(recipients, target, labels: nil)
|
||||
return recipients unless target.respond_to? :labels
|
||||
|
||||
(labels || target.labels).each do |label|
|
||||
recipients += label.subscribers
|
||||
end
|
||||
|
||||
recipients
|
||||
end
|
||||
|
||||
def new_resource_email(target, project, method)
|
||||
recipients = build_recipients(target, project, target.author)
|
||||
recipients = build_recipients(target, project, target.author, action: :new)
|
||||
|
||||
recipients.each do |recipient|
|
||||
mailer.send(method, recipient.id, target.id).deliver_later
|
||||
@@ -392,6 +415,15 @@ class NotificationService
|
||||
end
|
||||
end
|
||||
|
||||
def relabeled_resource_email(target, labels, current_user, method)
|
||||
recipients = build_relabeled_recipients(target, current_user, labels: labels)
|
||||
label_names = labels.map(&:name)
|
||||
|
||||
recipients.each do |recipient|
|
||||
mailer.send(method, recipient.id, target.id, label_names, current_user.id).deliver_later
|
||||
end
|
||||
end
|
||||
|
||||
def reopen_resource_email(target, project, current_user, method, status)
|
||||
recipients = build_recipients(target, project, current_user)
|
||||
|
||||
@@ -416,6 +448,11 @@ class NotificationService
|
||||
|
||||
recipients = reject_muted_users(recipients, project)
|
||||
recipients = add_subscribed_users(recipients, target)
|
||||
|
||||
if action == :new
|
||||
recipients = add_labels_subscribers(recipients, target)
|
||||
end
|
||||
|
||||
recipients = reject_unsubscribed_users(recipients, target)
|
||||
|
||||
recipients.delete(current_user)
|
||||
@@ -423,6 +460,13 @@ class NotificationService
|
||||
recipients.uniq
|
||||
end
|
||||
|
||||
def build_relabeled_recipients(target, current_user, labels:)
|
||||
recipients = add_labels_subscribers([], target, labels: labels)
|
||||
recipients = reject_unsubscribed_users(recipients, target)
|
||||
recipients.delete(current_user)
|
||||
recipients.uniq
|
||||
end
|
||||
|
||||
def mailer
|
||||
Notify
|
||||
end
|
||||
|
||||
@@ -42,12 +42,15 @@
|
||||
- else
|
||||
#{link_to "View it on GitLab", @target_url}.
|
||||
%br
|
||||
-# Don't link the host is the line below, one link in the email is easier to quickly click than two.
|
||||
-# Don't link the host in the line below, one link in the email is easier to quickly click than two.
|
||||
You're receiving this email because of your account on #{Gitlab.config.gitlab.host}.
|
||||
If you'd like to receive fewer emails, you can
|
||||
- if @sent_notification && @sent_notification.unsubscribable?
|
||||
= link_to "unsubscribe", unsubscribe_sent_notification_url(@sent_notification)
|
||||
from this thread or
|
||||
adjust your notification settings.
|
||||
- if @labels_url
|
||||
adjust your #{link_to 'label subscriptions', @labels_url}.
|
||||
- else
|
||||
- if @sent_notification && @sent_notification.unsubscribable?
|
||||
= link_to "unsubscribe", unsubscribe_sent_notification_url(@sent_notification)
|
||||
from this thread or
|
||||
adjust your notification settings.
|
||||
|
||||
= email_action @target_url
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Reassigned <%= issuable.class.model_name.human.titleize %> <%= issuable.iid %>
|
||||
|
||||
<%= url_for([issuable.project.namespace.becomes(Namespace), issuable.project, issuable, {only_path: false}]) %>
|
||||
<%= url_for([issuable.project.namespace.becomes(Namespace), issuable.project, issuable, { only_path: false }]) %>
|
||||
|
||||
Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee -%>
|
||||
to <%= "#{issuable.assignee_id ? issuable.assignee_name : 'Unassigned'}" %>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
%p
|
||||
#{'Label'.pluralize(@label_names.size)} added:
|
||||
%em= @label_names.to_sentence
|
||||
@@ -0,0 +1,3 @@
|
||||
<%= 'Label'.pluralize(@label_names.size) %> added: <%= @label_names.to_sentence %>
|
||||
|
||||
<%= url_for([issuable.project.namespace.becomes(Namespace), issuable.project, issuable, { only_path: false }]) %>
|
||||
@@ -0,0 +1 @@
|
||||
= render 'relabeled_issuable_email', issuable: @issue
|
||||
@@ -0,0 +1 @@
|
||||
<%= render 'relabeled_issuable_email', issuable: @issue %>
|
||||
@@ -0,0 +1 @@
|
||||
= render 'relabeled_issuable_email', issuable: @merge_request
|
||||
@@ -0,0 +1 @@
|
||||
<%= render 'relabeled_issuable_email', issuable: @merge_request %>
|
||||
@@ -10,6 +10,16 @@
|
||||
= link_to_label(label) do
|
||||
= pluralize label.open_issues_count, 'open issue'
|
||||
|
||||
- if current_user
|
||||
.label-subscription{data: {url: toggle_subscription_namespace_project_label_path(@project.namespace, @project, label)}}
|
||||
.subscription-status{data: {status: label_subscription_status(label)}}
|
||||
%button.btn.btn-sm.btn-info.subscribe-button
|
||||
%span= label_subscription_toggle_button_text(label)
|
||||
|
||||
- if can? current_user, :admin_label, @project
|
||||
= link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm'
|
||||
= link_to 'Delete', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
|
||||
|
||||
- if current_user
|
||||
:javascript
|
||||
new Subscription('##{dom_id(label)} .label-subscription');
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
%hr
|
||||
- if current_user
|
||||
- subscribed = issuable.subscribed?(current_user)
|
||||
.block.light
|
||||
.block.light.subscription{data: {url: toggle_subscription_path(issuable)}}
|
||||
.sidebar-collapsed-icon
|
||||
= icon('rss')
|
||||
.title.hide-collapsed
|
||||
@@ -124,5 +124,5 @@
|
||||
= clipboard_button(clipboard_text: project_ref)
|
||||
|
||||
:javascript
|
||||
new Subscription("#{toggle_subscription_path(issuable)}");
|
||||
new Subscription('.subscription');
|
||||
new IssuableContext();
|
||||
|
||||
@@ -675,6 +675,10 @@ Rails.application.routes.draw do
|
||||
collection do
|
||||
post :generate
|
||||
end
|
||||
|
||||
member do
|
||||
post :toggle_subscription
|
||||
end
|
||||
end
|
||||
|
||||
resources :issues, constraints: { id: /\d+/ }, except: [:destroy] do
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
@labels
|
||||
Feature: Labels
|
||||
Background:
|
||||
Given I sign in as a user
|
||||
And I own project "Shop"
|
||||
And project "Shop" has labels: "bug", "feature", "enhancement"
|
||||
When I visit project "Shop" labels page
|
||||
|
||||
@javascript
|
||||
Scenario: I can subscribe to a label
|
||||
Then I should see that I am not subscribed to the "bug" label
|
||||
When I click button "Subscribe" for the "bug" label
|
||||
Then I should see that I am subscribed to the "bug" label
|
||||
When I click button "Unsubscribe" for the "bug" label
|
||||
Then I should see that I am not subscribed to the "bug" label
|
||||
@@ -0,0 +1,34 @@
|
||||
class Spinach::Features::Labels < Spinach::FeatureSteps
|
||||
include SharedAuthentication
|
||||
include SharedIssuable
|
||||
include SharedProject
|
||||
include SharedNote
|
||||
include SharedPaths
|
||||
include SharedMarkdown
|
||||
|
||||
step 'And I visit project "Shop" labels page' do
|
||||
visit namespace_project_labels_path(project.namespace, project)
|
||||
end
|
||||
|
||||
step 'I should see that I am subscribed to the "bug" label' do
|
||||
expect(subscribe_button).to have_content 'Unsubscribe'
|
||||
end
|
||||
|
||||
step 'I should see that I am not subscribed to the "bug" label' do
|
||||
expect(subscribe_button).to have_content 'Subscribe'
|
||||
end
|
||||
|
||||
step 'I click button "Unsubscribe" for the "bug" label' do
|
||||
subscribe_button.click
|
||||
end
|
||||
|
||||
step 'I click button "Subscribe" for the "bug" label' do
|
||||
subscribe_button.click
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def subscribe_button
|
||||
first('.subscribe-button span')
|
||||
end
|
||||
end
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
FactoryGirl.define do
|
||||
factory :label do
|
||||
title "Bug"
|
||||
sequence(:title) { |n| "label#{n}" }
|
||||
color "#990000"
|
||||
project
|
||||
end
|
||||
|
||||
@@ -100,6 +100,34 @@ describe Notify do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'that have been relabeled' do
|
||||
subject { Notify.relabeled_issue_email(recipient.id, issue.id, %w[foo bar baz], current_user.id) }
|
||||
|
||||
it_behaves_like 'a multiple recipients email'
|
||||
it_behaves_like 'an answer to an existing thread', 'issue'
|
||||
it_behaves_like 'it should show Gmail Actions View Issue link'
|
||||
it_behaves_like 'a user cannot unsubscribe through footer link'
|
||||
it_behaves_like 'an email with a labels subscriptions link in its footer'
|
||||
|
||||
it 'is sent as the author' do
|
||||
sender = subject.header[:from].addrs[0]
|
||||
expect(sender.display_name).to eq(current_user.name)
|
||||
expect(sender.address).to eq(gitlab_sender)
|
||||
end
|
||||
|
||||
it 'has the correct subject' do
|
||||
is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/
|
||||
end
|
||||
|
||||
it 'contains the names of the added labels' do
|
||||
is_expected.to have_body_text /foo, bar, and baz/
|
||||
end
|
||||
|
||||
it 'contains a link to the issue' do
|
||||
is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/
|
||||
end
|
||||
end
|
||||
|
||||
describe 'status changed' do
|
||||
let(:status) { 'closed' }
|
||||
subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) }
|
||||
@@ -219,6 +247,34 @@ describe Notify do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'that have been relabeled' do
|
||||
subject { Notify.relabeled_merge_request_email(recipient.id, merge_request.id, %w[foo bar baz], current_user.id) }
|
||||
|
||||
it_behaves_like 'a multiple recipients email'
|
||||
it_behaves_like 'an answer to an existing thread', 'merge_request'
|
||||
it_behaves_like 'it should show Gmail Actions View Merge request link'
|
||||
it_behaves_like 'a user cannot unsubscribe through footer link'
|
||||
it_behaves_like 'an email with a labels subscriptions link in its footer'
|
||||
|
||||
it 'is sent as the author' do
|
||||
sender = subject.header[:from].addrs[0]
|
||||
expect(sender.display_name).to eq(current_user.name)
|
||||
expect(sender.address).to eq(gitlab_sender)
|
||||
end
|
||||
|
||||
it 'has the correct subject' do
|
||||
is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/
|
||||
end
|
||||
|
||||
it 'contains the names of the added labels' do
|
||||
is_expected.to have_body_text /foo, bar, and baz/
|
||||
end
|
||||
|
||||
it 'contains a link to the merge request' do
|
||||
is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/
|
||||
end
|
||||
end
|
||||
|
||||
describe 'status changed' do
|
||||
let(:status) { 'reopened' }
|
||||
subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) }
|
||||
|
||||
@@ -112,6 +112,10 @@ shared_examples 'an unsubscribeable thread' do
|
||||
it { is_expected.to have_body_text /unsubscribe/ }
|
||||
end
|
||||
|
||||
shared_examples "a user cannot unsubscribe through footer link" do
|
||||
shared_examples 'a user cannot unsubscribe through footer link' do
|
||||
it { is_expected.not_to have_body_text /unsubscribe/ }
|
||||
end
|
||||
|
||||
shared_examples 'an email with a labels subscriptions link in its footer' do
|
||||
it { is_expected.to have_body_text /label subscriptions/ }
|
||||
end
|
||||
|
||||
@@ -113,6 +113,48 @@ describe Issue, "Issuable" do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#subscribed?' do
|
||||
context 'user is not a participant in the issue' do
|
||||
before { allow(issue).to receive(:participants).with(user).and_return([]) }
|
||||
|
||||
it 'returns false when no subcription exists' do
|
||||
expect(issue.subscribed?(user)).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns true when a subcription exists and subscribed is true' do
|
||||
issue.subscriptions.create(user: user, subscribed: true)
|
||||
|
||||
expect(issue.subscribed?(user)).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false when a subcription exists and subscribed is false' do
|
||||
issue.subscriptions.create(user: user, subscribed: false)
|
||||
|
||||
expect(issue.subscribed?(user)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'user is a participant in the issue' do
|
||||
before { allow(issue).to receive(:participants).with(user).and_return([user]) }
|
||||
|
||||
it 'returns false when no subcription exists' do
|
||||
expect(issue.subscribed?(user)).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns true when a subcription exists and subscribed is true' do
|
||||
issue.subscriptions.create(user: user, subscribed: true)
|
||||
|
||||
expect(issue.subscribed?(user)).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false when a subcription exists and subscribed is false' do
|
||||
issue.subscriptions.create(user: user, subscribed: false)
|
||||
|
||||
expect(issue.subscribed?(user)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#to_hook_data" do
|
||||
let(:data) { issue.to_hook_data(user) }
|
||||
let(:project) { issue.project }
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Subscribable, 'Subscribable' do
|
||||
let(:resource) { create(:issue) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
describe '#subscribed?' do
|
||||
it 'returns false when no subcription exists' do
|
||||
expect(resource.subscribed?(user)).to be_falsey
|
||||
end
|
||||
|
||||
it 'returns true when a subcription exists and subscribed is true' do
|
||||
resource.subscriptions.create(user: user, subscribed: true)
|
||||
|
||||
expect(resource.subscribed?(user)).to be_truthy
|
||||
end
|
||||
|
||||
it 'returns false when a subcription exists and subscribed is false' do
|
||||
resource.subscriptions.create(user: user, subscribed: false)
|
||||
|
||||
expect(resource.subscribed?(user)).to be_falsey
|
||||
end
|
||||
end
|
||||
describe '#subscribers' do
|
||||
it 'returns [] when no subcribers exists' do
|
||||
expect(resource.subscribers).to be_empty
|
||||
end
|
||||
|
||||
it 'returns the subscribed users' do
|
||||
resource.subscriptions.create(user: user, subscribed: true)
|
||||
resource.subscriptions.create(user: create(:user), subscribed: false)
|
||||
|
||||
expect(resource.subscribers).to eq [user]
|
||||
end
|
||||
end
|
||||
|
||||
describe '#toggle_subscription' do
|
||||
it 'toggles the current subscription state for the given user' do
|
||||
expect(resource.subscribed?(user)).to be_falsey
|
||||
|
||||
resource.toggle_subscription(user)
|
||||
|
||||
expect(resource.subscribed?(user)).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
describe '#unsubscribe' do
|
||||
it 'unsubscribes the given current user' do
|
||||
resource.subscriptions.create(user: user, subscribed: true)
|
||||
expect(resource.subscribed?(user)).to be_truthy
|
||||
|
||||
resource.unsubscribe(user)
|
||||
|
||||
expect(resource.subscribed?(user)).to be_falsey
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,6 +6,7 @@ describe Issues::UpdateService, services: true do
|
||||
let(:user3) { create(:user) }
|
||||
let(:issue) { create(:issue, title: 'Old title', assignee_id: user3.id) }
|
||||
let(:label) { create(:label) }
|
||||
let(:label2) { create(:label) }
|
||||
let(:project) { issue.project }
|
||||
|
||||
before do
|
||||
@@ -48,7 +49,7 @@ describe Issues::UpdateService, services: true do
|
||||
it { expect(@issue.assignee).to eq(user2) }
|
||||
it { expect(@issue).to be_closed }
|
||||
it { expect(@issue.labels.count).to eq(1) }
|
||||
it { expect(@issue.labels.first.title).to eq('Bug') }
|
||||
it { expect(@issue.labels.first.title).to eq(label.name) }
|
||||
|
||||
it 'should send email to user2 about assign of new issue and email to user3 about issue unassignment' do
|
||||
deliveries = ActionMailer::Base.deliveries
|
||||
@@ -148,6 +149,48 @@ describe Issues::UpdateService, services: true do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the issue is relabeled' do
|
||||
let!(:non_subscriber) { create(:user) }
|
||||
let!(:subscriber) { create(:user).tap { |u| label.toggle_subscription(u) } }
|
||||
|
||||
it 'sends notifications for subscribers of newly added labels' do
|
||||
opts = { label_ids: [label.id] }
|
||||
|
||||
perform_enqueued_jobs do
|
||||
@issue = Issues::UpdateService.new(project, user, opts).execute(issue)
|
||||
end
|
||||
|
||||
should_email(subscriber)
|
||||
should_not_email(non_subscriber)
|
||||
end
|
||||
|
||||
context 'when issue has the `label` label' do
|
||||
before { issue.labels << label }
|
||||
|
||||
it 'does not send notifications for existing labels' do
|
||||
opts = { label_ids: [label.id, label2.id] }
|
||||
|
||||
perform_enqueued_jobs do
|
||||
@issue = Issues::UpdateService.new(project, user, opts).execute(issue)
|
||||
end
|
||||
|
||||
should_not_email(subscriber)
|
||||
should_not_email(non_subscriber)
|
||||
end
|
||||
|
||||
it 'does not send notifications for removed labels' do
|
||||
opts = { label_ids: [label2.id] }
|
||||
|
||||
perform_enqueued_jobs do
|
||||
@issue = Issues::UpdateService.new(project, user, opts).execute(issue)
|
||||
end
|
||||
|
||||
should_not_email(subscriber)
|
||||
should_not_email(non_subscriber)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Issue has tasks' do
|
||||
before { update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" }) }
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ describe MergeRequests::UpdateService, services: true do
|
||||
let(:merge_request) { create(:merge_request, :simple, title: 'Old title', assignee_id: user3.id) }
|
||||
let(:project) { merge_request.project }
|
||||
let(:label) { create(:label) }
|
||||
let(:label2) { create(:label) }
|
||||
|
||||
before do
|
||||
project.team << [user, :master]
|
||||
@@ -53,7 +54,7 @@ describe MergeRequests::UpdateService, services: true do
|
||||
it { expect(@merge_request.assignee).to eq(user2) }
|
||||
it { expect(@merge_request).to be_closed }
|
||||
it { expect(@merge_request.labels.count).to eq(1) }
|
||||
it { expect(@merge_request.labels.first.title).to eq('Bug') }
|
||||
it { expect(@merge_request.labels.first.title).to eq(label.name) }
|
||||
it { expect(@merge_request.target_branch).to eq('target') }
|
||||
|
||||
it 'should execute hooks with update action' do
|
||||
@@ -176,6 +177,48 @@ describe MergeRequests::UpdateService, services: true do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the issue is relabeled' do
|
||||
let!(:non_subscriber) { create(:user) }
|
||||
let!(:subscriber) { create(:user).tap { |u| label.toggle_subscription(u) } }
|
||||
|
||||
it 'sends notifications for subscribers of newly added labels' do
|
||||
opts = { label_ids: [label.id] }
|
||||
|
||||
perform_enqueued_jobs do
|
||||
@merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
|
||||
end
|
||||
|
||||
should_email(subscriber)
|
||||
should_not_email(non_subscriber)
|
||||
end
|
||||
|
||||
context 'when issue has the `label` label' do
|
||||
before { merge_request.labels << label }
|
||||
|
||||
it 'does not send notifications for existing labels' do
|
||||
opts = { label_ids: [label.id, label2.id] }
|
||||
|
||||
perform_enqueued_jobs do
|
||||
@merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
|
||||
end
|
||||
|
||||
should_not_email(subscriber)
|
||||
should_not_email(non_subscriber)
|
||||
end
|
||||
|
||||
it 'does not send notifications for removed labels' do
|
||||
opts = { label_ids: [label2.id] }
|
||||
|
||||
perform_enqueued_jobs do
|
||||
@merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request)
|
||||
end
|
||||
|
||||
should_not_email(subscriber)
|
||||
should_not_email(non_subscriber)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when MergeRequest has tasks' do
|
||||
before { update_merge_request({ description: "- [ ] Task 1\n- [ ] Task 2" }) }
|
||||
|
||||
|
||||
@@ -224,6 +224,15 @@ describe NotificationService, services: true do
|
||||
|
||||
should_not_email(issue.assignee)
|
||||
end
|
||||
|
||||
it "emails subscribers of the issue's labels" do
|
||||
subscriber = create(:user)
|
||||
label = create(:label, issues: [issue])
|
||||
label.toggle_subscription(subscriber)
|
||||
notification.new_issue(issue, @u_disabled)
|
||||
|
||||
should_email(subscriber)
|
||||
end
|
||||
end
|
||||
|
||||
describe :reassigned_issue do
|
||||
@@ -296,6 +305,35 @@ describe NotificationService, services: true do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#relabeled_issue' do
|
||||
let(:label) { create(:label, issues: [issue]) }
|
||||
let(:label2) { create(:label) }
|
||||
let!(:subscriber_to_label) { create(:user).tap { |u| label.toggle_subscription(u) } }
|
||||
let!(:subscriber_to_label2) { create(:user).tap { |u| label2.toggle_subscription(u) } }
|
||||
|
||||
it "emails subscribers of the issue's added labels only" do
|
||||
notification.relabeled_issue(issue, [label2], @u_disabled)
|
||||
|
||||
should_not_email(subscriber_to_label)
|
||||
should_email(subscriber_to_label2)
|
||||
end
|
||||
|
||||
it "doesn't send email to anyone but subscribers of the given labels" do
|
||||
notification.relabeled_issue(issue, [label2], @u_disabled)
|
||||
|
||||
should_not_email(issue.assignee)
|
||||
should_not_email(issue.author)
|
||||
should_not_email(@u_watcher)
|
||||
should_not_email(@u_participant_mentioned)
|
||||
should_not_email(@subscriber)
|
||||
should_not_email(@watcher_and_subscriber)
|
||||
should_not_email(@unsubscriber)
|
||||
should_not_email(@u_participating)
|
||||
should_not_email(subscriber_to_label)
|
||||
should_email(subscriber_to_label2)
|
||||
end
|
||||
end
|
||||
|
||||
describe :close_issue do
|
||||
it 'should sent email to issue assignee and issue author' do
|
||||
notification.close_issue(issue, @u_disabled)
|
||||
@@ -349,6 +387,15 @@ describe NotificationService, services: true do
|
||||
should_not_email(@u_participating)
|
||||
should_not_email(@u_disabled)
|
||||
end
|
||||
|
||||
it "emails subscribers of the merge request's labels" do
|
||||
subscriber = create(:user)
|
||||
label = create(:label, merge_requests: [merge_request])
|
||||
label.toggle_subscription(subscriber)
|
||||
notification.new_merge_request(merge_request, @u_disabled)
|
||||
|
||||
should_email(subscriber)
|
||||
end
|
||||
end
|
||||
|
||||
describe :reassigned_merge_request do
|
||||
@@ -366,6 +413,35 @@ describe NotificationService, services: true do
|
||||
end
|
||||
end
|
||||
|
||||
describe :relabel_merge_request do
|
||||
let(:label) { create(:label, merge_requests: [merge_request]) }
|
||||
let(:label2) { create(:label) }
|
||||
let!(:subscriber_to_label) { create(:user).tap { |u| label.toggle_subscription(u) } }
|
||||
let!(:subscriber_to_label2) { create(:user).tap { |u| label2.toggle_subscription(u) } }
|
||||
|
||||
it "emails subscribers of the merge request's added labels only" do
|
||||
notification.relabeled_merge_request(merge_request, [label2], @u_disabled)
|
||||
|
||||
should_not_email(subscriber_to_label)
|
||||
should_email(subscriber_to_label2)
|
||||
end
|
||||
|
||||
it "doesn't send email to anyone but subscribers of the given labels" do
|
||||
notification.relabeled_merge_request(merge_request, [label2], @u_disabled)
|
||||
|
||||
should_not_email(merge_request.assignee)
|
||||
should_not_email(merge_request.author)
|
||||
should_not_email(@u_watcher)
|
||||
should_not_email(@u_participant_mentioned)
|
||||
should_not_email(@subscriber)
|
||||
should_not_email(@watcher_and_subscriber)
|
||||
should_not_email(@unsubscriber)
|
||||
should_not_email(@u_participating)
|
||||
should_not_email(subscriber_to_label)
|
||||
should_email(subscriber_to_label2)
|
||||
end
|
||||
end
|
||||
|
||||
describe :closed_merge_request do
|
||||
it do
|
||||
notification.close_mr(merge_request, @u_disabled)
|
||||
@@ -467,16 +543,4 @@ describe NotificationService, services: true do
|
||||
# Make the watcher a subscriber to detect dupes
|
||||
issuable.subscriptions.create(user: @watcher_and_subscriber, subscribed: true)
|
||||
end
|
||||
|
||||
def sent_to_user?(user)
|
||||
ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
|
||||
end
|
||||
|
||||
def should_email(user)
|
||||
expect(sent_to_user?(user)).to be_truthy
|
||||
end
|
||||
|
||||
def should_not_email(user)
|
||||
expect(sent_to_user?(user)).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
@@ -32,6 +32,7 @@ RSpec.configure do |config|
|
||||
config.include LoginHelpers, type: :feature
|
||||
config.include LoginHelpers, type: :request
|
||||
config.include StubConfiguration
|
||||
config.include EmailHelpers
|
||||
config.include RelativeUrl, type: feature
|
||||
config.include TestEnv
|
||||
config.include ActiveJob::TestHelper
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
module EmailHelpers
|
||||
def sent_to_user?(user)
|
||||
ActionMailer::Base.deliveries.map(&:to).flatten.count(user.email) == 1
|
||||
end
|
||||
|
||||
def should_email(user)
|
||||
expect(sent_to_user?(user)).to be_truthy
|
||||
end
|
||||
|
||||
def should_not_email(user)
|
||||
expect(sent_to_user?(user)).to be_falsey
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user