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
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/subscription.js.coffee34
-rw-r--r--app/assets/stylesheets/pages/labels.scss4
-rw-r--r--app/controllers/concerns/toggle_subscription_action.rb17
-rw-r--r--app/controllers/projects/issues_controller.rb11
-rw-r--r--app/controllers/projects/labels_controller.rb9
-rw-r--r--app/controllers/projects/merge_requests_controller.rb10
-rw-r--r--app/helpers/labels_helper.rb8
-rw-r--r--app/mailers/emails/issues.rb28
-rw-r--r--app/mailers/emails/merge_requests.rb49
-rw-r--r--app/models/concerns/issuable.rb22
-rw-r--r--app/models/concerns/subscribable.rb44
-rw-r--r--app/models/label.rb2
-rw-r--r--app/models/subscription.rb2
-rw-r--r--app/services/issuable_base_service.rb17
-rw-r--r--app/services/issues/update_service.rb9
-rw-r--r--app/services/merge_requests/update_service.rb13
-rw-r--r--app/services/notification_service.rb70
-rw-r--r--app/views/layouts/notify.html.haml13
-rw-r--r--app/views/notify/_reassigned_issuable_email.text.erb2
-rw-r--r--app/views/notify/_relabeled_issuable_email.html.haml3
-rw-r--r--app/views/notify/_relabeled_issuable_email.text.erb3
-rw-r--r--app/views/notify/relabeled_issue_email.html.haml1
-rw-r--r--app/views/notify/relabeled_issue_email.text.erb1
-rw-r--r--app/views/notify/relabeled_merge_request_email.html.haml1
-rw-r--r--app/views/notify/relabeled_merge_request_email.text.erb1
-rw-r--r--app/views/projects/labels/_label.html.haml10
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml4
27 files changed, 267 insertions, 121 deletions
diff --git a/app/assets/javascripts/subscription.js.coffee b/app/assets/javascripts/subscription.js.coffee
index 7f41616d4e7..084f0e0dc65 100644
--- a/app/assets/javascripts/subscription.js.coffee
+++ b/app/assets/javascripts/subscription.js.coffee
@@ -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')
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 5ec0966194c..61ee34b695e 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -41,3 +41,7 @@
.color-label {
padding: 3px 4px;
}
+
+.label-subscription {
+ display: inline-block;
+}
diff --git a/app/controllers/concerns/toggle_subscription_action.rb b/app/controllers/concerns/toggle_subscription_action.rb
new file mode 100644
index 00000000000..8a43c0b93c4
--- /dev/null
+++ b/app/controllers/concerns/toggle_subscription_action.rb
@@ -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
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 67faa1e4437..b0a03ee45cc 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -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)
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index ecac3c395ec..40d8098690a 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -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)
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 03ba289eb94..61b82c9db46 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -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
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 89a054289e8..4455dcd0e20 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -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
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 4a88cb61132..5f9adb32e00 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -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
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 325996e2e16..55bb4f65270 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -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
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 3c42f582937..86ab84615ba 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -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,
diff --git a/app/models/concerns/subscribable.rb b/app/models/concerns/subscribable.rb
new file mode 100644
index 00000000000..d5a881b2445
--- /dev/null
+++ b/app/models/concerns/subscribable.rb
@@ -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
diff --git a/app/models/label.rb b/app/models/label.rb
index 5ff644b8426..f7ffc0b7f36 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -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)
diff --git a/app/models/subscription.rb b/app/models/subscription.rb
index dd75d3ab8ba..dd800ce110f 100644
--- a/app/models/subscription.rb
+++ b/app/models/subscription.rb
@@ -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
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index ca87dca4a70..18f76d3f650 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -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
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 51ef9dfe610..3563cbaa997 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -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
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 6319ad805b6..477c64e7377 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -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
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index ca8a41d93b8..19a6779dea9 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -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
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index 325c68c69dc..37b4d562966 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -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
diff --git a/app/views/notify/_reassigned_issuable_email.text.erb b/app/views/notify/_reassigned_issuable_email.text.erb
index 855d37429d9..daf20a226dd 100644
--- a/app/views/notify/_reassigned_issuable_email.text.erb
+++ b/app/views/notify/_reassigned_issuable_email.text.erb
@@ -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'}" %>
diff --git a/app/views/notify/_relabeled_issuable_email.html.haml b/app/views/notify/_relabeled_issuable_email.html.haml
new file mode 100644
index 00000000000..80a0de255be
--- /dev/null
+++ b/app/views/notify/_relabeled_issuable_email.html.haml
@@ -0,0 +1,3 @@
+%p
+ #{'Label'.pluralize(@label_names.size)} added:
+ %em= @label_names.to_sentence
diff --git a/app/views/notify/_relabeled_issuable_email.text.erb b/app/views/notify/_relabeled_issuable_email.text.erb
new file mode 100644
index 00000000000..6a83d79fd61
--- /dev/null
+++ b/app/views/notify/_relabeled_issuable_email.text.erb
@@ -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 }]) %>
diff --git a/app/views/notify/relabeled_issue_email.html.haml b/app/views/notify/relabeled_issue_email.html.haml
new file mode 100644
index 00000000000..b17b16e1814
--- /dev/null
+++ b/app/views/notify/relabeled_issue_email.html.haml
@@ -0,0 +1 @@
+= render 'relabeled_issuable_email', issuable: @issue
diff --git a/app/views/notify/relabeled_issue_email.text.erb b/app/views/notify/relabeled_issue_email.text.erb
new file mode 100644
index 00000000000..eeced97f601
--- /dev/null
+++ b/app/views/notify/relabeled_issue_email.text.erb
@@ -0,0 +1 @@
+<%= render 'relabeled_issuable_email', issuable: @issue %>
diff --git a/app/views/notify/relabeled_merge_request_email.html.haml b/app/views/notify/relabeled_merge_request_email.html.haml
new file mode 100644
index 00000000000..9eaa9afa5b1
--- /dev/null
+++ b/app/views/notify/relabeled_merge_request_email.html.haml
@@ -0,0 +1 @@
+= render 'relabeled_issuable_email', issuable: @merge_request
diff --git a/app/views/notify/relabeled_merge_request_email.text.erb b/app/views/notify/relabeled_merge_request_email.text.erb
new file mode 100644
index 00000000000..87bc80ead32
--- /dev/null
+++ b/app/views/notify/relabeled_merge_request_email.text.erb
@@ -0,0 +1 @@
+<%= render 'relabeled_issuable_email', issuable: @merge_request %>
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index f7ddd30c5a9..4927d239c1e 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -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');
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 9020a1330a3..23b1ed1e51b 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -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();