Send email on watcher being removed

This commit is contained in:
Yegor Tovstik
2019-12-06 12:48:25 +01:00
parent 4774d2afdb
commit ee2762e8ef
15 changed files with 333 additions and 43 deletions
+15
View File
@@ -89,6 +89,21 @@ class UserMailer < BaseMailer
end
end
def work_package_watcher_removed(work_package, user, watcher_remover)
User.execute_as user do
@issue = work_package
@watcher_remover = watcher_remover
set_work_package_headers(work_package)
message_id work_package, user
references work_package, user
with_locale_for(user) do
mail to: user.mail, subject: subject_for_work_package(work_package)
end
end
end
def password_lost(token)
return unless token.user # token's can have no user
@@ -0,0 +1,39 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
class WatcherAddedNotificationMailer < WatcherNotificationMailer
class << self
private
def perform_notification_job(watcher, watcher_changer)
DeliverWatcherAddedNotificationJob.
perform_later(watcher.id, watcher.user.id, watcher_changer.id)
end
end
end
+11 -7
View File
@@ -29,18 +29,22 @@
class WatcherNotificationMailer
class << self
def handle_watcher(watcher, watcher_setter)
def handle_watcher(watcher, watcher_changer)
# We only handle this watcher setting if associated user wants to be notified
# about it.
return unless notify_about_watcher_added?(watcher, watcher_setter)
return unless notify_about_watcher_changed?(watcher, watcher_changer)
unless other_jobs_queued?(watcher.watchable)
DeliverWatcherNotificationJob.perform_later(watcher.id, watcher.user.id, watcher_setter.id)
perform_notification_job(watcher, watcher_changer)
end
end
private
def perform_notification_job(watcher, watcher_changer)
raise NotImplementedError, 'Subclass has to implement #notification_job'
end
# HACK: TODO this needs generalization as well as performance improvements
# We need to make sure no work package created or updated job is queued to avoid sending two
# mails in short succession.
@@ -49,8 +53,8 @@ class WatcherNotificationMailer
"%NotificationJob%journal_id: #{work_package.journals.last.id}%").exists?
end
def notify_about_watcher_added?(watcher, watcher_setter)
return false if notify_about_self_watching?(watcher, watcher_setter)
def notify_about_watcher_changed?(watcher, watcher_changer)
return false if notify_about_self_watching?(watcher, watcher_changer)
case watcher.user.mail_notification
when 'only_my_events'
@@ -62,8 +66,8 @@ class WatcherNotificationMailer
end
end
def notify_about_self_watching?(watcher, watcher_setter)
watcher.user == watcher_setter && !watcher.user.pref.self_notified?
def notify_about_self_watching?(watcher, watcher_changer)
watcher.user == watcher_changer && !watcher.user.pref.self_notified?
end
def watching_selected_includes_project?(watcher)
@@ -0,0 +1,40 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
class WatcherRemovedNotificationMailer < WatcherNotificationMailer
class << self
private
def perform_notification_job(watcher, watcher_changer)
# As watcher is already destroyed we need to pass a hash
DeliverWatcherRemovedNotificationJob.
perform_later(watcher.attributes, watcher.user.id, watcher_changer.id)
end
end
end
@@ -0,0 +1,37 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2017 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See docs/COPYRIGHT.rdoc for more details.
++#%>
<%= t(:text_work_package_watcher_removed, id: "##{@issue.id}", watcher_remover: @watcher_remover) %>
<hr />
<%= render partial: 'issue_details', locals: { issue: @issue } %>
<p>
<%= format_text(t(:text_latest_note, note: last_issue_note(@issue)),
only_path: false,
object: @issue,
project: @issue.project) %>
</p>
@@ -0,0 +1,34 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2017 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See docs/COPYRIGHT.rdoc for more details.
++#%>
<%= t(:text_work_package_watcher_added, id: "##{@issue.id}", watcher_setter: @watcher_setter) %>
----------------------------------------
<%= render partial: 'issue_details', locals: { issue: @issue } %>
<%= t(:text_latest_note, note: last_issue_note(@issue)) %>
@@ -0,0 +1,36 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
class DeliverWatcherAddedNotificationJob < DeliverWatcherNotificationJob
def render_mail(recipient:, sender:)
return unless watcher
UserMailer.work_package_watcher_added(watcher.watchable, recipient, sender)
end
end
@@ -28,17 +28,14 @@
#++
class DeliverWatcherNotificationJob < DeliverNotificationJob
def perform(watcher_id, recipient_id, watcher_setter_id)
def perform(watcher_id, recipient_id, watcher_changer_id)
@watcher_id = watcher_id
super(recipient_id, watcher_setter_id)
super(recipient_id, watcher_changer_id)
end
def render_mail(recipient:, sender:)
return nil unless watcher
UserMailer.work_package_watcher_added(watcher.watchable, recipient, sender)
raise NotImplementedError, 'Subclass has to implement #render_mail'
end
private
@@ -0,0 +1,43 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
class DeliverWatcherRemovedNotificationJob < DeliverWatcherNotificationJob
# As watcher is already destroyed we need to pass a hash
def perform(watcher_attributes, recipient_id, watcher_remover_id)
@watcher = Watcher.new(watcher_attributes)
super(watcher.id, recipient_id, watcher_remover_id)
end
def render_mail(recipient:, sender:)
return unless watcher
UserMailer.work_package_watcher_removed(watcher.watchable, recipient, sender)
end
end
+5 -1
View File
@@ -37,5 +37,9 @@ OpenProject::Notifications.subscribe(OpenProject::Events::AGGREGATED_WORK_PACKAG
end
OpenProject::Notifications.subscribe('watcher_added') do |payload|
WatcherNotificationMailer.handle_watcher(payload[:watcher], payload[:watcher_setter])
WatcherAddedNotificationMailer.handle_watcher(payload[:watcher], payload[:watcher_setter])
end
OpenProject::Notifications.subscribe('watcher_removed') do |payload|
WatcherRemovedNotificationMailer.handle_watcher(payload[:watcher], payload[:watcher_remover])
end
+1
View File
@@ -2316,6 +2316,7 @@ en:
text_work_package_category_reassign_to: "Reassign work packages to this category"
text_work_package_updated: "Work package %{id} has been updated by %{author}."
text_work_package_watcher_added: "You have been added as a watcher to Work package %{id} by %{watcher_setter}."
text_work_package_watcher_removed: "You have been removed from watchers of Work package %{id} by %{watcher_remover}."
text_work_packages_destroy_confirmation: "Are you sure you want to delete the selected work package(s)?"
text_work_packages_ref_in_commit_messages: "Referencing and fixing work packages in commit messages"
text_journal_added: "%{label} %{value} added"
+4
View File
@@ -34,8 +34,12 @@ class Services::RemoveWatcher
def run(success: -> {}, failure: -> {})
if @work_package.watcher_users.include?(@user)
@watcher = @work_package.watchers.find_by_user_id(@user.id)
@work_package.watcher_users.delete(@user)
success.call
OpenProject::Notifications.send('watcher_removed',
watcher: @watcher,
watcher_remover: User.current)
else
failure.call
end
+13
View File
@@ -143,6 +143,19 @@ describe UserMailer, type: :mailer do
end
end
describe '#work_package_watcher_removed' do
let(:watcher_remover) { user }
before do
UserMailer.work_package_watcher_removed(work_package, recipient, watcher_remover).deliver_now
end
it_behaves_like 'mail is sent'
it 'contains the WP subject in the mail subject' do
expect(ActionMailer::Base.deliveries.first.subject).to include(work_package.subject)
end
end
describe :wiki_content_added do
let(:wiki_content) { FactoryBot.create(:wiki_content) }
+31 -21
View File
@@ -28,9 +28,11 @@
#++
require 'spec_helper'
describe WatcherNotificationMailer do
def call_listener(watcher, watcher_setter)
described_class.handle_watcher(watcher, watcher_setter)
shared_examples 'WatcherNotificationMailer' do |watcher_notification_job|
let(:watcher_notification_job) { watcher_notification_job }
def call_listener(watcher, watcher_changer)
described_class.handle_watcher(watcher, watcher_changer)
end
before do
@@ -47,7 +49,7 @@ describe WatcherNotificationMailer do
work_package
}
let(:watcher_setter) do
let(:watcher_changer) do
FactoryBot.build_stubbed(:user,
mail_notification: watching_setting,
preference: user_pref)
@@ -81,8 +83,8 @@ describe WatcherNotificationMailer do
let(:self_notified) { true }
it 'notifies the watcher' do
expect(DeliverWatcherNotificationJob).to receive(:perform_later)
call_listener(watcher, watcher_setter)
expect(watcher_notification_job).to receive(:perform_later)
call_listener(watcher, watcher_changer)
end
end
@@ -91,30 +93,30 @@ describe WatcherNotificationMailer do
let(:self_notified) { false }
it 'notifies the watcher' do
expect(DeliverWatcherNotificationJob).to receive(:perform_later)
call_listener(watcher, watcher_setter)
expect(watcher_notification_job).to receive(:perform_later)
call_listener(watcher, watcher_changer)
end
end
context 'but when watcher is added by theirself
and has self_notified deactivated' do
let(:watching_user) { watcher_setter }
let(:watching_user) { watcher_changer }
let(:self_notified) { false }
it 'does not notify the watcher' do
expect(DeliverWatcherNotificationJob).not_to receive(:perform_later)
call_listener(watcher, watcher_setter)
expect(watcher_notification_job).not_to receive(:perform_later)
call_listener(watcher, watcher_changer)
end
end
context 'but when watcher is added by theirself
and has self_notified activated' do
let(:watching_user) { watcher_setter }
let(:watching_user) { watcher_changer }
let(:self_notified) { true }
it 'notifies the watcher' do
expect(DeliverWatcherNotificationJob).to receive(:perform_later)
call_listener(watcher, watcher_setter)
expect(watcher_notification_job).to receive(:perform_later)
call_listener(watcher, watcher_changer)
end
end
end
@@ -124,18 +126,18 @@ describe WatcherNotificationMailer do
context 'when added by a different user' do
it 'does not notify the watcher' do
expect(DeliverWatcherNotificationJob).not_to receive(:perform_later)
call_listener(watcher, watcher_setter)
expect(watcher_notification_job).not_to receive(:perform_later)
call_listener(watcher, watcher_changer)
end
end
context 'when watcher is added by theirself' do
let(:watching_user) { watcher_setter }
let(:watching_user) { watcher_changer }
let(:self_notified) { false }
it 'does not notify the watcher' do
expect(DeliverWatcherNotificationJob).not_to receive(:perform_later)
call_listener(watcher, watcher_setter)
expect(watcher_notification_job).not_to receive(:perform_later)
call_listener(watcher, watcher_changer)
end
end
end
@@ -153,7 +155,7 @@ describe WatcherNotificationMailer do
end
it_behaves_like 'does not notify the added watcher for', 'only_owner' do
before do
work_package.author = watcher_setter
work_package.author = watcher_changer
end
end
@@ -164,7 +166,7 @@ describe WatcherNotificationMailer do
end
it_behaves_like 'does not notify the added watcher for', 'only_assigned' do
before do
work_package.assigned_to = watcher_setter
work_package.assigned_to = watcher_changer
end
end
@@ -190,3 +192,11 @@ describe WatcherNotificationMailer do
end
end
end
describe WatcherRemovedNotificationMailer do
include_examples "WatcherNotificationMailer", DeliverWatcherRemovedNotificationJob
end
describe WatcherAddedNotificationMailer do
include_examples "WatcherNotificationMailer", DeliverWatcherAddedNotificationJob
end
@@ -29,28 +29,29 @@
require 'spec_helper'
describe DeliverWatcherNotificationJob, type: :model do
shared_examples "DeliverWatcherNotificationJob" do |mailer_method_name|
let(:mailer_method_name) { mailer_method_name }
let(:project) { FactoryBot.create(:project) }
let(:role) { FactoryBot.create(:role, permissions: [:view_work_packages]) }
let(:watcher_setter) { FactoryBot.create(:user) }
let(:watcher_changer) { FactoryBot.create(:user) }
let(:watcher_user) do
FactoryBot.create(:user, member_in_project: project, member_through_role: role)
end
let(:work_package) { FactoryBot.build(:work_package, project: project) }
let(:watcher) { FactoryBot.create(:watcher, watchable: work_package, user: watcher_user) }
subject { described_class.new.perform(watcher.id, watcher_user.id, watcher_setter.id) }
subject { described_class.new.perform(watcher_parameter, watcher_user.id, watcher_changer.id) }
before do
# make sure no actual calls make it into the UserMailer
allow(UserMailer).to receive(:work_package_watcher_added)
allow(UserMailer).to receive(mailer_method_name)
.and_return(double('mail', deliver_now: nil))
end
it 'sends a mail' do
expect(UserMailer).to receive(:work_package_watcher_added).with(work_package,
watcher_user,
watcher_setter)
expect(UserMailer).to receive(mailer_method_name).with(work_package,
watcher_user,
watcher_changer)
subject
end
@@ -59,7 +60,7 @@ describe DeliverWatcherNotificationJob, type: :model do
before do
mail = double('mail')
allow(mail).to receive(:deliver_now).and_raise(SocketError)
expect(UserMailer).to receive(:work_package_watcher_added).and_return(mail)
expect(UserMailer).to receive(mailer_method_name).and_return(mail)
end
it 'raises the error' do
@@ -68,3 +69,15 @@ describe DeliverWatcherNotificationJob, type: :model do
end
end
end
describe DeliverWatcherRemovedNotificationJob, type: :model do
include_examples "DeliverWatcherNotificationJob", :work_package_watcher_removed do
let(:watcher_parameter) { watcher.attributes }
end
end
describe DeliverWatcherAddedNotificationJob, type: :model do
include_examples "DeliverWatcherNotificationJob", :work_package_watcher_added do
let(:watcher_parameter) { watcher }
end
end