From 8aca19d8271250cfdc3559455ff68fc342f4a9b9 Mon Sep 17 00:00:00 2001 From: ulferts Date: Fri, 24 Sep 2021 16:23:26 +0200 Subject: [PATCH] sending empty mail on mentioning --- app/mailers/application_mailer.rb | 16 +++ app/mailers/user_mailer.rb | 17 +-- app/mailers/work_package_mailer.rb | 65 +++++++++++ .../mail_service/work_package_strategy.rb | 40 +++++++ .../work_package_mailer/mentioned.html.erb | 0 .../work_package_mailer/mentioned.text.erb | 0 config/locales/en.yml | 2 + spec/factories/user_factory.rb | 6 + .../notifications/immediate_reminder_spec.rb | 52 +++++++++ spec/mailers/work_package_mailer_spec.rb | 108 ++++++++++++++++++ .../notifications/mail_service_spec.rb | 86 +++++++++++++- 11 files changed, 375 insertions(+), 17 deletions(-) create mode 100644 app/mailers/work_package_mailer.rb create mode 100644 app/services/notifications/mail_service/work_package_strategy.rb create mode 100644 app/views/work_package_mailer/mentioned.html.erb create mode 100644 app/views/work_package_mailer/mentioned.text.erb create mode 100644 spec/mailers/work_package_mailer_spec.rb diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 683bddc7c0..bcbd7e363b 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -110,10 +110,26 @@ class ApplicationMailer < ActionMailer::Base super(headers, &block) end + # like #mail, but contains special author based filters + # currently only: + # - remove_self_notifications + # might be refactored at a later time to be as generic as Interceptors + def mail_for_author(author, headers = {}, &block) + message = mail headers, &block + + self.class.remove_self_notifications(message, author) + + message + end + def message_id(object, user) headers['Message-ID'] = "<#{self.class.generate_message_id(object, user)}>" end + def references(object, user) + headers['References'] = "<#{self.class.generate_message_id(object, user)}>" + end + # Prepends given fields with 'X-OpenProject-' to save some duplication def open_project_headers(hash) hash.each { |key, value| headers["X-OpenProject-#{key}"] = value.to_s } diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 7f6bd7520d..23f9bf3c64 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -279,22 +279,7 @@ class UserMailer < ApplicationMailer "#{wp.project.name} - #{wp.status.name} #{wp.type.name} ##{wp.id}: #{wp.subject}" end - # like #mail, but contains special author based filters - # currently only: - # - remove_self_notifications - # might be refactored at a later time to be as generic as Interceptors - def mail_for_author(author, headers = {}, &block) - message = mail headers, &block - - self.class.remove_self_notifications(message, author) - - message - end - - def references(object, user) - headers['References'] = "<#{self.class.generate_message_id(object, user)}>" - end - + # TODO: Delete since moved to WorkPackageMailer def set_work_package_headers(work_package) open_project_headers 'Project' => work_package.project.identifier, 'Issue-Id' => work_package.id, diff --git a/app/mailers/work_package_mailer.rb b/app/mailers/work_package_mailer.rb new file mode 100644 index 0000000000..9cdbc2c1dc --- /dev/null +++ b/app/mailers/work_package_mailer.rb @@ -0,0 +1,65 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2021 the OpenProject GmbH +# +# 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-2013 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 COPYRIGHT and LICENSE files for more details. +#++ + +class WorkPackageMailer < ApplicationMailer + def mentioned(recipient, journal) + author = journal.user + work_package = journal.journable + + User.execute_as author do + set_work_package_headers(work_package) + + message_id journal, recipient + references work_package, recipient + + with_locale_for(recipient) do + mail_for_author author, + to: recipient.mail, + subject: I18n.t(:'mail.mention.subject', + user_name: author.name, + id: work_package.id, + subject: work_package.subject) + end + end + end + + private + + def set_work_package_headers(work_package) + open_project_headers 'Project' => work_package.project.identifier, + 'WorkPackage-Id' => work_package.id, + 'WorkPackage-Author' => work_package.author.login, + 'Type' => 'WorkPackage' + + if work_package.assigned_to + open_project_headers 'WorkPackage-Assignee' => work_package.assigned_to.login + end + end +end diff --git a/app/services/notifications/mail_service/work_package_strategy.rb b/app/services/notifications/mail_service/work_package_strategy.rb new file mode 100644 index 0000000000..143f6eb11c --- /dev/null +++ b/app/services/notifications/mail_service/work_package_strategy.rb @@ -0,0 +1,40 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2021 the OpenProject GmbH +# +# 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-2013 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 COPYRIGHT and LICENSE files for more details. +#++ + +module Notifications::MailService::WorkPackageStrategy + class << self + def send_mail(notification) + return unless notification.ian_mentioned? + return unless notification.recipient.pref.immediate_reminders[:mentioned] + + WorkPackageMailer + .mentioned(notification.recipient, notification.journal) + .deliver_later + end + end +end diff --git a/app/views/work_package_mailer/mentioned.html.erb b/app/views/work_package_mailer/mentioned.html.erb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/views/work_package_mailer/mentioned.text.erb b/app/views/work_package_mailer/mentioned.text.erb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/config/locales/en.yml b/config/locales/en.yml index dcd87ee529..fbba1f23a5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1998,6 +1998,8 @@ en: updated_at: 'at %{timestamp} by %{user}' you_have: 'You have' logo_alt_text: 'Logo' + mention: + subject: "%{user_name} mentioned you in #%{id} - %{subject}" notification: center: 'To notification center' settings: 'Change email settings' diff --git a/spec/factories/user_factory.rb b/spec/factories/user_factory.rb index d522f2f823..c0363aa122 100644 --- a/spec/factories/user_factory.rb +++ b/spec/factories/user_factory.rb @@ -72,6 +72,12 @@ FactoryBot.define do end end + callback(:after_stub) do |user, evaluator| + if evaluator.preferences.present? + user.preference = FactoryBot.build_stubbed(:user_preference, user: user, settings: evaluator.preferences) + end + end + factory :admin do firstname { 'OpenProject' } sequence(:lastname) { |n| "Admin#{n}" } diff --git a/spec/features/notifications/immediate_reminder_spec.rb b/spec/features/notifications/immediate_reminder_spec.rb index a148016a23..1c38b3df75 100644 --- a/spec/features/notifications/immediate_reminder_spec.rb +++ b/spec/features/notifications/immediate_reminder_spec.rb @@ -50,4 +50,56 @@ describe "Immediate reminder settings", type: :feature, js: true do it_behaves_like 'immediate reminder settings' end + + describe 'email sending', js: false do + let(:project) { FactoryBot.create(:project) } + let(:work_package) { FactoryBot.create(:work_package, project: project) } + let(:receiver) do + FactoryBot.create( + :user, + preferences: { + immediate_reminders: { + mentioned: true + } + }, + notification_settings: [ + FactoryBot.build(:in_app_notification_setting, + mentioned: true), + FactoryBot.build(:mail_notification_setting, + mentioned: true) + ], + member_in_project: project, + member_with_permissions: %i[view_work_packages] + ) + end + + current_user do + FactoryBot.create(:user) + end + + it 'sends a mail to the mentioned user immediately' do + perform_enqueued_jobs do + note = <<~NOTE + Hey + @#{receiver.name} + + NOTE + + work_package.add_journal(current_user, note) + work_package.save! + end + + expect(ActionMailer::Base.deliveries.length) + .to be 1 + + expect(ActionMailer::Base.deliveries.first.subject) + .to eql I18n.t(:'mail.mention.subject', + user_name: current_user.name, + id: work_package.id, + subject: work_package.subject) + end + end end diff --git a/spec/mailers/work_package_mailer_spec.rb b/spec/mailers/work_package_mailer_spec.rb new file mode 100644 index 0000000000..7ad4498e0d --- /dev/null +++ b/spec/mailers/work_package_mailer_spec.rb @@ -0,0 +1,108 @@ +#-- encoding: UTF-8 + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2021 the OpenProject GmbH +# +# 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-2013 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 COPYRIGHT and LICENSE files for more details. +#++ + +require 'spec_helper' + +describe WorkPackageMailer, type: :mailer do + include OpenProject::ObjectLinking + include ActionView::Helpers::UrlHelper + include OpenProject::StaticRouting::UrlHelpers + + let(:work_package) do + FactoryBot.build_stubbed(:work_package, + project: project, + assigned_to: assignee) + end + let(:project) { FactoryBot.build_stubbed(:project) } + let(:author) { FactoryBot.build_stubbed(:user) } + let(:recipient) { FactoryBot.build_stubbed(:user) } + let(:assignee) { FactoryBot.build_stubbed(:user) } + let(:journal) do + FactoryBot.build_stubbed(:work_package_journal, + journable: work_package, + user: author) + end + + describe '#mentioned' do + subject(:mail) { described_class.mentioned(recipient, journal) } + + it "has a subject" do + expect(mail.subject) + .to eql I18n.t(:'mail.mention.subject', + user_name: author.name, + id: work_package.id, + subject: work_package.subject) + end + + it 'is sent to the recipient' do + expect(mail.to) + .to match_array([recipient.mail]) + end + + it 'has a project header' do + expect(mail['X-OpenProject-Project'].value) + .to eql project.identifier + end + + it 'has a work package id header' do + expect(mail['X-OpenProject-WorkPackage-Id'].value) + .to eql work_package.id.to_s + end + + it 'has a work package author header' do + expect(mail['X-OpenProject-WorkPackage-Author'].value) + .to eql work_package.author.login + end + + it 'has a type header' do + expect(mail['X-OpenProject-Type'].value) + .to eql 'WorkPackage' + end + + it 'has a message id header' do + created_at = work_package.created_at.strftime('%Y%m%d%H%M%S') + + expect(mail['Message-ID'].value) + .to eql "" + end + + it 'has a references header' do + created_at = work_package.created_at.strftime('%Y%m%d%H%M%S') + + expect(mail['References'].value) + .to eql "" + end + + it 'has a work package assignee header' do + expect(mail['X-OpenProject-WorkPackage-Assignee'].value) + .to eql work_package.assigned_to.login + end + end +end diff --git a/spec/services/notifications/mail_service_spec.rb b/spec/services/notifications/mail_service_spec.rb index 795c18a7e9..be03cd6d27 100644 --- a/spec/services/notifications/mail_service_spec.rb +++ b/spec/services/notifications/mail_service_spec.rb @@ -34,12 +34,96 @@ describe Notifications::MailService, type: :model do subject(:call) { instance.call } let(:recipient) do - FactoryBot.build_stubbed(:user) + FactoryBot.build_stubbed(:user, + preference: FactoryBot.build_stubbed(:user_preference, + settings: { + immediate_reminders: { + mentioned: immediate_reminders_mentioned + } + })) end let(:actor) do FactoryBot.build_stubbed(:user) end let(:instance) { described_class.new(notification) } + let(:immediate_reminders_mentioned) { true } + + context 'with a work package journal notification' do + let(:journal) do + FactoryBot.build_stubbed(:work_package_journal).tap do |j| + allow(j) + .to receive(:initial?) + .and_return(journal_initial) + end + end + let(:read_ian) { false } + let(:reason_ian) { :mentioned } + let(:notification) do + FactoryBot.build_stubbed(:notification, + journal: journal, + recipient: recipient, + actor: actor, + reason_ian: reason_ian, + read_ian: read_ian) + end + let(:journal_initial) { false } + + let(:mail) do + mail = instance_double(ActionMailer::MessageDelivery) + + allow(WorkPackageMailer) + .to receive(:mentioned) + .and_return(mail) + + allow(mail) + .to receive(:deliver_later) + + mail + end + + before do + mail + end + + shared_examples_for 'sends a mentioned mail' do + it 'sends a mail' do + call + + expect(WorkPackageMailer) + .to have_received(:mentioned) + .with(recipient, + journal) + + expect(mail) + .to have_received(:deliver_later) + end + end + + shared_examples_for 'sends no mentioned mail' do + it 'sends no mail' do + call + + expect(WorkPackageMailer) + .not_to have_received(:mentioned) + end + end + + context 'with the notification mentioning the user' do + it_behaves_like 'sends a mentioned mail' + end + + context 'with the notification not mentioning the user' do + let(:reason_ian) { false } + + it_behaves_like 'sends no mentioned mail' + end + + context 'with the notification mentioning the user but with the recipient having deactivated the mail' do + let(:immediate_reminders_mentioned) { false } + + it_behaves_like 'sends no mentioned mail' + end + end context 'with a wiki_content journal notification' do let(:journal) do