diff --git a/app/helpers/mail_digest_helper.rb b/app/helpers/mail_digest_helper.rb index 4152f04304..158c9fa8c2 100644 --- a/app/helpers/mail_digest_helper.rb +++ b/app/helpers/mail_digest_helper.rb @@ -42,6 +42,16 @@ module MailDigestHelper summary end + def date_alerts_text(notification, html: true) + date_value = date_value(notification) + is_past, is_overdue, days_diff = text_modifiers_for(notification, date_value) + + alert_text = build_alert_text(date_value, is_past, is_overdue, days_diff) + property_text = build_property_text(notification, is_overdue, days_diff) + + highlight_overdue("#{property_text} #{alert_text}", is_overdue, html) + end + def digest_notification_timestamp_text(notification, html: true) journal = notification.journal user = html ? link_to_user(journal.user, only_path: false) : journal.user.name @@ -74,4 +84,43 @@ module MailDigestHelper def number_of_authors(notifications) notifications.group_by { |n| n[:actor_id] }.count end + + def date_value(notification) + work_package = notification.resource + notification.reason == "date_alert_start_date" ? work_package.start_date : work_package.due_date + end + + def text_modifiers_for(notification, value) + return unless value + + is_past = value.before?(Time.zone.today) + is_overdue = is_past && notification.reason == "date_alert_due_date" + difference_in_days = (value - Time.zone.today).to_i.abs + [is_past, is_overdue, difference_in_days] + end + + def build_property_text(notification, is_overdue, days_diff) + return I18n.t('js.notifications.date_alerts.overdue') if is_overdue && days_diff > 0 + return I18n.t('js.notifications.date_alerts.milestone_date') if notification.resource.milestone? + return I18n.t('js.work_packages.properties.startDate') if notification.reason == "date_alert_start_date" + + I18n.t('js.work_packages.properties.dueDate') + end + + def build_alert_text(date_value, is_past, is_overdue, days_diff) + return I18n.t('js.notifications.date_alerts.property_is_deleted') unless date_value + return I18n.t('js.notifications.date_alerts.property_today') if days_diff == 0 + + days_text = I18n.t('js.units.day', count: days_diff) + return I18n.t('js.notifications.date_alerts.overdue_since', difference_in_days: days_text) if is_overdue + return I18n.t('js.notifications.date_alerts.property_was', difference_in_days: days_text) if is_past + + I18n.t('js.notifications.date_alerts.property_is', difference_in_days: days_text) + end + + def highlight_overdue(text, is_overdue, html) + return text unless html && is_overdue + + content_tag :span, text, style: 'color: #C92A2A' + end end diff --git a/app/mailers/digest_mailer.rb b/app/mailers/digest_mailer.rb index 84d16016b9..e5134781ac 100644 --- a/app/mailers/digest_mailer.rb +++ b/app/mailers/digest_mailer.rb @@ -86,7 +86,11 @@ class DigestMailer < ApplicationMailer def load_notifications(notification_ids) Notification .where(id: notification_ids) - .includes(:project, :resource, journal: %i[data attachable_journals customizable_journals]) - .reject { |notification| notification.resource.nil? || notification.project.nil? || notification.journal.nil? } + .includes(:project, :resource) + .reject do |notification| + notification.resource.nil? || + notification.project.nil? || + (notification.journal.nil? && !notification.date_alert?) + end end end diff --git a/app/models/notification.rb b/app/models/notification.rb index 3ec7e172c4..1c85c8ec1e 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -29,4 +29,8 @@ class Notification < ApplicationRecord :mail_alert_unsent, :recipient, :visible + + def date_alert? + reason.in?(["date_alert_start_date", "date_alert_due_date"]) + end end diff --git a/app/views/digest_mailer/work_packages.html.erb b/app/views/digest_mailer/work_packages.html.erb index c20d8d4b22..4318594c6b 100644 --- a/app/views/digest_mailer/work_packages.html.erb +++ b/app/views/digest_mailer/work_packages.html.erb @@ -17,7 +17,7 @@ salutation: I18n.t(:'mail.salutation', user: @user.firstname) } %> - <% @aggregated_notifications.first(DigestMailer::MAX_SHOWN_WORK_PACKAGES).each do | work_package, notifications_by_work_package| %> + <% @aggregated_notifications.first(DigestMailer::MAX_SHOWN_WORK_PACKAGES).each do |work_package, notifications_by_work_package| %> <%= render layout: 'mailer/notification_row', locals: { work_package: work_package, @@ -29,7 +29,11 @@ > diff --git a/app/views/digest_mailer/work_packages.text.erb b/app/views/digest_mailer/work_packages.text.erb index 4d2d3e5023..8ea6769d5c 100644 --- a/app/views/digest_mailer/work_packages.text.erb +++ b/app/views/digest_mailer/work_packages.text.erb @@ -9,7 +9,11 @@ <%= "=" * (('# ' + work_package.id.to_s + work_package.subject).length + 4) %> <% unique_reasons = unique_reasons_of_notifications(notifications_by_work_package) %> - <% unique_reasons.each_with_index do |reason, index| %><%= I18n.t(:"mail.work_packages.reason.#{reason || :unknown}", default: '-') %><%= ', ' unless unique_reasons.size-1 == index %><% end %>: <%= digest_notification_timestamp_text(notifications_by_work_package.first, html: false) %> + <% unique_reasons.each_with_index do |reason, index| %><%= I18n.t(:"mail.work_packages.reason.#{reason || :unknown}", default: '-') %><%= ', ' unless unique_reasons.size-1 == index %><% end %>: <% if notifications_by_work_package.first.date_alert? %> + <%= date_alerts_text(notifications_by_work_package.first, html: false) %> + <% else %> + <%= digest_notification_timestamp_text(notifications_by_work_package.first, html: false) %> + <% end %> <%= digest_additional_author_text(notifications_by_work_package) %> <% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 9c0501773b..611ede0ccd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2109,6 +2109,8 @@ en: mentioned: 'Mentioned' subscribed: 'all' prefix: 'Received because of the notification setting: %{reason}' + date_alert_start_date: 'Date alert' + date_alert_due_date: 'Date alert' see_all: 'See all' updated_at: 'Updated at %{timestamp} by %{user}' diff --git a/spec/mailers/digest_mailer_spec.rb b/spec/mailers/digest_mailer_spec.rb index f05b0097a9..35229bc108 100644 --- a/spec/mailers/digest_mailer_spec.rb +++ b/spec/mailers/digest_mailer_spec.rb @@ -28,7 +28,7 @@ require 'spec_helper' -describe DigestMailer, type: :mailer do +describe DigestMailer do include OpenProject::ObjectLinking include ActionView::Helpers::UrlHelper include OpenProject::StaticRouting::UrlHelpers @@ -130,5 +130,180 @@ describe DigestMailer, type: :mailer do .to eql({}) end end + + describe "#date_alerts_text" do + let!(:project1) { create(:project) } + let!(:recipient) { create(:user) } + let(:notifications) { [notification] } + + context 'when notification_wp_start_past' do + let(:work_package) do + create(:work_package, subject: 'WP start past', project: project1, start_date: 1.day.ago, type: Type.first) + end + let(:notification) do + create(:notification, + reason: :date_alert_start_date, + recipient:, + resource: work_package, + project: project1) + end + + it 'matches generated text' do + expect(mail_body).to have_text('Start date was 1 day ago') + end + end + + context 'when notification_wp_start_future' do + let(:work_package) do + create(:work_package, subject: 'WP start future', project: project1, start_date: 2.days.from_now, type: Type.first) + end + let(:notification) do + create(:notification, + reason: :date_alert_start_date, + recipient:, + resource: work_package, + project: project1) + end + + it 'matches generated text' do + expect(mail_body).to have_text('Start date is in 2 days') + end + end + + context 'when notification_wp_due_past' do + let(:work_package) do + create(:work_package, subject: 'WP due past', project: project1, due_date: 3.days.ago, type: Type.first) + end + let(:notification) do + create(:notification, + reason: :date_alert_due_date, + recipient:, + resource: work_package, + project: project1) + end + + it 'matches generated text' do + expect(mail_body).to have_text('Overdue since 3 days') + end + end + + context 'when notification_wp_due_future' do + let(:work_package) do + create(:work_package, subject: 'WP due future', project: project1, due_date: 3.days.from_now, type: Type.first) + end + let(:notification) do + create(:notification, + reason: :date_alert_due_date, + recipient:, + resource: work_package, + project: project1) + end + + it 'matches generated text' do + expect(mail_body).to have_text('Finish date is in 3 days') + end + end + + context 'when notification_milestone_past' do + let(:milestone_type) { create(:type_milestone) } + let(:work_package) do + create(:work_package, subject: 'Milestone WP past', project: project1, type: milestone_type, due_date: 2.days.ago) + end + let(:notification) do + create(:notification, + reason: :date_alert_due_date, + recipient:, + resource: work_package, + project: project1) + end + + it 'matches generated text' do + expect(mail_body).to include('Overdue since 2 days') + end + end + + context 'when notification_milestone_future' do + let(:milestone_type) { create(:type_milestone) } + let(:work_package) do + create(:work_package, subject: 'Milestone WP future', project: project1, type: milestone_type, due_date: 1.day.from_now) + end + let(:notification) do + create(:notification, + reason: :date_alert_due_date, + recipient:, + resource: work_package, + project: project1) + end + + it 'matches generated text' do + expect(mail_body).to have_text('Milestone date is in 1 day') + end + end + + context 'when notification_wp_unset_date' do + let(:work_package) { create(:work_package, subject: 'Unset date', project: project1, due_date: nil, type: Type.first) } + let(:notification) do + create(:notification, + reason: :date_alert_due_date, + recipient:, + resource: work_package, + project: project1) + end + + it 'matches generated text' do + expect(mail_body).to have_text('Finish date is deleted') + end + end + + context 'when notification_wp_due_today' do + let(:work_package) do + create(:work_package, subject: 'Due today', project: project1, due_date: Time.zone.today, type: Type.first) + end + let(:notification) do + create(:notification, + reason: :date_alert_due_date, + recipient:, + resource: work_package, + project: project1) + end + + it 'matches generated text' do + expect(mail_body).to have_text('Finish date is today') + end + end + + context 'when notification_wp_double_date_alert' do + let(:work_package) do + create(:work_package, subject: 'Alert + Mention', project: project1, due_date: 1.day.from_now, type: Type.first) + end + let(:notification) do + create(:notification, + reason: :date_alert_due_date, + recipient:, + resource: work_package, + project: project1) + end + + it 'matches generated text' do + expect(mail_body).to have_text('Finish date is in 1 day') + end + end + + context 'when notification is mentioned and no journal' do + let(:work_package) { create(:work_package, subject: 'Unset date', project: project1, due_date: nil, type: Type.first) } + let(:notification) do + create(:notification, + reason: :mentioned, + recipient:, + resource: work_package, + project: project1, + journal: nil) + end + + it 'does not send the email' do + expect(mail.body).to eq("") + end + end + end end end
- <%= digest_notification_timestamp_text(notifications_by_work_package.first) %> + <% if notifications_by_work_package.first.date_alert? %> + <%= date_alerts_text(notifications_by_work_package.first) %> + <% else %> + <%= digest_notification_timestamp_text(notifications_by_work_package.first) %> + <% end %> <%= digest_additional_author_text(notifications_by_work_package) %>